One-shot timer library for AVR-GCC

Basic problem, outline of the solution

I wrote this library some time ago, after repeatedly running out of hardware timers even for simple and small projects. I noticed that I often needed something like "Get back again after 200 milliseconds.". Another class of timing events is like "Watch out for this area each 250 milliseconds.".

Based on my FreeBSD background, I decided that my project(s) needed some simple timer queue management for medium to low resolution timing purposes. (Microsecond timing is explicitly not covered by this. It can be done by either one of the remaining hardware timer channels, or perhaps also by simple delay loops, see <avr/delay.h>.) The main idea for such a timer management is that one of the timers is used as a "hardware tick", triggering the timer queue manager on each tick, which will in turn walk the timer queue, and see which events to fire that time. Any triggered event will then call a callback function that has been registered when starting the timer. In case a periodic event is desired, it's then the responsibility of the callback function to re-trigger the event. This is a simplified event management as can be found on any modern operating system, which includes those tiny real-time OSes for the AVR as well. The timer library discussed here is merely intended for projects where a full RTOS is not being considered.

Note that the hardware timer used to trigger the timer queue management not necessarily needs to be dedicated to the queue management. In particular, if the application already requires a PWM channel anyway, the very same timer could perhaps be used for this timer library (using the overflow interrupt).

Implementation scenarios

There are two basic options to run such a timer queue:

  1. The respective timer interrupt service calls the queue manager directly.
  2. The timer interrupt service just sets a flag only, and the application's main loop handles calling the queue manager as soon as it notices the flag.

Option #2 above is the preferable way, as it is less prone to interrupt congestion in case some of the callback functions consumes too much time. If one timer tick gets missed, it will simply ignored, but the remainder of the application will at least continue to work. The basic issue with this approach is that the main loop must run quick enough to ensure that the interrupt flag bit is (normally) recognized quick enough.

Option #1, while it seems to be the more obvious solution at first, is much more problematic. It contradicts the common principle to keep interrupt service routines as short as possible. If at all, the timer queue manager should at least be run with interrupts already re-enabled by that time, so other interrupts get a chance to fire while a callback function is being processed. Obviously, this risks endless nesting of timer interrupt handling in case one of the callback functions hogs CPU time.

My implementation

This implementation now supports both of the options above. If option #1 (called directly from the timer ISR) needs to be chosen, the library ought to be compiled with the macro TMR_INTPROTECT being set (e.g. using the compiler's command-line option -DTMR_INTPROTECT). This will compile the library in a way where interrupts are being disabled when entering critical regions. For option #2 above, this measure is not necessary (saving code and execution time as well) as the queue management would never happen from inside an interrupt routine anyway.

The interface to all functions described below, and the type definitions can be found in the header file timer.h. The implementation itself is performed in timer.c which needs to be linked to the application.

Defined data types

Defined functions

Examples

Option #2: timer ISR just sets a flag bit

This is a small part from a real project that is using this library. It contains examples for the preprocessor calculations, as well as for re-triggering timer events and argument passing.

volatile struct
{
  /* hardware interrupts */
  uint8_t tmr2_int: 1;
  /* ... */
} intbits;

#if SYSCLK == 14745600L
/*
 * 14.7456 MHz clk / (TMCON1 * TMCON2) = 200 Hz soft interrupt rate
 */
#define TMCON1 144
#define TMCON2 512
#define TICKRATE 200L

SIGNAL(SIG_OUTPUT_COMPARE2)
{
#if TMCON2 >= 256
  static uint16_t ticks;
#else
  static uint8_t ticks;
#endif

  if (++ticks == TMCON2) {
    intbits.tmr2_int = 1;
    ticks = 0;
  }
}

/* ... */
void
calibrate(union timeoutarg arg)
{
  /* ... */
  if (arg.i == 0) {
    /* ... */
    arg.i = 1;
  } /* else ... */

  timeout(TICKRATE / 10, calibrate, arg);
}

void
startcalib(union timeoutarg arg)
{
  void **argp = arg.p;

  /*
   * If a timeout handle has been passed, clear it to indicate the
   * timeout has hit.
   */
  if (argp)
    *argp = 0;

  /* ... */
  timeout(TICKRATE / 100, calibrate, arg);
}

int
main(void)
{
  union timeoutarg u;
  /* ... */

  /* get things going... */
  u.i = 0;
  timeout(TICKRATE, startcalib, u);

  for (;;)
    {
      if (intbits.tmr2_int)
        {
          intbits.tmr2_int = 0;
          timertick();
        }

      /* ... */
    }

  /* NOTREACHED */
  return 0;
}

Basic outline for option #1

This only outlines the main difference against the previous example: timertick() is called directly from within the ISR, after re-enabling interrupts. Nothing is required in the main loop.

SIGNAL(SIG_OUTPUT_COMPARE2)
{
#if TMCON2 >= 256
  static uint16_t ticks;
#else
  static uint8_t ticks;
#endif

  if (++ticks == TMCON2) {
    ticks = 0;
    sei();
    timertick();
  }
}

Comments by readers of this page

sent the following remark:

One change that I would make is rather than a bit flag in the IRQ, is to use a counter. I've found this works better over the years.

The IRQ counts up. The main loop then does a

while (--counter > 0)

operation. This prevents ticks from being lost. Of course you need proper protections for decrementing the counter outside of the IRQ etc.


Last modified: Thu Sep 30 22:01:50 MET DST 2004