From 0332b728e60de52b035555cdc4d4a99e736d1db8 Mon Sep 17 00:00:00 2001 From: Mateusz Kowalczyk Date: Wed, 10 Jun 2026 01:37:18 +0000 Subject: [PATCH 1/2] Move I2C work out of interrupt context to fix display freeze The tube library calls delay(100) after every display write and the BME280 library calls delay(100) when re-initializing after a failed transfer. Inside the TC3 timer ISR the SysTick interrupt is blocked, so micros() stops advancing past ~1ms and delay() can spin forever when its start sample lands exactly at the bottom of the micros() sawtooth. All clocks derive from the same 48MHz source, so the ISR phase is locked and one specific code path (display update on a falling temperature reading) hits the fatal window reproducibly. ISRs now only raise flags; sensor reads, display updates and timer rescheduling run from loop(), where delay() works normally. Co-Authored-By: Claude Fable 5 --- button.ino | 4 ++-- metronom.ino | 32 +++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/button.ino b/button.ino index b422bda..88e8bb8 100644 --- a/button.ino +++ b/button.ino @@ -18,11 +18,11 @@ void buttonISRstate() { else if (countdown == 0) { // TODO: restart metronome timer to align countdown countdown = COUNTDOWN; - updateTube(); + displayNeedsUpdate = true; } else { countdown = -1; digitalWrite(BUTTON_LED_PIN, LOW); - updateTube(); + displayNeedsUpdate = true; // TODO: disable metronome timer } } diff --git a/metronom.ino b/metronom.ino index f6e8db9..67b9f41 100644 --- a/metronom.ino +++ b/metronom.ino @@ -37,18 +37,19 @@ volatile float sensorValue[SENSORS] = {}; volatile uint displaySensor = TEMPERATURE; volatile bool displayNeedsUpdate = false; volatile bool tubeRotated = false; +volatile bool sensorsDue = false; int tubeTimerID; +/* I2C (BME280 reads, tube writes) must never run in interrupt context: + both libraries call delay(), which inside an ISR can spin forever + because SysTick (millis/micros) is blocked. ISRs only raise flags; + loop() does the actual bus work. */ void TasksHandler(void) { TasksISRs.run(); +} - if (displayNeedsUpdate || tubeRotated) - updateTube(); - - if (tubeRotated) { - TasksISRs.changeInterval(tubeTimerID, SENSOR_TIMESHARE[displaySensor]); - tubeRotated = false; - } +void scheduleSensors() { + sensorsDue = true; } void setup() { @@ -62,7 +63,7 @@ void setup() { TaskTimer.attachInterruptInterval_MS(TASK_HANDLER_INTERVAL, TasksHandler); TasksISRs.setInterval(METRONOME_INTERVAL, runMetronome); - TasksISRs.setInterval(SENSOR_INTERVAL, readSensors); + TasksISRs.setInterval(SENSOR_INTERVAL, scheduleSensors); tubeTimerID = TasksISRs.setInterval(SENSOR_TIMESHARE[displaySensor], rotateTube); } @@ -79,4 +80,17 @@ void runMetronome() { } } -void loop() {} +void loop() { + if (sensorsDue) { + sensorsDue = false; + readSensors(); + } + + if (displayNeedsUpdate || tubeRotated) + updateTube(); + + if (tubeRotated) { + TasksISRs.changeInterval(tubeTimerID, SENSOR_TIMESHARE[displaySensor]); + tubeRotated = false; + } +} -- 2.53.0 From 6abd900a8f3e0f86d510485d21cd7ba8b335b70d Mon Sep 17 00:00:00 2001 From: Mateusz Kowalczyk Date: Wed, 10 Jun 2026 02:13:55 +0000 Subject: [PATCH 2/2] Lower display brightness to probe peak-current freeze Piotr observed the freeze correlates with digit '8' on the last (units) tube position, regardless of which sensor is shown (25.08 C, 48 %, 1018 hPa). '8' lights the most 14-seg segments of any digit, and the units digit changes most often, so the last position is both peak-current and the most-written-to. At brightness 15 (max) that peak current likely dips the supply / glitches the I2C bus, which the in-ISR delay()/endTransmission() then turns into a permanent hang. This commit drops brightness 15 -> 4 as a cheap, reversible probe. If freezes stop or get much rarer, the root cause is current/brownout (fix: decoupling cap on display VCC, or separate supply, plus an I2C bus-recovery/timeout) rather than purely the ISR timing. Co-Authored-By: Claude Fable 5 --- tube.ino | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tube.ino b/tube.ino index d39f81c..69c9228 100644 --- a/tube.ino +++ b/tube.ino @@ -6,7 +6,11 @@ char tubeText[5] = ""; void initTube() { // Wire initialized by BME280 sensor tube.setTubeType(TYPE_4, TYPE_4_DEFAULT_I2C_ADDR); - tube.setBrightness(15); + // Brightness drives LED current. The freeze correlates with '8' on the last + // multiplexed digit (most segments lit -> peak current) at full brightness 15, + // which points at a supply/I2C-bus glitch. Lowered to probe that hypothesis; + // raise back toward 15 if the display is too dim and freezes don't return. + tube.setBrightness(4); tube.setBlinkRate(BLINK_OFF); } -- 2.53.0