Skip to content

Commit 2d2777a

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 a33c580 commit 2d2777a

File tree

1 file changed

+65
-36
lines changed

1 file changed

+65
-36
lines changed

src/firmware/sagas.ts

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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();
@@ -1209,63 +1212,89 @@ function* handleFlashEV3(action: ReturnType<typeof firmwareFlashEV3>): Generator
12091212
// FIXME: should be called much earlier.
12101213
yield* put(didStart());
12111214

1212-
const sectorSize = 64 * 1024; // flash memory sector size
12131215
const maxPayloadSize = 1018; // maximum payload size for EV3 commands
12141216

1215-
for (let i = 0; i < action.firmware.byteLength; i += sectorSize) {
1216-
const sectorData = action.firmware.slice(i, i + sectorSize);
1217-
assert(sectorData.byteLength <= sectorSize, 'sector data too large');
1217+
const erasePayload = new DataView(new ArrayBuffer(8));
1218+
erasePayload.setUint32(0, 0, true); // start address
1219+
erasePayload.setUint32(4, action.firmware.byteLength, true); // size
1220+
console.debug(`Erasing bytes [0x0, ${hex(action.firmware.byteLength, 0)})`);
12181221

1219-
const erasePayload = new DataView(new ArrayBuffer(8));
1220-
erasePayload.setUint32(0, i, true);
1221-
erasePayload.setUint32(4, sectorData.byteLength, true);
1222-
const [, eraseError] = yield* sendCommand(
1223-
0xf0,
1224-
new Uint8Array(erasePayload.buffer),
1222+
yield* put(
1223+
alertsShowAlert(
1224+
'firmware',
1225+
'flashProgress',
1226+
{
1227+
action: 'erase',
1228+
progress: undefined,
1229+
},
1230+
firmwareBleProgressToastId,
1231+
true,
1232+
),
1233+
);
1234+
1235+
// Measured erase rate is approximately .25 kB/ms. This was on a powerful
1236+
// computer so it may be that flashing from something like a raspberry pi
1237+
// would take longer. We'll set a timeout three times as long as would have
1238+
// taken at the measured rate.
1239+
const eraseTimeoutMs = (action.firmware.byteLength / 256) * 1000 * 3;
1240+
const startTime = Date.now();
1241+
const [, eraseError] = yield* sendCommand(
1242+
0xf0,
1243+
new Uint8Array(erasePayload.buffer),
1244+
{ timeoutms: eraseTimeoutMs },
1245+
);
1246+
console.debug(
1247+
`EV3 erase took ${Date.now() - startTime} ms for ${
1248+
action.firmware.byteLength
1249+
} bytes, timeout was ${eraseTimeoutMs} ms`,
1250+
);
1251+
1252+
if (eraseError) {
1253+
yield* put(
1254+
alertsShowAlert('alerts', 'unexpectedError', {
1255+
error: eraseError,
1256+
}),
12251257
);
1258+
// FIXME: should have a better error reason
1259+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1260+
yield* put(firmwareDidFailToFlashEV3());
1261+
yield* cleanup();
1262+
return;
1263+
}
12261264

1227-
if (eraseError) {
1265+
// If we don't write an exact multiple of the sector size, the flash process
1266+
// will hang on the last write we send.
1267+
const firmware = action.firmware;
1268+
for (let i = 0; i < firmware.byteLength; i += maxPayloadSize) {
1269+
const payload = firmware.slice(i, i + maxPayloadSize);
1270+
console.debug(
1271+
`Programming bytes [${hex(i, 0)}, ${hex(i + maxPayloadSize, 0)})`,
1272+
);
1273+
1274+
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1275+
if (sendError) {
12281276
yield* put(
12291277
alertsShowAlert('alerts', 'unexpectedError', {
1230-
error: eraseError,
1278+
error: sendError,
12311279
}),
12321280
);
12331281
// FIXME: should have a better error reason
1234-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1282+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
12351283
yield* put(firmwareDidFailToFlashEV3());
12361284
yield* cleanup();
12371285
return;
12381286
}
12391287

1240-
for (let j = 0; j < sectorData.byteLength; j += maxPayloadSize) {
1241-
const payload = sectorData.slice(j, j + maxPayloadSize);
1242-
1243-
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1244-
if (sendError) {
1245-
yield* put(
1246-
alertsShowAlert('alerts', 'unexpectedError', {
1247-
error: sendError,
1248-
}),
1249-
);
1250-
// FIXME: should have a better error reason
1251-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
1252-
yield* put(firmwareDidFailToFlashEV3());
1253-
yield* cleanup();
1254-
return;
1255-
}
1256-
}
1257-
1258-
yield* put(
1259-
didProgress((i + sectorData.byteLength) / action.firmware.byteLength),
1260-
);
1288+
const progress = (i + payload.byteLength) / firmware.byteLength;
1289+
yield* put(didProgress(progress));
12611290

12621291
yield* put(
12631292
alertsShowAlert(
12641293
'firmware',
12651294
'flashProgress',
12661295
{
12671296
action: 'flash',
1268-
progress: (i + sectorData.byteLength) / action.firmware.byteLength,
1297+
progress: progress,
12691298
},
12701299
firmwareBleProgressToastId,
12711300
true,

0 commit comments

Comments
 (0)