How to execute code regularly with timer on Raspberry Pi Pico?

In MCU projects, we often need to execute a piece of code regularly. For example, reading a sensor every few milliseconds or toggling an LED every second. Naively, we will use delay to achieve this effect. When there are multiple things going on the project with different periods, it becomes hard to manage with delay. Furthermore, delay blocks the CPU and we cannot perform other tasks during the delay. This is where timer comes in handy. We can achieve precise timing without blocking the CPU. In this blog, I will go through the basics of using pico_time API on the Raspberry Pi Pico to execute code regularly.

Why timer?

Let's assume we are building a vending machine. We need to measure the refrigerator temperature every 20 seconds, and we also need to connect to the server to perform checks every 30 seconds. Naively, we might implement a 10 second delay and count the number of delays. If it is a multiple of 2, measure the temperature. It it is a multiple of 3, connect to the server.

int count = 0;
while (1) {
    sleep_ms(10000);    // equivalent to delay() in Arduino
    count++;

    if (count % 2 == 0) {
        measure_temperature();
    }

    if (count % 3 == 0) {
        connect_to_server();
    }
}

This naive implementation poses a few problems.

  1. Delay blocks the CPU. Other tasks cannot be performed while the CPU sits there doing nothing.
  2. The interval between execution will be slightly longer than the delay. Suppose the connect_to_server() function takes 2 seconds to run. Then, the loop will actually take 12 seconds to run and the interval between two measurements of temperature will be 22 seconds.
  3. The management of the loop becomes difficult. Suppose we want to perform another task every 27 seconds. Then, we need to modify the count for every other task and the delay length.

To solve these problems, we typically use the hardware timer to generate interrupts and execute code after a set period. On the Raspberry Pi Pico, we can utilise the pico_time API in the Raspberry Pi pico-sdk. This API manages the hardware timer and we can set it to trigger with multiple different periods.

The pico_time API on Raspberry Pi MCUs.

On MCUs, there is a circuit called timer. It is a counter that increments every clock cycle. We can set a target value at which it will reset and generate an interrupt. Then, we can trigger the execution of some code with the interrupt service routine.

On Raspberry Pi MCUs, there is a special counter that increments every one microsecond called the system timer. It is a 64-bit counter which won't overflow until thousands of years later. The pico_time API is a high level API that manages this hardware on the MCUs. In this blog, we are going to explore how to set a repeating timer with this API.

Raspberry Pi MCUs does things a bit differently to typical MCUs. Instead of setting multiple hardware timers to generate interrupts at different rates, it triggers all interrupts from this system timer and it doesn't reset. The pico_time API manages this timer and set the target at which it triggers on the go.

How to execute code regularly?

To make sure you can run the example on your Pico without any additional hardware, we will toggle an LED every 2 seconds and print some text over USB every 3 seconds instead. First, we need to include pico/stdlib.h for the pico_time API.

#include "pico/stdlib.h"

Next, we need to write a callback function, which will be called when the timer interrupt triggers. We are going to use two timers, so we need two callback functions.

We shouldn't put blocking code such as print statements in the callback function. Therefore, we need to set a flag and print from the main function. This is because blocking an ISR may lead to the processor missing other ISRs.

volatile bool print_flag = false;

bool blink_timer_callback(__unused struct repeating_timer *t) {
    gpio_xor_mask(1 << LED_PIN);
    return true;
}

bool print_timer_callback(__unused struct repeating_timer *t) {
    print_flag = true;
    return true;
}

In the main() function, we need to first setup the two timers. We need to set the interval and the callback function. We can also pass arguments into the callback function optionally, but we are not doing that in this example.

struct repeating_timer blink_timer;
struct repeating_timer print_timer;
add_repeating_timer_ms(-2000, blink_timer_callback, NULL, &blink_timer);
add_repeating_timer_ms(-3000, print_timer_callback, NULL, &print_timer);

In the code snippet above, you can find that the interval is set to a negative value. For the pico_time API, positive and negative value of interval means two different modes of timing. If the interval is positive, then it means the time between the last return and this call of the callback function. If the interval is negative, then it means the time between the last call and this call of the callback function. It won't have much difference when the interval is long compared to the time that it takes for the function to execute. However, if the desired interval is a few milliseconds, then the difference will be noticeable.

We still need to handle the printing in the main loop. To print after the timer triggered, we need to check the value of the flag variable. If it is true, print and reset the flag.

while(1) {
    if(print_flag) {
        puts("Printing something every 3 seconds.");
        print_flag = false;
    }
}

With these settings, your Pico should be able to toggle an LED every 2 seconds and print something over USB every 3 seconds. You can download the source code of this example here.

References