forked from Arduino/metronom
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>
97 lines
2.3 KiB
C++
97 lines
2.3 KiB
C++
#include "SAMDTimerInterrupt.h"
|
|
#include "SAMD_ISR_Timer.h"
|
|
|
|
SAMDTimer TaskTimer(TIMER_TC3);
|
|
SAMD_ISR_Timer TasksISRs;
|
|
|
|
enum {
|
|
TEMPERATURE = 0,
|
|
HUMIDITY,
|
|
PRESSURE,
|
|
SENSORS
|
|
};
|
|
|
|
// COUNTDOWN is effectively multiplied by METRONOME_INTERVAL
|
|
const int COUNTDOWN = 600;
|
|
// INTERVALS and TIMESHARES given in [ms]
|
|
const uint TASK_HANDLER_INTERVAL = 20;
|
|
const uint METRONOME_INTERVAL = 1000;
|
|
const uint SENSOR_INTERVAL = 1000;
|
|
const uint SENSOR_TIMESHARE[SENSORS] = {90000, 20000, 10000};
|
|
// FREQUENCY in [Hz]
|
|
const uint BUZZER_FREQ = 300;
|
|
|
|
const uint BUTTON_PIN = 6;
|
|
const uint BUTTON_LED_PIN = 5;
|
|
const uint BUZZER_PIN = 0;
|
|
|
|
/* Metronome state is expressed by countdown:
|
|
-1 - IDLE
|
|
0 - BEATING
|
|
>0 - COUNTDOWN
|
|
Unless metronome is in COUNTDOWN mode, display rotates through sensor readings.
|
|
*/
|
|
volatile int countdown = -1;
|
|
// Units: temperature [C], humidity [%], pressure [P]
|
|
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();
|
|
}
|
|
|
|
void scheduleSensors() {
|
|
sensorsDue = true;
|
|
}
|
|
|
|
void setup() {
|
|
initButton();
|
|
initBME280();
|
|
initBuzzer();
|
|
initTube();
|
|
|
|
readSensors();
|
|
updateTube();
|
|
|
|
TaskTimer.attachInterruptInterval_MS(TASK_HANDLER_INTERVAL, TasksHandler);
|
|
TasksISRs.setInterval(METRONOME_INTERVAL, runMetronome);
|
|
TasksISRs.setInterval(SENSOR_INTERVAL, scheduleSensors);
|
|
tubeTimerID = TasksISRs.setInterval(SENSOR_TIMESHARE[displaySensor], rotateTube);
|
|
}
|
|
|
|
void runMetronome() {
|
|
if (countdown > 0) {
|
|
countdown -= 1;
|
|
|
|
digitalWrite(BUTTON_LED_PIN, countdown % 2 ? HIGH : LOW);
|
|
|
|
displayNeedsUpdate = true;
|
|
} else if (countdown == 0) {
|
|
TasksISRs.setTimeout(100, noBuzz);
|
|
buzz();
|
|
}
|
|
}
|
|
|
|
void loop() {
|
|
if (sensorsDue) {
|
|
sensorsDue = false;
|
|
readSensors();
|
|
}
|
|
|
|
if (displayNeedsUpdate || tubeRotated)
|
|
updateTube();
|
|
|
|
if (tubeRotated) {
|
|
TasksISRs.changeInterval(tubeTimerID, SENSOR_TIMESHARE[displaySensor]);
|
|
tubeRotated = false;
|
|
}
|
|
}
|