When last we spoke on this subject I had left you with a time-accurate but impractical timer interrupt handler.
#define F_CPU 16000000
uint8_t Timer_ms = 0;
static void initTIMER1( void )
{
// CTC mode (WGM1[3:0] = 0x04)
TCCR1A = 0;
TCCR1B = _BV(WGM12);
// CLK/8 prescaler (see datasheet for table)
TCCR1B |= _BV(CS11);
// Automatic comparison register (16Mhz/8) is a 2 MHz tick, i.e. two
// million ticks per second, or two thousand ticks per millisecond.
// Note that on AVR we get one more tick than the value we put in
// this register
OCR1A = (F_CPU/8)/1000 - 1;
// Enable timer1 match-A interrupt
TIMSK = _BV(OCIE1A);
}
// Note the change of vector here from "overflow" to "match"
ISR(TIMER1_COMPA_vect)
{
// 1 ms has passed
Timer_ms++;
// TIFR.OCF1A is automatically cleared when the ISR is called, so we
// needn't worry about the ISR being incorrectly retriggered
}
Why is this impractical though? Well let’s look a little wider, what might we want to do with a timer in a main loop? Let’s say something simple: flash an LED every quarter of a second. We want to know when the timer has changed, but we’re only updating a counter in the ISR. We also have to be aware that Timer_ms
is altered by an interrupt context, so to prevent races we shouldn’t carelessly access it in the main loop context.
Let’s look at the solution.
static uint8_t interruptTicks = 0;
ISR(TIMER1_COMPA_vect)
{
interruptTicks++;
}
int main( void )
{
uint8_t localTicks = 0;
unit16_t Timer_ms = 0;
uint16_t Timer_1s = 0;
uint8_t Alarm_ms = 0;
initTimer1();
while(1) {
// Disable interrupts as a poor-man's atomic operation
cli();
localTicks += interruptTicks;
interruptTicks = 0;
// Disabling interrupts still leaves them pending
sei();
// We could put the processor to low-power sleep here and wait
// to be woken by the timer interrupt rather than busy-waiting
// ----- Timer interrupt bottom half
// We've now got a tick count we can use without worrying about
// race conditions, localTicks is never accessed outside this
// context
if( localTicks == 0 )
continue;
// --- Timers tick upwards
Timer_ms += localTicks;
if( Timer_ms > 1000 ) {
Timer_ms -= 1000;
if( Timer_1s < 0xffff )
Timer_1s++;
}
// --- Alarms tick downward
if( Alarm_ms < localTicks ) {
Alarm_ms = 0;
// Call whatever alarm handler you want here
} else {
Alarm_ms -= localTicks;
}
// Everyone who needed to count ticks has counted them
localTicks = 0;
}
return;
}
You can see that you could add any number of timers, any number of alarms, and any cascade conditions you want between them. None of this is done in the ISR and can take up to 255 ms (an age in microcontroller land), without ever missing a single tick or creating a race condition. By using interrupts for our timer we’ve also gained the ability to go to sleep and thus use much less power than we would if we were simply polling for an overflow.
I haven’t done it here as it would only obfuscate matters, but there is nothing to stop you writing a generic alarm structure that contains both the alarm time and a pointer to the function to call on timeout. Oh what the heck…
struct sAlarmStructure
{
uint16_t AlarmTime_ms;
void (*timeoutCallback)(uint8_t);
void *opaque:
} AlarmList[MAX_ALARMS];
uint8_t registerTimer( uint16_t timeout, void (*callback)(uint8_t), void *opaque )
{
uint8_t i;
for( i = 0; i < MAX_ALARMS; i++ ) {
// Timers
if( AlarmList[i].AlarmTime_ms == 0 ) {
AlarmList[i].AlarmTime_ms = timeout;
AlarmList[i].timeoutCallback = callback;
AlarmList[i].opaque = opaque;
return i;
}
}
// No spare timer slots left
return -1;
}
int main( void )
{
uint8_t localTicks = 0;
uint8_t i;
initTimer1();
while(1) {
// Disable interrupts as a poor-man's atomic operation
cli();
localTicks += interruptTicks;
interruptTicks = 0;
// Disabling interrupts still leaves them pending
sei();
for( i = 0; i < MAX_ALARMS]; i++ ) {
if( AlarmList[i].AlarmTime_ms > localTicks ) {
AlarmList[i].AlarmTime_ms -= localTicks;
} else {
AlarmList[i].AlarmTime_ms = 0;
// The callback function can restart itself if it wants
// because we're handing it its own AlarmList index
AlarmList[i].timeoutCallback(i);
}
}
// Rest of main loop can call registerTimer() when it needs to
// defer a task
}
return;
}
So we’ve gone from not being able to rely on the exact number of counts between events to a generic, run-time configurable alarm system. We’ve got a fast ISR and no race condition.