Using a timer/clock interrupt, one that occurs at a regular rate, can be useful in managing tasks, providing a pseudo-operating system with essentially no overhead. The code for each task is laid out one after another in the ISR. For tasks that we want to execute at regular intervals we arrange the task like this, where unsigned currentTime is a counter that is incremented at the top of the ISR:
static unsigned lastTimeForTask = 0;
if (currentTime-lastTimeForTask >= TASK_RUN_INTERVAL) {
lastTimeForTask = currentTime;
DO THE ACTION FOR THE TASK HERE
}
We can also have the task run if triggered by some external condition:
if (checkForTheCondition() == TRUE) {
DO THE ACTION FOR THE TASK HERE
}
Or only check for the action on occasion, which is nice if the check is time consuming and the condition occurs infrequently:
static unsigned lastTimeForCheck = 0;
if (currentTime-lastTimeForCheck >= TASK_CHECK_INTERVAL) {
lastTimeForCheck = currentTime;
if (checkForTheCondition() == TRUE) {
DO THE ACTION FOR THE TASK HERE
}
}
But things become interesting if the action might take a long time. We really don’t want to block execution of other tasks. If the interrupt rate is 1ms and the action takes 10ms, this could upset tasks that need the 1ms resolution. So we allow the interrupt routine to be reentrant, but block the task from running if it hasn’t completed. This is done with a locked code region:
static volatile bool lock = false; // NOTE: Must be volatile!
if (!lock) { // Execute only if not locked
if (checkForTheCondition() == TRUE) { // Or another test shown above
lock = true;
sei(); // Re-enable interrupts
DO THE LENGTHY ACTION FOR THE TASK HERE
cli(); // Disable interrupts again
lock = false;
}
}
The tasks in the ISR should be organized with the quickest ones first to minimize latency for the quick tasks. Also, for the ATmega4809 based Arduinos the timer interrupt flag should be cleared at the very top of the ISR.
In the next post I’ll give examples where using locks made running without an OS possible.