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
+160 -49
View File
@@ -185,12 +185,30 @@ BLECharacteristic audioStat = BLECharacteristic("12340004-0000-1000-8000-00805f9
// BLE upload state
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
File g_pocFile(InternalFS); // open file handle (read or write)
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
// ============================================================
@@ -439,6 +457,17 @@ void loadTrackTable() {
Serial.print("Loaded ");
Serial.print(g_numTracks);
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
}
@@ -593,13 +622,21 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
Serial.println(g_pocWriteTrack);
}
#else
if (len >= 4) {
g_bleWriteAddr = ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | data[3];
g_bleUploading = true;
// Erase 64K block at target
flashEraseBlock64K(g_bleWriteAddr & ~(FLASH_BLOCK_64K - 1));
Serial.print("BLE: Start write at 0x");
Serial.println(g_bleWriteAddr, HEX);
if (len >= 2) {
g_bleWriteTrack = data[1];
// Track 0 resets allocation; subsequent tracks append
if (g_bleWriteTrack == 0) g_flashNextFree = AUDIO_START_ADDR;
g_bleFlashAddr = g_flashNextFree;
g_bleFlashStart = g_bleFlashAddr; // save start for finalize
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
break;
@@ -607,10 +644,17 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
case 0x03: // Finish upload
#ifdef POC_INTERNAL_FLASH
if (g_pocFile) g_pocFile.close();
#endif
g_bleUploading = false;
loadTrackTable();
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;
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]
// Byte 0 >= 0x80 distinguishes list responses from upload-progress
// packets (which always have byte 0 == 0x00 for files < 16 MB).
#ifdef POC_INTERNAL_FLASH
for (uint8_t i = 0; i < g_numTracks; i++) {
uint32_t audioBytes = g_trackLen[i] - g_trackDataOff[i];
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);
}
Serial.print("BLE: Listed "); Serial.print(g_numTracks); Serial.println(" tracks");
#endif
break;
case 0x07: // Delete track
// data[1] = track index to delete.
// Responds via audioStat: [0xD0, track_idx, success(0/1), 0]
#ifdef POC_INTERNAL_FLASH
if (len >= 2) {
uint8_t trkNum = data[1];
bool ok = false;
#ifdef POC_INTERNAL_FLASH
char fname[16];
pocFilename(trkNum, fname);
bool ok = InternalFS.remove(fname);
ok = InternalFS.remove(fname);
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};
audioStat.write(resp, 4);
audioStat.notify(resp, 4);
@@ -672,7 +731,6 @@ void audioCmd_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
Serial.print(trkNum);
Serial.println(ok ? " OK" : " FAILED");
}
#endif
break;
}
}
@@ -687,39 +745,88 @@ void audioData_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
g_pocFile.write(data, len);
g_bleWriteLen += len;
#else
// Erase new sectors as we cross boundaries
uint32_t endAddr = g_bleWriteAddr + len;
uint32_t currentSector = g_bleWriteAddr / FLASH_SECTOR;
uint32_t endSector = (endAddr - 1) / FLASH_SECTOR;
for (uint32_t s = currentSector + 1; s <= endSector; s++) {
flashEraseSector(s * FLASH_SECTOR);
// Push into ring buffer; flash I/O happens in bleFlashTick() from loop()
// so this callback returns immediately without blocking the SoftDevice.
for (uint16_t i = 0; i < len; i++) {
uint32_t nextHead = (g_bleBufHead + 1) % BLE_FLASH_BUF;
if (nextHead == g_bleBufTail) break; // full — drop tail (shouldn't happen at 16kHz)
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;
#endif
// Update status characteristic with bytes written
uint8_t stat[4];
stat[0] = (g_bleWriteLen >> 24) & 0xFF;
stat[1] = (g_bleWriteLen >> 16) & 0xFF;
stat[2] = (g_bleWriteLen >> 8) & 0xFF;
stat[3] = g_bleWriteLen & 0xFF;
audioStat.write(stat, 4);
audioStat.notify(stat, 4);
// Progress notifications are intentionally omitted here: sending one BLE
// notification per 180-byte packet floods the SoftDevice TX queue (~7 k
// packets for a 1.3 MB file) and causes the last notifications to be
// dropped. A single definitive notification is sent by bleFlashTick()
// when finalization completes (after CMD_UPLOAD_END drains the ring buffer
// and writes the track table).
}
// 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() {
/* 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.setName("BabyMobile");
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);
#else
// SPI flash upload: tracks are packed sequentially starting at AUDIO_START_ADDR.
// Uploading track 0 resets the allocation pointer.
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
// Uploading track 0 resets g_flashNextFree (shared with BLE upload path).
static uint32_t g_serFlashCurAddr = 0; // current write head
static uint32_t g_serFlashErasedThru = 0; // highest erased byte address (exclusive)
#endif
static void serUploadTick() {
@@ -1221,8 +1327,8 @@ static void serUploadTick() {
}
#else
// Track 0 resets flash allocation
if (g_serTrack == 0) g_serFlashNextFree = AUDIO_START_ADDR;
g_serFlashCurAddr = g_serFlashNextFree;
if (g_serTrack == 0) g_flashNextFree = AUDIO_START_ADDR;
g_serFlashCurAddr = g_flashNextFree;
g_trackStart[g_serTrack] = g_serFlashCurAddr;
// 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("/"); Serial.println(g_serBytesReceived);
#else
// Advance free pointer to next sector boundary
g_serFlashNextFree = ((g_serFlashCurAddr + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR;
// Advance shared free pointer to next sector boundary
g_flashNextFree = ((g_serFlashCurAddr + FLASH_SECTOR - 1) / FLASH_SECTOR) * FLASH_SECTOR;
g_trackLen[g_serTrack] = g_serBytesReceived;
if (g_serTrack >= g_numTracks) g_numTracks = g_serTrack + 1;
writeTrackTable();
@@ -1397,6 +1503,11 @@ void loop() {
// ---- Serial upload ----
serUploadTick();
#ifndef POC_INTERNAL_FLASH
// ---- BLE flash drain ----
bleFlashTick();
#endif
// ---- Refill audio buffers ----
if (g_playing) {
for (uint8_t b = 0; b < 2; b++) {