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 <noreply@anthropic.com>
This commit is contained in:
Mateusz Kowalczyk
2026-06-10 01:37:18 +00:00
parent c2af98d588
commit 0332b728e6
2 changed files with 25 additions and 11 deletions

View File

@@ -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
}
}

View File

@@ -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;
}
}