fix BLE upload: use g_bleFlashStart for correct track address and byte count on finalize
This commit is contained in:
@@ -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++) {
|
||||
|
||||
Reference in New Issue
Block a user