fix(eink): drive ringtone note advance from TIMER1 IRQ, not loop() poll

On nRF52 the buzzer advanced notes by polling millis() >= _note_end_ms in
loop(). A blocking display refresh (e-ink endFrame) starves loop(), so a
note boundary that falls inside a refresh is serviced late — the note
plays long, or the next is skipped. The keypress-time 300 ms render delay
only masked the first note.

Advance notes from a hardware TIMER1 compare interrupt instead, scheduled
for each note's exact duration, so timing is independent of render cadence.
TIMER0 is the SoftDevice's; TIMER1 is free (tone() uses PWM2, nrfx TIMER1
driver is disabled). loop() becomes a no-op on nRF52; the UITask keypress
render-delay workaround is removed.

_disarmNoteTimer() clears the latched NVIC pending IRQ (not just the event)
on stop()/_nrfBegin(), so a note-advance latched just before a stop can't
fire spuriously — which would skip a new melody's first note or blip after
an explicit stop. Event read-backs flush the write buffer per the nRF52
event anomaly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-28 11:37:48 +02:00
parent 64a48f2aea
commit 1387cc9507
3 changed files with 79 additions and 16 deletions

View File

@@ -1995,18 +1995,10 @@ void UITask::loop() {
if (!_locked && curr) {
curr->handleInput(c);
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
#ifdef PIN_BUZZER
if (buzzer.isPlaying()) {
// Keep the next render at least 300 ms away so the blocking e-ink endFrame()
// doesn't extend the current note. 300 ms covers the slowest note at 120 BPM (1/4).
unsigned long deadline = millis() + 300;
if (_next_refresh < deadline) _next_refresh = deadline;
} else {
_next_refresh = 100; // trigger refresh immediately
}
#else
_next_refresh = 100; // trigger refresh
#endif
// Note timing no longer depends on render cadence (TIMER1 IRQ advances
// notes directly — see buzzer.cpp), so a redraw right after a keypress
// can't clip a note; no need to hold it back while buzzer.isPlaying().
_next_refresh = 100; // trigger refresh immediately
} else if (_locked) {
// Locked: eat all keys — wake window is set only when display first turns on
_next_refresh = 0;