Skip to content

Commit 6548390

Browse files
committed
firmware: Flash the whole EV3 at once.
Research in pybricks/support#2375 (comment) and below shows that erasing the firmware and writing it sector by sector causes hangs during the flashing process. Instead, we should erase the whole firmware at once, and flash it at once. This successfully loads new EV3 firmware without hangs.
1 parent 44237e0 commit 6548390

File tree

1 file changed

+66
-37
lines changed

1 file changed

+66
-37
lines changed

src/firmware/sagas.ts

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2020-2025 The Pybricks Authors
2+
// Copyright (c) 2020-2026 The Pybricks Authors
33

44
import {
55
FirmwareReader,
@@ -1138,6 +1138,7 @@ function* handleFlashEV3(action: ReturnType<typeof firmwareFlashEV3>): Generator
11381138
function* sendCommand(
11391139
command: number,
11401140
payload?: Uint8Array,
1141+
options?: { timeoutms?: number },
11411142
): SagaGenerator<[DataView | undefined, Error | undefined]> {
11421143
// We need to start listing for reply before sending command in order
11431144
// to avoid race conditions.
@@ -1163,9 +1164,11 @@ function* handleFlashEV3(action: ReturnType<typeof firmwareFlashEV3>): Generator
11631164
return [undefined, sendError];
11641165
}
11651166

1167+
const timeoutms = options?.timeoutms ?? 5000;
1168+
11661169
const { reply, timeout } = yield* race({
11671170
reply: take(replyChannel),
1168-
timeout: delay(5000),
1171+
timeout: delay(timeoutms),
11691172
});
11701173

11711174
replyChannel.close();
@@ -1238,63 +1241,89 @@ function* handleFlashEV3(action: ReturnType<typeof firmwareFlashEV3>): Generator
12381241
// FIXME: should be called much earlier.
12391242
yield* put(didStart());
12401243

1241-
const sectorSize = 64 * 1024; // flash memory sector size
12421244
const maxPayloadSize = 1018; // maximum payload size for EV3 commands
12431245

1244-
for (let i = 0; i < action.firmware.byteLength; i += sectorSize) {
1245-
const sectorData = action.firmware.slice(i, i + sectorSize);
1246-
assert(sectorData.byteLength <= sectorSize, 'sector data too large');
1246+
const erasePayload = new DataView(new ArrayBuffer(8));
1247+
erasePayload.setUint32(0, 0, true); // start address
1248+
erasePayload.setUint32(4, action.firmware.byteLength, true); // size
1249+
console.debug(`Erasing bytes [0x0, ${hex(action.firmware.byteLength, 0)})`);
1250+
1251+
yield* put(
1252+
alertsShowAlert(
1253+
'firmware',
1254+
'flashProgress',
1255+
{
1256+
action: 'erase',
1257+
progress: undefined,
1258+
},
1259+
firmwareBleProgressToastId,
1260+
true,
1261+
),
1262+
);
1263+
1264+
// Measured erase rate is approximately .25 kB/ms. This was on a powerful
1265+
// computer so it may be that flashing from something like a raspberry pi
1266+
// would take longer. We'll set a timeout three times as long as would have
1267+
// taken at the measured rate.
1268+
const eraseTimeoutMs = (action.firmware.byteLength / 256) * 1000 * 3;
1269+
const startTime = Date.now();
1270+
const [, eraseError] = yield* sendCommand(
1271+
0xf0,
1272+
new Uint8Array(erasePayload.buffer),
1273+
{ timeoutms: eraseTimeoutMs },
1274+
);
1275+
console.debug(
1276+
`EV3 erase took ${Date.now() - startTime} ms for ${
1277+
action.firmware.byteLength
1278+
} bytes, timeout was ${eraseTimeoutMs} ms`,
1279+
);
1280+
1281+
if (eraseError) {
1282+
yield* put(
1283+
alertsShowAlert('alerts', 'unexpectedError', {
1284+
error: eraseError,
1285+
}),
1286+
);
1287+
// FIXME: should have a better error reason
1288+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1289+
yield* put(firmwareDidFailToFlashEV3());
1290+
yield* cleanup();
1291+
return;
1292+
}
12471293

1248-
const erasePayload = new DataView(new ArrayBuffer(8));
1249-
erasePayload.setUint32(0, i, true);
1250-
erasePayload.setUint32(4, sectorData.byteLength, true);
1251-
const [, eraseError] = yield* sendCommand(
1252-
0xf0,
1253-
new Uint8Array(erasePayload.buffer),
1294+
// If we don't write an exact multiple of the sector size, the flash process
1295+
// will hang on the last write we send.
1296+
const firmware = action.firmware;
1297+
for (let i = 0; i < firmware.byteLength; i += maxPayloadSize) {
1298+
const payload = firmware.slice(i, i + maxPayloadSize);
1299+
console.debug(
1300+
`Programming bytes [${hex(i, 0)}, ${hex(i + maxPayloadSize, 0)})`,
12541301
);
12551302

1256-
if (eraseError) {
1303+
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1304+
if (sendError) {
12571305
yield* put(
12581306
alertsShowAlert('alerts', 'unexpectedError', {
1259-
error: eraseError,
1307+
error: sendError,
12601308
}),
12611309
);
12621310
// FIXME: should have a better error reason
1263-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1311+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
12641312
yield* put(firmwareDidFailToFlashEV3());
12651313
yield* cleanup();
12661314
return;
12671315
}
12681316

1269-
for (let j = 0; j < sectorData.byteLength; j += maxPayloadSize) {
1270-
const payload = sectorData.slice(j, j + maxPayloadSize);
1271-
1272-
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1273-
if (sendError) {
1274-
yield* put(
1275-
alertsShowAlert('alerts', 'unexpectedError', {
1276-
error: sendError,
1277-
}),
1278-
);
1279-
// FIXME: should have a better error reason
1280-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
1281-
yield* put(firmwareDidFailToFlashEV3());
1282-
yield* cleanup();
1283-
return;
1284-
}
1285-
}
1286-
1287-
yield* put(
1288-
didProgress((i + sectorData.byteLength) / action.firmware.byteLength),
1289-
);
1317+
const progress = (i + payload.byteLength) / firmware.byteLength;
1318+
yield* put(didProgress(progress));
12901319

12911320
yield* put(
12921321
alertsShowAlert(
12931322
'firmware',
12941323
'flashProgress',
12951324
{
12961325
action: 'flash',
1297-
progress: (i + sectorData.byteLength) / action.firmware.byteLength,
1326+
progress: progress,
12981327
},
12991328
firmwareBleProgressToastId,
13001329
true,

0 commit comments

Comments
 (0)