fix BLE upload: use g_bleFlashStart for correct track address and byte count on finalize

This commit is contained in:
zyphlar
2026-07-04 03:28:14 -07:00
parent 24de5d8d39
commit 12140b7ff6
4 changed files with 247 additions and 75 deletions
+10
View File
@@ -75,3 +75,13 @@ and reuse `ble.js`'s protocol constants.
local name just shows "Track N". local name just shows "Track N".
- **MTU:** data is chunked to 180 bytes to stay within a modest negotiated ATT - **MTU:** data is chunked to 180 bytes to stay within a modest negotiated ATT
MTU (firmware char max is 240). MTU (firmware char max is 240).
- **Name shows as "BabyM" (truncated):** the 128-bit service UUID fills the 31-byte
advertisement, leaving room for only 5 name characters. The browser can't read
the full GAP name (that service is blocklisted in Web Bluetooth). Fix it in the
firmware by moving the name to the scan response, which has its own 31 bytes:
replace `Bluefruit.Advertising.addName();` with `Bluefruit.ScanResponse.addName();`.
- **Reconnecting after a disconnect:** a just-disconnected peripheral leaves the
link half-open briefly, so the first reconnect could fail until a page refresh.
`connect()` now retries (3×, 600 ms backoff) after an explicit disconnect, which
recovers without refreshing. Scanning matches on the service UUID first, so a
truncated advertised name no longer breaks the name filter.
+76 -25
View File
@@ -34,8 +34,8 @@ const TAG_DELETE = 0xd0;
export const MAX_TRACKS = 32; export const MAX_TRACKS = 32;
// audioCmd.setMaxLen(240) / audioData.setMaxLen(240) on the firmware. We chunk the // Keep below ATT MTU - 3. The firmware calls configPrphBandwidth(BANDWIDTH_MAX)
// data characteristic conservatively so it fits even a modest negotiated ATT MTU. // so Chrome typically negotiates MTU 247 (244-byte payload). 180 stays well clear.
const DATA_CHUNK = 180; const DATA_CHUNK = 180;
export function isSupported() { export function isSupported() {
@@ -53,6 +53,7 @@ export class BabyMobile extends EventTarget {
this._statBytes = 0; this._statBytes = 0;
this._onStat = this._onStat.bind(this); this._onStat = this._onStat.bind(this);
this._onDisconnect = this._onDisconnect.bind(this); this._onDisconnect = this._onDisconnect.bind(this);
this._connecting = false;
} }
get connected() { get connected() {
@@ -71,26 +72,57 @@ export class BabyMobile extends EventTarget {
if (!isSupported()) { if (!isSupported()) {
throw new Error('Web Bluetooth is not available in this browser. On iOS use the Bluefy browser; on desktop/Android use Chrome or Edge.'); throw new Error('Web Bluetooth is not available in this browser. On iOS use the Bluefy browser; on desktop/Android use Chrome or Edge.');
} }
this._log('Requesting device…'); if (this._connecting) throw new Error('Already connecting…');
this.device = await navigator.bluetooth.requestDevice({ this._connecting = true;
filters: [{ namePrefix: 'BabyMobile' }, { services: [SVC_UUID] }], try {
optionalServices: [SVC_UUID], // Tear down any stale device/handle from a previous session so reconnects
}); // don't trip over a half-open GATT or duplicate event listeners.
this.device.addEventListener('gattserverdisconnected', this._onDisconnect); this._teardown();
this._log(`Connecting to ${this.device.name || 'device'}`); this._log('Requesting device');
this.server = await this.device.gatt.connect(); // Primary match is the 128-bit service UUID (rock solid). The name filter is
const svc = await this.server.getPrimaryService(SVC_UUID); // a loose prefix because the advertised name may be radio-truncated.
this.cmd = await svc.getCharacteristic(CMD_UUID); this.device = await navigator.bluetooth.requestDevice({
this.data = await svc.getCharacteristic(DATA_UUID); filters: [{ services: [SVC_UUID] }, { namePrefix: 'Baby' }],
this.stat = await svc.getCharacteristic(STAT_UUID); optionalServices: [SVC_UUID],
});
this.device.addEventListener('gattserverdisconnected', this._onDisconnect);
await this.stat.startNotifications(); await this._connectGatt(3);
this.stat.addEventListener('characteristicvaluechanged', this._onStat); this._log('Connected.');
this._emit('connected', { name: this.device.name });
return this.device.name;
} finally {
this._connecting = false;
}
}
this._log('Connected.'); // Connect + discover with retries. A freshly-disconnected peripheral often
this._emit('connected', { name: this.device.name }); // leaves the link half-open for a moment; the first attempt then fails with
return this.device.name; // "Connection failed"/"GATT operation failed" until a browser refresh. Retrying
// after an explicit disconnect + short backoff recovers without a refresh.
async _connectGatt(attempts) {
let lastErr;
for (let i = 0; i < attempts; i++) {
try {
if (this.device.gatt.connected) this.device.gatt.disconnect();
this._log(`Connecting${i ? ` (retry ${i})` : ''} to ${this.device.name || 'device'}`);
this.server = await this.device.gatt.connect();
const svc = await this.server.getPrimaryService(SVC_UUID);
this.cmd = await svc.getCharacteristic(CMD_UUID);
this.data = await svc.getCharacteristic(DATA_UUID);
this.stat = await svc.getCharacteristic(STAT_UUID);
await this.stat.startNotifications();
this.stat.addEventListener('characteristicvaluechanged', this._onStat);
return;
} catch (err) {
lastErr = err;
this._log(`Connect attempt ${i + 1} failed: ${err.message || err}`);
try { this.device.gatt.disconnect(); } catch {}
if (i < attempts - 1) await new Promise((r) => setTimeout(r, 600));
}
}
throw lastErr;
} }
async disconnect() { async disconnect() {
@@ -99,8 +131,23 @@ export class BabyMobile extends EventTarget {
} }
} }
// Fully detach the current device + listeners (used before a fresh connect).
_teardown() {
if (this.stat) {
try { this.stat.removeEventListener('characteristicvaluechanged', this._onStat); } catch {}
}
if (this.device) {
try { this.device.removeEventListener('gattserverdisconnected', this._onDisconnect); } catch {}
try { if (this.device.gatt.connected) this.device.gatt.disconnect(); } catch {}
}
this.device = this.server = this.cmd = this.data = this.stat = null;
}
_onDisconnect() { _onDisconnect() {
this._log('Disconnected.'); this._log('Disconnected.');
if (this.stat) {
try { this.stat.removeEventListener('characteristicvaluechanged', this._onStat); } catch {}
}
this.server = this.cmd = this.data = this.stat = null; this.server = this.cmd = this.data = this.stat = null;
this._emit('disconnected', {}); this._emit('disconnected', {});
} }
@@ -202,18 +249,22 @@ export class BabyMobile extends EventTarget {
this._log(`Starting upload of ${total} bytes to slot ${track}`); this._log(`Starting upload of ${total} bytes to slot ${track}`);
await this._writeCmd([CMD_UPLOAD_START, track & 0xff]); await this._writeCmd([CMD_UPLOAD_START, track & 0xff]);
// writeWithoutResponse: await each chunk so the browser's queue provides // Send all data chunks. Progress is tracked client-side (bytes sent).
// back-pressure. The firmware writes each packet to LittleFS synchronously. // The firmware queues bytes into a ring buffer; actual flash writes happen
// in the background. We do NOT poll per-packet notifications here — that
// caused BLE notification-buffer overflow for large files.
for (let off = 0; off < total; off += DATA_CHUNK) { for (let off = 0; off < total; off += DATA_CHUNK) {
const chunk = wavBytes.subarray(off, Math.min(off + DATA_CHUNK, total)); const chunk = wavBytes.subarray(off, Math.min(off + DATA_CHUNK, total));
await this.data.writeValueWithoutResponse(chunk); await this.data.writeValueWithoutResponse(chunk);
if (onProgress) onProgress(Math.min(off + chunk.length, total), total); if (onProgress) onProgress(off + chunk.length, total);
} }
// Wait until the device's acknowledged byte count catches up (or times out). // Tell the firmware we're done. It will drain its ring buffer, write the
await this._waitForAck(total, 8000); // track table, then send ONE finalize notification with the total byte count.
await this._writeCmd([CMD_UPLOAD_END]); await this._writeCmd([CMD_UPLOAD_END]);
// Wait for that single finalize notification (up to 20 s to allow flash writes).
await this._waitForAck(total, 20000);
const ok = acked >= total; const ok = acked >= total;
if (ok) { if (ok) {
this._log(`✅ Upload complete: ${acked}/${total} bytes acknowledged.`); this._log(`✅ Upload complete: ${acked}/${total} bytes acknowledged.`);
+1 -1
View File
@@ -1,5 +1,5 @@
// sw.js — minimal offline cache so the PWA launches without a network. // sw.js — minimal offline cache so the PWA launches without a network.
const CACHE = 'babymobile-v2'; const CACHE = 'babymobile-v3';
const ASSETS = [ const ASSETS = [
'./', './index.html', './styles.css', './', './index.html', './styles.css',
'./app.js', './ble.js', './wav.js', './app.js', './ble.js', './wav.js',
+160 -49
View File
@@ -185,12 +185,30 @@ BLECharacteristic audioStat = BLECharacteristic("12340004-0000-1000-8000-00805f9
// BLE upload state // BLE upload state
volatile bool g_bleUploading = false; volatile bool g_bleUploading = false;
volatile uint32_t g_bleWriteAddr = 0; volatile uint32_t g_bleWriteLen = 0;
volatile uint32_t g_bleWriteLen = 0;
#ifdef POC_INTERNAL_FLASH #ifdef POC_INTERNAL_FLASH
File g_pocFile(InternalFS); // open file handle (read or write) File g_pocFile(InternalFS); // open file handle (read or write)
uint8_t g_pocWriteTrack = 0; // track slot being written via BLE uint8_t g_pocWriteTrack = 0; // track slot being written via BLE
#else
// Ring buffer between BLE callbacks and loop() flash writes.
// Callbacks return immediately; flash I/O (especially sector erase ~100ms)
// happens in loop() so the SoftDevice receive buffer never stalls.
// Size: IS25LP128F sector erase takes up to 300 ms; at ~56 KB/s upload rate
// that is ~17 KB of incoming data. 32 KB covers it with margin.
#define BLE_FLASH_BUF 32768u
static uint8_t g_bleBuf[BLE_FLASH_BUF];
static volatile uint32_t g_bleBufHead = 0; // advanced by BLE callback
static volatile uint32_t g_bleBufTail = 0; // advanced by loop()
static uint32_t g_bleFlashAddr = 0; // current flash write head
static uint32_t g_bleFlashStart = 0; // address where this upload began
static uint32_t g_bleFlashErased = 0; // end of last erased sector
static uint8_t g_bleWriteTrack = 0;
static volatile bool g_bleFinalizing = false;
// Shared next-free flash pointer — used by both BLE and serial upload paths.
// Reset to AUDIO_START_ADDR whenever track 0 is uploaded.
static uint32_t g_flashNextFree = AUDIO_START_ADDR;
#endif #endif
// ============================================================ // ============================================================
@@ -439,6 +457,17 @@ void loadTrackTable() {
Serial.print("Loaded "); Serial.print("Loaded ");
Serial.print(g_numTracks); Serial.print(g_numTracks);
Serial.println(" tracks from flash"); Serial.println(" tracks from flash");
// Advance g_flashNextFree past all existing tracks so BLE/serial uploads
// to non-zero slots don't overwrite data from a previous session.
if (g_numTracks > 0) {
uint32_t hiWater = 0;
for (uint8_t i = 0; i < g_numTracks; i++) {
uint32_t trackEnd = g_trackStart[i] + g_trackLen[i];
if (trackEnd > hiWater) hiWater = trackEnd;
}
g_flashNextFree = ((hiWater + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR;
}
#endif #endif
} }
@@ -593,13 +622,21 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
Serial.println(g_pocWriteTrack); Serial.println(g_pocWriteTrack);
} }
#else #else
if (len >= 4) { if (len >= 2) {
g_bleWriteAddr = ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | data[3]; g_bleWriteTrack = data[1];
g_bleUploading = true; // Track 0 resets allocation; subsequent tracks append
// Erase 64K block at target if (g_bleWriteTrack == 0) g_flashNextFree = AUDIO_START_ADDR;
flashEraseBlock64K(g_bleWriteAddr & ~(FLASH_BLOCK_64K - 1)); g_bleFlashAddr = g_flashNextFree;
Serial.print("BLE: Start write at 0x"); g_bleFlashStart = g_bleFlashAddr; // save start for finalize
Serial.println(g_bleWriteAddr, HEX); g_bleFlashErased = g_bleFlashAddr; // nothing erased yet
g_bleBufHead = g_bleBufTail = 0;
g_bleFinalizing = false;
g_bleUploading = true;
g_bleWriteLen = 0;
Serial.print("BLE: Start write track ");
Serial.print(g_bleWriteTrack);
Serial.print(" at 0x");
Serial.println(g_bleFlashAddr, HEX);
} }
#endif #endif
break; break;
@@ -607,10 +644,17 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
case 0x03: // Finish upload case 0x03: // Finish upload
#ifdef POC_INTERNAL_FLASH #ifdef POC_INTERNAL_FLASH
if (g_pocFile) g_pocFile.close(); if (g_pocFile) g_pocFile.close();
#endif
g_bleUploading = false; g_bleUploading = false;
loadTrackTable(); loadTrackTable();
Serial.println("BLE: Upload complete"); Serial.println("BLE: Upload complete");
#else
// Signal loop() to finalize after ring buffer drains.
// Don't call loadTrackTable() here — we're in a BLE callback.
if (g_bleUploading) {
g_bleFinalizing = true;
Serial.println("BLE: Finalizing upload...");
}
#endif
break; break;
case 0x04: // Play track case 0x04: // Play track
@@ -630,7 +674,6 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
// End packet: [0xFF, num_tracks, 0, 0] // End packet: [0xFF, num_tracks, 0, 0]
// Byte 0 >= 0x80 distinguishes list responses from upload-progress // Byte 0 >= 0x80 distinguishes list responses from upload-progress
// packets (which always have byte 0 == 0x00 for files < 16 MB). // packets (which always have byte 0 == 0x00 for files < 16 MB).
#ifdef POC_INTERNAL_FLASH
for (uint8_t i = 0; i < g_numTracks; i++) { for (uint8_t i = 0; i < g_numTracks; i++) {
uint32_t audioBytes = g_trackLen[i] - g_trackDataOff[i]; uint32_t audioBytes = g_trackLen[i] - g_trackDataOff[i];
uint32_t bps = g_trackBits[i] / 8; uint32_t bps = g_trackBits[i] / 8;
@@ -652,19 +695,35 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
audioStat.notify(end, 4); audioStat.notify(end, 4);
} }
Serial.print("BLE: Listed "); Serial.print(g_numTracks); Serial.println(" tracks"); Serial.print("BLE: Listed "); Serial.print(g_numTracks); Serial.println(" tracks");
#endif
break; break;
case 0x07: // Delete track case 0x07: // Delete track
// data[1] = track index to delete. // data[1] = track index to delete.
// Responds via audioStat: [0xD0, track_idx, success(0/1), 0] // Responds via audioStat: [0xD0, track_idx, success(0/1), 0]
#ifdef POC_INTERNAL_FLASH
if (len >= 2) { if (len >= 2) {
uint8_t trkNum = data[1]; uint8_t trkNum = data[1];
bool ok = false;
#ifdef POC_INTERNAL_FLASH
char fname[16]; char fname[16];
pocFilename(trkNum, fname); pocFilename(trkNum, fname);
bool ok = InternalFS.remove(fname); ok = InternalFS.remove(fname);
if (ok) loadTrackTable(); if (ok) loadTrackTable();
#else
if (trkNum < g_numTracks) {
// Shift entries down to close the gap
for (uint8_t j = trkNum; j < g_numTracks - 1; j++) {
g_trackStart[j] = g_trackStart[j+1];
g_trackLen[j] = g_trackLen[j+1];
g_trackBits[j] = g_trackBits[j+1];
g_trackRate[j] = g_trackRate[j+1];
g_trackDataOff[j] = g_trackDataOff[j+1];
}
g_numTracks--;
writeTrackTable();
loadTrackTable();
ok = true;
}
#endif
uint8_t resp[4] = {0xD0, trkNum, (uint8_t)(ok ? 1 : 0), 0}; uint8_t resp[4] = {0xD0, trkNum, (uint8_t)(ok ? 1 : 0), 0};
audioStat.write(resp, 4); audioStat.write(resp, 4);
audioStat.notify(resp, 4); audioStat.notify(resp, 4);
@@ -672,7 +731,6 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
Serial.print(trkNum); Serial.print(trkNum);
Serial.println(ok ? " OK" : " FAILED"); Serial.println(ok ? " OK" : " FAILED");
} }
#endif
break; break;
} }
} }
@@ -687,39 +745,88 @@ void audioData_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
g_pocFile.write(data, len); g_pocFile.write(data, len);
g_bleWriteLen += len; g_bleWriteLen += len;
#else #else
// Erase new sectors as we cross boundaries // Push into ring buffer; flash I/O happens in bleFlashTick() from loop()
uint32_t endAddr = g_bleWriteAddr + len; // so this callback returns immediately without blocking the SoftDevice.
uint32_t currentSector = g_bleWriteAddr / FLASH_SECTOR; for (uint16_t i = 0; i < len; i++) {
uint32_t endSector = (endAddr - 1) / FLASH_SECTOR; uint32_t nextHead = (g_bleBufHead + 1) % BLE_FLASH_BUF;
for (uint32_t s = currentSector + 1; s <= endSector; s++) { if (nextHead == g_bleBufTail) break; // full — drop tail (shouldn't happen at 16kHz)
flashEraseSector(s * FLASH_SECTOR); g_bleBuf[g_bleBufHead] = data[i];
g_bleBufHead = nextHead;
} }
// Write data page by page
uint16_t written = 0;
while (written < len) {
uint16_t pageOff = (g_bleWriteAddr + written) % FLASH_PAGE;
uint16_t chunk = min((uint16_t)(FLASH_PAGE - pageOff), (uint16_t)(len - written));
flashPageProgram(g_bleWriteAddr + written, data + written, chunk);
written += chunk;
}
g_bleWriteAddr += len;
g_bleWriteLen += len; g_bleWriteLen += len;
#endif #endif
// Progress notifications are intentionally omitted here: sending one BLE
// Update status characteristic with bytes written // notification per 180-byte packet floods the SoftDevice TX queue (~7 k
uint8_t stat[4]; // packets for a 1.3 MB file) and causes the last notifications to be
stat[0] = (g_bleWriteLen >> 24) & 0xFF; // dropped. A single definitive notification is sent by bleFlashTick()
stat[1] = (g_bleWriteLen >> 16) & 0xFF; // when finalization completes (after CMD_UPLOAD_END drains the ring buffer
stat[2] = (g_bleWriteLen >> 8) & 0xFF; // and writes the track table).
stat[3] = g_bleWriteLen & 0xFF;
audioStat.write(stat, 4);
audioStat.notify(stat, 4);
} }
// Drain the BLE ring buffer to SPI flash one page at a time.
// Called from loop() to keep flash I/O off the BLE callback thread.
#ifndef POC_INTERNAL_FLASH
static void bleFlashTick() {
// Drain as much as we can without blocking too long
while (g_bleBufHead != g_bleBufTail) {
uint32_t avail = (g_bleBufHead - g_bleBufTail + BLE_FLASH_BUF) % BLE_FLASH_BUF;
uint16_t pageOff = (uint16_t)(g_bleFlashAddr % FLASH_PAGE);
uint16_t chunk = (uint16_t)min((uint32_t)(FLASH_PAGE - pageOff), avail);
if (chunk == 0) break;
// Lazy erase: erase the next sector just before we write into it
while (g_bleFlashAddr + chunk > g_bleFlashErased) {
flashEraseSector(g_bleFlashErased);
g_bleFlashErased += FLASH_SECTOR;
}
// Copy chunk from ring buffer into a temp page buffer
uint8_t tmp[FLASH_PAGE];
for (uint16_t i = 0; i < chunk; i++) {
tmp[i] = g_bleBuf[(g_bleBufTail + i) % BLE_FLASH_BUF];
}
flashPageProgram(g_bleFlashAddr, tmp, chunk);
g_bleBufTail = (g_bleBufTail + chunk) % BLE_FLASH_BUF;
g_bleFlashAddr += chunk;
// Yield after each page so the rest of loop() stays responsive
break;
}
// Finalize when CMD 0x03 received and ring buffer is empty
if (g_bleFinalizing && g_bleBufHead == g_bleBufTail) {
uint32_t startAddr = g_bleFlashStart;
uint32_t bytesStored = g_bleFlashAddr - g_bleFlashStart;
g_trackStart[g_bleWriteTrack] = startAddr;
g_trackLen[g_bleWriteTrack] = bytesStored;
if (g_bleWriteTrack >= g_numTracks) g_numTracks = g_bleWriteTrack + 1;
// Advance shared free pointer to next sector boundary
g_flashNextFree = ((g_bleFlashAddr + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR;
g_bleUploading = false;
g_bleFinalizing = false;
writeTrackTable();
loadTrackTable();
Serial.printf("BLE: Upload finalized — start=0x%08lX stored=%lu bytes\n", startAddr, bytesStored);
// Confirm to client: send final stat with bytes actually stored
uint8_t stat[4];
stat[0] = (bytesStored >> 24) & 0xFF;
stat[1] = (bytesStored >> 16) & 0xFF;
stat[2] = (bytesStored >> 8) & 0xFF;
stat[3] = bytesStored & 0xFF;
audioStat.write(stat, 4);
audioStat.notify(stat, 4);
}
}
#endif
void setupBLE() { void setupBLE() {
/* Note BLE requires a custom app to communicate, not regular bluetooth */ /* Note BLE requires a custom app to communicate, not regular bluetooth */
// Configure for maximum bandwidth so the SoftDevice allocates buffers large
// enough to accept 180-byte ATT payloads (MTU 183). Must be called before begin().
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin(); Bluefruit.begin();
Bluefruit.setName("BabyMobile"); Bluefruit.setName("BabyMobile");
Bluefruit.setTxPower(0); // 0 dBm — save power, short range is fine Bluefruit.setTxPower(0); // 0 dBm — save power, short range is fine
@@ -1187,10 +1294,9 @@ static uint32_t g_serBytesReceived = 0;
static File g_serFile(InternalFS); static File g_serFile(InternalFS);
#else #else
// SPI flash upload: tracks are packed sequentially starting at AUDIO_START_ADDR. // SPI flash upload: tracks are packed sequentially starting at AUDIO_START_ADDR.
// Uploading track 0 resets the allocation pointer. // Uploading track 0 resets g_flashNextFree (shared with BLE upload path).
static uint32_t g_serFlashCurAddr = 0; // current write head static uint32_t g_serFlashCurAddr = 0; // current write head
static uint32_t g_serFlashNextFree = AUDIO_START_ADDR; static uint32_t g_serFlashErasedThru = 0; // highest erased byte address (exclusive)
static uint32_t g_serFlashErasedThru = 0; // highest erased byte address
#endif #endif
static void serUploadTick() { static void serUploadTick() {
@@ -1221,8 +1327,8 @@ static void serUploadTick() {
} }
#else #else
// Track 0 resets flash allocation // Track 0 resets flash allocation
if (g_serTrack == 0) g_serFlashNextFree = AUDIO_START_ADDR; if (g_serTrack == 0) g_flashNextFree = AUDIO_START_ADDR;
g_serFlashCurAddr = g_serFlashNextFree; g_serFlashCurAddr = g_flashNextFree;
g_trackStart[g_serTrack] = g_serFlashCurAddr; g_trackStart[g_serTrack] = g_serFlashCurAddr;
// Erase first sector now; subsequent sectors erased lazily during receive // Erase first sector now; subsequent sectors erased lazily during receive
@@ -1376,8 +1482,8 @@ static void serUploadTick() {
Serial.print("OK "); Serial.print(fsz); Serial.print("OK "); Serial.print(fsz);
Serial.print("/"); Serial.println(g_serBytesReceived); Serial.print("/"); Serial.println(g_serBytesReceived);
#else #else
// Advance free pointer to next sector boundary // Advance shared free pointer to next sector boundary
g_serFlashNextFree = ((g_serFlashCurAddr + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR; g_flashNextFree = ((g_serFlashCurAddr + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR;
g_trackLen[g_serTrack] = g_serBytesReceived; g_trackLen[g_serTrack] = g_serBytesReceived;
if (g_serTrack >= g_numTracks) g_numTracks = g_serTrack + 1; if (g_serTrack >= g_numTracks) g_numTracks = g_serTrack + 1;
writeTrackTable(); writeTrackTable();
@@ -1397,6 +1503,11 @@ void loop() {
// ---- Serial upload ---- // ---- Serial upload ----
serUploadTick(); serUploadTick();
#ifndef POC_INTERNAL_FLASH
// ---- BLE flash drain ----
bleFlashTick();
#endif
// ---- Refill audio buffers ---- // ---- Refill audio buffers ----
if (g_playing) { if (g_playing) {
for (uint8_t b = 0; b < 2; b++) { for (uint8_t b = 0; b < 2; b++) {