From 7b1dc8e11890a5ffa8a891758a3676f5b10fc6dd Mon Sep 17 00:00:00 2001 From: zyphlar Date: Sat, 4 Jul 2026 03:28:13 -0700 Subject: [PATCH] 16kHz PWM EasyDMA audio, loop mode, serial p/l/r/DELETE commands, button and end-of-track fixes --- baby_mobile_v2/baby_mobile_v2.ino | 49 +++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/baby_mobile_v2/baby_mobile_v2.ino b/baby_mobile_v2/baby_mobile_v2.ino index 5bd1e58..7195885 100644 --- a/baby_mobile_v2/baby_mobile_v2.ino +++ b/baby_mobile_v2/baby_mobile_v2.ino @@ -204,6 +204,7 @@ uint8_t g_numTracks = 0; uint32_t g_trackStart[MAX_TRACKS]; uint32_t g_trackLen[MAX_TRACKS]; uint8_t g_currentTrack = 0; +bool g_loopTracks = false; // false = play once and stop; true = auto-advance through all tracks // Audio DMA buffers — uint16_t PWM duty-cycle values for EasyDMA static uint16_t g_pwmBuf[2][AUDIO_BUF_SIZE]; @@ -348,6 +349,7 @@ void loadTrackTable() { if (!f.open(fname, FILE_O_READ)) break; g_trackLen[i] = f.size(); f.close(); + if (g_trackLen[i] == 0) break; // stop at first empty file g_numTracks = i + 1; } Serial.print("Found "); @@ -720,7 +722,7 @@ static void audioFillBuf(uint8_t b) { } g_bufReady[b] = true; // Arm stop timer on first pure-silence fill (all audio already sent to DMA) - if (toRead == 0 && g_trackDoneMs == 0) { + if (toRead == 0 && g_trackDoneMs == 0 && g_playing) { g_trackDoneMs = millis() + 100; // 100ms > 3 buffer lengths (3 × 32ms) } } @@ -871,23 +873,26 @@ void buttonsInit() { } } -// Returns 1-8, or 0 if none pressed +// Returns 1-4, or 0 if none pressed. No blocking delay — safe to call every loop(). uint8_t buttonRead() { for (uint8_t i = 0; i < NUM_BUTTONS; i++) { - // Buttons pull to ground - if (digitalRead(BTN_PINS[i]) == LOW) { - delay(20); // debounce - if (digitalRead(BTN_PINS[i]) == LOW) { - return i + 1; - } - } + if (digitalRead(BTN_PINS[i]) == LOW) return i + 1; } return 0; } +// Wait until the button is physically released. No timeout — prevents the 800ms +// cooldown from expiring while the button is still held and re-triggering. void waitButtonRelease(uint8_t btn) { - unsigned long deadline = millis() + 300; - while (buttonRead() == btn && millis() < deadline) {} + while (buttonRead() == btn) { + // Keep DMA buffers fed while waiting + if (g_playing) { + for (uint8_t b = 0; b < 2; b++) { + if (!g_bufReady[b]) audioFillBuf(b); + } + } + } + delay(20); // debounce after release } @@ -1102,6 +1107,26 @@ static void serUploadTick() { if (g_serLineBuf[0] == 'u') g_audioGain++; else if (g_audioGain > 1) g_audioGain--; Serial.print("GAIN "); Serial.println(g_audioGain); + } else if (g_serLineLen == 1 && g_serLineBuf[0] == 'p') { + if (g_playing) { audioStop(); Serial.println("STOP"); } + else if (g_numTracks > 0) { audioStart(g_currentTrack); Serial.println("PLAY"); } + else Serial.println("ERR no tracks"); + } else if (g_serLineLen == 1 && g_serLineBuf[0] == 'l') { + g_loopTracks = !g_loopTracks; + Serial.print("LOOP "); Serial.println(g_loopTracks ? "ON" : "OFF"); + } else if (g_serLineLen == 1 && g_serLineBuf[0] == 'r') { + Serial.println("REBOOT"); + delay(10); + NVIC_SystemReset(); + } else if (sscanf(g_serLineBuf, "DELETE %u", &utrk) == 1) { + char fname[16]; + pocFilename((uint8_t)utrk, fname); + if (InternalFS.remove(fname)) { + Serial.print("DELETED "); Serial.println(fname); + } else { + Serial.print("ERR no file "); Serial.println(fname); + } + loadTrackTable(); } else { Serial.print("ERR bad cmd: "); Serial.println(g_serLineBuf); @@ -1192,7 +1217,7 @@ void loop() { if (g_playing && g_trackDoneMs != 0 && millis() >= g_trackDoneMs) { g_trackDoneMs = 0; audioStop(); - if (g_numTracks > 0) { + if (g_loopTracks && g_numTracks > 0) { g_currentTrack = (g_currentTrack + 1) % g_numTracks; audioStart(g_currentTrack); }