Reliable timings using Threads or Virtual Timers

One common task is to have threads do something at regular, scheduled, intervals.

The problem

An obvious solution would be to write something like this:

msg_t my_thread(void *param) {
 
  while (true) {
    do_something();
    chThdSleepMilliseconds(1000); /* Fixed interval.*/
  }
}

This example works well assuming that the do_something() execution time is well below the system tick period and that my_thread() is not preempted by other threads that could insert long intervals. If the above conditions are not satisfied you may have do_something() executed at irregular intervals, for example:

T0…T0+1000…T0+2002…T0+3002…T0+4005…

Also note that the error accumulates over time and this kind of behavior can lead to anomalies really hard to debug.

A better solution

It is possible to rewrite the above code using absolute deadlines rather than fixed intervals:

msg_t my_thread(void *param) {
 
  systime_t time = chVTGetSystemTimeX(); // T0
  while (true) {
    time += TIME_MS2I(1000);             // Next deadline
    do_something();
    chThdSleepUntil(time);
  }
}

Using this code do_something() will always be executed at an absolute deadline time and the error will not accumulate over time regardless of the execution time and delays inserted by other threads. Note that this solution requires that the do_something() execution time must not exceed the deadline or the thread would stay sleeping into chThdSleepUntil() until the (very far) specified instant in the future.

A safer solution

If it is not possible to guarantee that do_something() execution time cannot always stay within the deadline it is possible to write code more tolerant to occasional:

msg_t my_thread(void *param) {
 
  systime_t prev = chVTGetSystemTime(); // Current system time.
  while (true) {
    do_something();
    prev = chThdSleepUntilWindowed(prev, chTimeAddX(prev, TIME_MS2I(1000)));
  }
}

This implementation ensures that, in case a deadline is missed, chThdSleepUntilWindowed() returns immediately, the error does not accumulate and it is eventually recovered on the next cycle. The “windowed” function only goes to sleep if the current system time is within the window: prev .. prev + 1000mS.

A different solution

Another way to perform activities at regular intervals is the use of a virtual timer. Virtual timers are able to generate callbacks at scheduled intervals, both one-shot and continuous modes are available, in this case we will use a continuous timer.

virtual_timer_t vt;
 
void do_something_callback(virtual_timer_t vtp, void *p) {
 
  (void)vtp;
  (void)p;
 
  /* Periodic code here.*/
  do_something();
}
 
int main(int argc, char **argv) {
 
  /* Starts the timer.*/
  chVTSetContinuous(&vt, TIME_MS2I(1000), do_something_callback, NULL);
  ...
}

Note that the callback code is executed from within the I-Locked state so you can only execute I-Class APIs from there. This solution has the advantage to not require a dedicated thread and thus uses much less RAM but the periodic code must have a very short execution time or it would degrade the overall system response time, the reason is that virtual timers callbacks are invoked from an interrupt handler.