diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c377632..91ea738 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,8 @@ { "permissions": { "allow": [ - "Bash(find /c/Users/Will/apps/mobile -type f -name *)" + "Bash(find /c/Users/Will/apps/mobile -type f -name *)", + "WebSearch" ] } } diff --git a/baby_mobile_v2/baby_mobile_v2.ino b/baby_mobile_v2/baby_mobile_v2.ino index 949ce9a..5bd1e58 100644 --- a/baby_mobile_v2/baby_mobile_v2.ino +++ b/baby_mobile_v2/baby_mobile_v2.ino @@ -207,14 +207,15 @@ uint8_t g_currentTrack = 0; // Audio DMA buffers — uint16_t PWM duty-cycle values for EasyDMA static uint16_t g_pwmBuf[2][AUDIO_BUF_SIZE]; -volatile bool g_bufReady[2] = {false, false}; +volatile bool g_bufReady[2] = {false, false}; +uint32_t g_trackDoneMs = 0; // nonzero = millis() deadline after which we stop uint32_t g_nextReadAddr = 0; uint32_t g_playEnd = 0; // Timers unsigned long g_lastActivity = 0; #define AUTO_OFF_MS (30UL * 60UL * 1000UL) // 30 min auto-shutoff -#define IDLE_SLEEP_MS (60UL * 1000UL) // 1 min idle → sleep +#define IDLE_SLEEP_MS (10UL * 60UL * 1000UL) // 10 min idle → sleep // USB connected flag volatile bool g_usbConnected = false; @@ -693,8 +694,8 @@ void setupBLE() { // Software gain: 1=unity, 2=2x, etc. Adjustable via 'u'/'d' serial. static uint8_t g_audioGain = 1; -// Read PCM from current source into g_pwmBuf[b], applying gain and scaling. -// Pads the tail with silence if fewer than AUDIO_BUF_SIZE bytes remain. +// Read PCM into g_pwmBuf[b], pad tail with silence. +// On the first pure-silence fill (track exhausted), arms the stop timer. static void audioFillBuf(uint8_t b) { uint8_t pcm[AUDIO_BUF_SIZE]; uint32_t toRead = 0; @@ -718,24 +719,23 @@ static void audioFillBuf(uint8_t b) { g_pwmBuf[b][i] = PWM_SILENCE; } g_bufReady[b] = true; + // Arm stop timer on first pure-silence fill (all audio already sent to DMA) + if (toRead == 0 && g_trackDoneMs == 0) { + g_trackDoneMs = millis() + 100; // 100ms > 3 buffer lengths (3 × 32ms) + } } // PWM0 ISR — fires every 32 ms (512 samples at 16 kHz). // Just signals which buffer needs refilling; loop() does the actual I/O. +// ISR just chains buffers — end-of-track is handled entirely in loop() extern "C" void PWM0_IRQHandler() { if (NRF_PWM0->EVENTS_SEQEND[0]) { NRF_PWM0->EVENTS_SEQEND[0] = 0; - if (g_playing) { - g_bufReady[0] = false; - NRF_PWM0->TASKS_SEQSTART[1] = 1; // start buf 1 immediately - } + if (g_playing) { g_bufReady[0] = false; NRF_PWM0->TASKS_SEQSTART[1] = 1; } } if (NRF_PWM0->EVENTS_SEQEND[1]) { NRF_PWM0->EVENTS_SEQEND[1] = 0; - if (g_playing) { - g_bufReady[1] = false; - NRF_PWM0->TASKS_SEQSTART[0] = 1; // start buf 0 immediately - } + if (g_playing) { g_bufReady[1] = false; NRF_PWM0->TASKS_SEQSTART[0] = 1; } } } @@ -801,6 +801,10 @@ void audioStart(uint8_t trackNum) { g_playEnd = g_nextReadAddr + g_trackLen[trackNum]; #endif + g_trackDoneMs = 0; + g_bufReady[0] = false; + g_bufReady[1] = false; + // Stop DMA cleanly, fill both buffers, restart NRF_PWM0->TASKS_STOP = 1; uint32_t t = millis(); @@ -823,6 +827,7 @@ void audioStart(uint8_t trackNum) { void audioStop() { g_playing = false; + g_trackDoneMs = 0; // cancel any pending auto-advance NRF_PWM0->TASKS_STOP = 1; #ifdef POC_INTERNAL_FLASH @@ -870,9 +875,9 @@ void buttonsInit() { uint8_t buttonRead() { for (uint8_t i = 0; i < NUM_BUTTONS; i++) { // Buttons pull to ground - if (analogRead(BTN_PINS[i]) < 128) { + if (digitalRead(BTN_PINS[i]) == LOW) { delay(20); // debounce - if (analogRead(BTN_PINS[i]) < 128) { + if (digitalRead(BTN_PINS[i]) == LOW) { return i + 1; } } @@ -894,7 +899,7 @@ void enterDeepSleep() { // Don't sleep if any button is currently LOW — would wake instantly. // Also catches floating pins that the internal pull-up isn't winning against. for (uint8_t i = 0; i < NUM_BUTTONS; i++) { - if (analogRead(BTN_PINS[i]) < 128) { + if (digitalRead(BTN_PINS[i]) == LOW) { g_lastActivity = millis(); // postpone return; } @@ -998,23 +1003,6 @@ void setup() { // Audio audioInit(); -#ifdef POC_INTERNAL_FLASH - if (g_numTracks > 0) { - Serial.println("Diag: playing track0 for 3s..."); - audioStart(0); - uint32_t diagEnd = millis() + 3000; - while (millis() < diagEnd && g_playing) { - for (uint8_t b = 0; b < 2; b++) { - if (!g_bufReady[b] && g_nextReadAddr < g_playEnd) { - audioFillBuf(b); - } - } - } - audioStop(); - Serial.println("Diag done"); - } -#endif - // USB Mass Storage #ifndef POC_INTERNAL_FLASH usb_msc.setID("BabyMobile", "Audio Drive", "2.0"); @@ -1191,21 +1179,22 @@ void loop() { serUploadTick(); #endif - // ---- Refill audio buffers (ISR sets g_bufReady[b]=false when buffer done) ---- + // ---- Refill audio buffers ---- if (g_playing) { for (uint8_t b = 0; b < 2; b++) { - if (!g_bufReady[b] && g_nextReadAddr < g_playEnd) { - audioFillBuf(b); - } + if (!g_bufReady[b]) audioFillBuf(b); } + } - // Track finished — both buffers drained and no data left - if (!g_bufReady[0] && !g_bufReady[1] && g_nextReadAddr >= g_playEnd) { - audioStop(); - if (g_numTracks > 0) { - g_currentTrack = (g_currentTrack + 1) % g_numTracks; - audioStart(g_currentTrack); - } + // ---- End of track: timer-based stop ---- + // g_trackDoneMs is armed 100ms after first silence fill. + // By then both DMA buffers have definitely cycled through silence. + if (g_playing && g_trackDoneMs != 0 && millis() >= g_trackDoneMs) { + g_trackDoneMs = 0; + audioStop(); + if (g_numTracks > 0) { + g_currentTrack = (g_currentTrack + 1) % g_numTracks; + audioStart(g_currentTrack); } } @@ -1217,18 +1206,20 @@ void loop() { btnLastMs[btn] = millis(); Serial.print("BTN"); Serial.println(btn); g_lastActivity = millis(); -/* + switch (btn) { case 1: // Play / Pause case 2: case 3: + case 4: + case 5: if (g_playing) { audioStop(); } else if (g_numTracks > 0) { audioStart(g_currentTrack); } break; - +/* case 4: // Next track if (g_numTracks > 0) { g_currentTrack = (g_currentTrack + 1) % g_numTracks; @@ -1242,7 +1233,7 @@ void loop() { if (g_playing) { audioStop(); audioStart(g_currentTrack); } } break; - +*/ case 6: // Motor toggle if (g_motorOn) motorStop(); else motorStart(g_motorSpeed); break; @@ -1266,7 +1257,7 @@ void loop() { audioStop(); motorStop(); break; - }*/ + } waitButtonRelease(btn); }