FreeRTOS and the Pi Pico: timers

I made use of FreeRTOS’ timer functionality in the most recent post in this series, but I didn’t go into detail because the post was focused on other features. It’s time to address that deficiency. Today I’m talking about timers.

Timers come in two types: one-shots and repeaters. Both are created the same way, but while one-shot timers fire only once, repeaters do so periodically until manually halted. Both types call a function when they fire, allowing you to queue up a sequence of events, each of which will take place after a given interval. Examples of the use of timers include causing code to be execute with a fixed periodicity, such as a  connection state watchdog, or implementing a timeout on a request for a resource served over the Internet.

You establish new timers with FreeRTOS’ xTimerCreate() function. This takes a name for the timer, albeit one used for debugging more than anything else, followed by the timer’s duration in system ticks, whether it’s a one-shot or repeater, an ID code, and the name of the call-on-fire function.

As an example, here’s the code from my RP2040 FreeRTOS template repo:

led_on_timer = xTimerCreate("LED_ON_TIMER",
                            psMS_TO_TICKS(LED_FLASH_PERIOD_MS),
                            pdTRUE,
                            (void*)0x00,
                            timer_fired_callback);

The second parameter is the output of a FreeRTOS macro that converts a millisecond value into FreeRTOS system ticks. This is a very useful call if you’re like me and you naturally think of timer durations in terms of standard time units: it macro saves you the job of converting manually.

The third parameter is FreeRTOS’ own Boolean type: pass pdTRUE to indicate the timer should repeat, or pdFALSE for a one-shot timer.

Parameter four is the timer’s ID. It’s a constant that you can use to identify which timer fired, typically within the function specified in parameter five. You sometimes want multiple timers to call the same code, or code that is substantially the same but has some timer-specific elements. The ID is one way you can use to determine which timer-specific code to execute.

xTimerCreate() returns a value of type TimerHandle_t, which you use to refer to the timer again. Usually that’s to activate it, which you do with xTimerStart(), which takes a timer handle and the number of ticks to allow FreeRTOS’ timer manager to add the timer to the queue.

Of course, there’s a chance FreeRTOS couldn’t create the required timer, so always check the handle isn’t null before starting the timer:

if (led_on_timer != NULL) xTimerStart(led_on_timer, 0);

As you’ll have guessed from its name, this timer periodically turns an LED on, in this case the Raspberry Pi Pico’s integrated LED on pin 25. How do we turn it off? If you want a straightforward even on-off periodicity, you just toggle the LED’s state in the callback function. I thought it would be more fun to have the LED blink every second. In other words to flash every 1000ms, but to stay illuminated for just 100ms.

This the code achieves with a second timer: a one-shot that is created and activated right after turning the LED on. When it fires, the same callback is triggered. The code checks which timer fired and, if it’s the second one, the LED is turned off. I could have used FreeRTOS’ pvGetTimerId() to read the fired timer’s ID and branch on that value, but instead I used a feature of FreeRTOS’ timer callback prototype: it is passed the fired timer’s handle as a parameter. So I just compare handles:

void timer_fired_callback(TimerHandle_t timer) {
  if (timer == led_on_timer) {
    // The LED ON timer fired so turn the LED on briefly
    led_on();

    // Create and start a one-shot timer to turn the LED off
    TimeHandle_t led_off_timer = xTimerCreate("LED_OFF_TIMER",
                                              pdMS_TO_TICKS(LED_OFF_PERIOD),
                                              pdFALSE,
                                              (void*)0xFF,
                                              timer_fired_callback);

    // Start the one-shot timer
    if (led_off_timer != NULL) xTimerStart(led_off_timer, 0);
  } else {
    // The LED OFF timer fired to turn off the LED
    led_off();
  }
}

Naturally, this requires you to store the primary timer’s handle in a global variable, led_on_timer. Using the ID of the timer avoids that if you have a downer on globals.

To see how IDs can be used, I added the following logging lines to the start of the callback:

uint32_t timer_id = (uint32_t)pvTimerGetTimerID(timer);
std::stringstream log_stream;
log_stream << "Timer fired. ID: " << timer_id << ", LED: " << (timer_id == 0 ? "on" : "off");
log_debug(log_stream.str());

Note If you’re using C rather than C++, you can achieve the same result with sprintf().

Adding this code exposed an interesting gotcha. Initially, it caused the code to crash. The reason: too little memory assigned to FreeRTOS’ timer task stack. My default FreeRTOSConfig.h file set the standard FreeRTOS constant configTIMER_TASK_STACK_DEPTH to 128 words. This was sufficient for all my existing FreeRTOS demo code, including code that uses timers. However, timer callbacks are executed in the context of FreeRTOS’ timer manager, and adding the string interpolation code pushed its call stack requirements past the limit. The fix: up configTIMER_TASK_STACK_DEPTH to 256 words.

Timer Control Calls

The demo code only starts timers, but FreeRTOS allows you to stop them too, typically if another event means you no longer need to call the timer’s callback function: for example, the data has arrived, so the reception timeout timer can be deactivated before it fires. To do so use xTimerStop() and pass in the timer’s handle.

xTimerReset() sets the timer’s counter back to zero. Active timers are reset; inactive timers are started afresh.

xTimerChangePeriod() applies a new duration and starts the timer afresh.

All of the timer state calls are indirect: they go to FreeRTOS’ timer manager rather than the timers themselves. As such they may fail: the scheduler may not have the resources to restart the specified timer, or the queue is full. All these calls return pdFAIL or pdPASS on, respectively, failure or success. This is even true of xTimerDelete(), called to remove a timer altogether. In addition to a timer handle, you specify a tick timeout for the action to be performed.

xTimerIsTimerActive(), meanwhile, lets your code determine the state of a given timer. Timers are inactive until started. One-shots become inactive when they fire; repeaters remain active. The only way to make a repeater inactive is to stop it manually. This call returns a FreeRTOS Boolean.

xTimerGetExpiryTime() will tell you how many ticks remain before the specified timer fires. xTimerGetPeriod() tells you the programmed duration if you need it — perhaps the timer’s period was calculated rather than set as a constant. 

Incidentally, make sure you call the ...FromISR() versions of the timer control functions if you need to start, stop, reset or delete timers from within interrupt service routines.

More on FreeRTOS and the RP2040