5.2 Timer ISR

As mentioned in a previous section, you can define a shell ISR for a timer as follows:

SIGNAL(SIG_OVERFLOW1)
{
}

A very common thing to do is to keep track of a tick counter. This way, the rest of the program can get a sense of time. The first cut is simply to use a global variable as follows:

unsigned long tick_counter = 0;
SIGNAL(SIG_OVERFLOW1)
{
  ++tick_counter;
}

Then the rest of the program can track time as follows:

{
  unsigned long first_tick;

  while (PINA & (1 << PB1)); // wait for PB1 be pressed
  first_tick = tick_counter; // mark the time
  while (!(PINA & (1 << PB1)); // wait for PB1 be released
  duration = tick_counter - first_tick;
}

This code attempts to time how long a push button has been kept pushed. Even though tick_counter can overflow, the unsigned subtraction actually is insensitive to overflows. As long as the duration to measure does not exceed $2^{32}-1$, duration still reflects the number of ticks that has passed.

This code does have a serious problem. As we read tick_counter (which is a 32-bit integer), the timer interrupt can occur. This is very bad. We can be halfway loading the 32-bit integer into registers when this happens. The ISR will complete without any problems and update tick_counter. However, as it returns, we resume to load the other part of the now-updated tick_counter into registers. This means half of the bits of the registers comes from the ``original'' tick_counter, while the other portion comes from the ``updated'' tick_counter. What if tick_counter wraps around to 0 in the ISR?

Besides, gcc is a smart compiler. It is likely to optimize the program and just assume that ``nothing is going to happen to tick_counter, so it may just decide that duration always get 0. In other words, by default, gcc assumes a global variable is not modified by ISRs.

To fix this code, we need to use the following definition for tick_counter:

volatile unsigned long tick_counter;

The reserved word volatile tells the compiler that ``anything can happen to this variable at any time''. However, we still have not fixed the first problem (getting interrupted when a multi-byte variable is getting loaded or stored).

The solution to the first problem is a little longer. We'll define a subroutine first:

volatile unsigned long get_tick_counter(void)
{
  unsigned char oldSREG = SREG;
  unsigned long tmp;

  _CLI(); // disable interrupt
  tmp = tick_counter;
  if (SREG & (1 << SREG_I)) _SEI();
  return tmp;
}

Again, volatile tells the compiler that this subroutine has the potential to return something different everytime it is called. This subroutine has the wrapper to disable interrupt first, then restore the interrupt flag at the end. The key of this subroutine is that we capture the tick_counter value after interrupt is disabled. This guarantees the operation is ``atomic''. The captured value in local variable tmp is then returned at the end.

Having defined get_tick_counter, we just need to change all references to tick_counter to get_tick_counter() in the program.

Copyright © 2006-02-15 by Tak Auyeung