Embedded HandbookApps
All sections › IV. FreeRTOS
STM32F103, STM32F407 · level intermediate · 12 min read
Open in the interactive handbook

29. Software timers

There are hardware timers on the chip (section 13) — from four on the F103C8 to fourteen on the F407, and all of them are needed for PWM, capture, the ADC. Yet devices constantly need to "do it in half a second", "do it every hundred milliseconds", "if nothing has arrived in two seconds — raise an alarm". Software timers give you as many of these as you need, at the cost of one task and 44 bytes each.

Terms used in the documentation

Term Meaning
software timer a timer kept by the kernel, not by hardware
timer daemon / service task the kernel task that services the timers
timer command queue the queue of commands to the timer task
callback function the function the timer calls
one-shot fires once
auto-reload restarts itself after firing
dormant inactive, not running
timer ID the timer's identifier, an arbitrary pointer
pend function call ask the daemon to run a function

29.1 How it works

A software timer is not hardware. It is an entry in a list: when to fire, what to call, whether to repeat. The list is serviced by one kernel task, called the daemon: it sleeps until the nearest deadline, wakes up, calls the required function and sleeps until the next one.

Three consequences follow from this, and they define all of its behaviour.

The callback function runs in the daemon task. Not in an interrupt, not in the task that started the timer. With everything that implies: the daemon's stack, the daemon's priority, and if the function blocks, the daemon blocks, and with it all the timers of the device stop at once.

Every action on a timer is a command through a queue. xTimerStart does not start the timer itself: it puts a command into the daemon's queue and returns. The daemon gets the CPU, reads the command and starts the timer.

The resolution is one tick. The daemon wakes on the tick, so you cannot do better than a millisecond at 1 kHz. For anything more precise — a hardware timer.

Hardware timer Software timer
number 4–14 per chip as many as you like
resolution clock cycle tick
handler runs in an interrupt in the daemon task
cost a hardware block is taken 44 bytes
good for PWM, capture, microseconds delays, periods, watchdogs

29.2 Creating and starting

static TimerHandle_t t_blink;

static void blink_cb(TimerHandle_t t)
{
    GPIOC->ODR ^= GPIO_ODR_ODR13;
}

t_blink = xTimerCreate("blink",                 /* имя для отладки      */
                       pdMS_TO_TICKS(500),      /* период в тиках       */
                       pdTRUE,                  /* автоперезапуск       */
                       NULL,                    /* идентификатор        */
                       blink_cb);               /* что вызывать         */

xTimerStart(t_blink, 0);

Two kinds, distinguished by the third argument:

Argument Kind Behaviour
pdTRUE auto-reload fires every N ticks until stopped
pdFALSE one-shot fires once and goes dormant

After creation the timer is not running. It stays dormant until a start command arrives; and a one-shot timer returns to the same dormant state after firing.

About the second argument of xTimerStart

xTimerStart(t_blink, pdMS_TO_TICKS(10));

It is easy to get this wrong: this is not a delay before starting. It is the time to wait for a slot in the command queue — how long the calling task is willing to wait if the daemon's queue is full. Usually you pass zero: a queue of ten commands rarely overflows, and if it has overflowed, that is a sign of trouble, not a reason to wait.

⚠️ Starting before the scheduler starts is allowed and common. The command goes into the queue and executes as soon as the daemon gets the CPU. But counting of the period starts at that point, not at the moment of the call.

Commands

Function What it does
xTimerStart start; if already running — restart from zero
xTimerStop stop
xTimerReset restart the count from zero
xTimerChangePeriod change the period; also starts the timer if it was stopped
xTimerDelete delete

Each has a FromISR twin with no wait time and with a wake-up flag (section 28).

⚠️ xTimerChangePeriod starts a stopped timer. You changed the period of a timer you were deliberately keeping off — and it started running. If that is not what you want, follow the period change with xTimerStop right away.


29.3 The daemon task

It is configured by three macros, and each one matters.

#define configTIMER_TASK_PRIORITY     (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH      10
#define configTIMER_TASK_STACK_DEPTH  256

The daemon's priority is the priority of all callbacks. Set it to the maximum: otherwise a timer function that is due to fire will wait for the CPU to become free, and timer accuracy disappears. The price: any long callback delays everything below it.

The queue length is how many commands can pile up while the daemon is busy. Ten is almost always enough; a failure of xTimerStart with zero wait means the queue is full.

The stack is shared by all callbacks. 256 words, that is, a kilobyte, is enough for simple actions. Formatted output in a callback will eat all of it (section 26).


29.4 The callback: what is allowed and what is not

The function runs in a task, not in an interrupt — so the ordinary kernel functions are available to it, not FromISR. But with one caveat.

⚠️ A callback must never block. No vTaskDelay, no waiting on a queue or a semaphore with a non-zero time. If the daemon blocks, all the device's timers stop: the blinking, the watchdogs, the delays. The rule is simple: in a callback the wait time is always zero.

What is usually done in a callback: toggle a pin, send a message to a queue with zero wait, give a semaphore, set a flag, change the state of a state machine. What is not done: waiting, long computations, bus transfers.

If a timer firing requires long work, the callback wakes a task, and the task does the work. The same deferred processing as with interrupts.


29.5 🔧 The ID: one handler for many timers

You can store an arbitrary pointer in a timer — the ID. That way one function serves a group of timers and tells them apart by it:

typedef struct { uint8_t channel; uint8_t state; } led_ctx_t;

static led_ctx_t leds[3];

static void led_cb(TimerHandle_t t)
{
    led_ctx_t *ctx = pvTimerGetTimerID(t);     /* чей это таймер */
    ctx->state ^= 1u;
    set_led(ctx->channel, ctx->state);
}

for (i = 0; i < 3; i++) {
    t_leds[i] = xTimerCreate("led", pdMS_TO_TICKS(100 * (i + 1)),
                             pdTRUE, &leds[i], led_cb);
}

Three timers with different periods, one function, 132 bytes for everything. The ID can also be changed on the fly with vTimerSetTimerID — handy for keeping state between firings.


29.6 🔧 A link watchdog timer

The most useful application of a one-shot timer.

The task: if nothing has arrived over the link for two seconds, consider the link lost and put the device into a safe state.

static void timeout_cb(TimerHandle_t t)
{
    enter_safe_state();                 /* связь молчала две секунды */
}

t_timeout = xTimerCreate("timeout", pdMS_TO_TICKS(2000), pdFALSE,
                         NULL, timeout_cb);
xTimerStart(t_timeout, 0);

/* в обработчике приёма */
void USART1_IRQHandler(void)
{
    BaseType_t woken = pdFALSE;

    byte = USART1->DR;
    xTimerResetFromISR(t_timeout, &woken);   /* отодвинуть срабатывание */
    portYIELD_FROM_ISR(woken);
}

Every received byte restarts the timer. As long as data keeps coming, it never fires. Once the line goes silent, it fires exactly two seconds after the last byte. Not a single line of timing logic in the protocol itself.

A link watchdog on a one-shot timer: bytes restart the count, after silence it fires in 2 s

The price is paid on every byte. Each call to xTimerResetFromISR is a command into the daemon's queue, a switch to it and back. A byte at 115200 baud lasts 6250 clock cycles of an F103 at 72 MHz; in an experiment with FreeRTOS 10.5.1 on Cortex-M3 in QEMU a "byte" arrived roughly that many instructions apart. Each one took about 760 instructions — 12% of the CPU, and that is a lower bound: an instruction costs at least one cycle. An interrupt that only records the time of the last byte cost 24 instructions. So on a fast line the timer is restarted less often: once per whole frame — from the parsing task or from the "line idle" interrupt (section 16.5) — or the interrupt only records the time, and a software timer checks it once every 100 ms.

The same technique works for turning off the display after inactivity, automatic exit from a menu, releasing a grip.


29.7 🔧 A device watchdog on a software timer

The temptation is obvious: create a timer that feeds the hardware watchdog (section 12) once every 200 ms — if the daemon runs, the system is alive. It is not. The daemon is a separate task with the highest priority, and it keeps running even when all the other tasks have hung. Such a watchdog repeats the mistake of "feeding from an interrupt" (12.7) and only protects against the death of the kernel itself.

The right way is to feed it only if all tasks have checked in since the last time. Each task sets its own bit in an event group (section 31), and the timer callback reads and clears the bits in one call: xEventGroupClearBits returns the group's value before clearing. It feeds only if all the bits are collected.

#define ALIVE_ALL 0x07u                     /* три задачи — биты 0–2 */

/* каждая задача — раз за оборот своего цикла */
void i_am_alive(unsigned task_no)
{
    xEventGroupSetBits(alive, 1u << task_no);
}

/* неправильно: демон жив всегда, даже когда все задачи зависли */
static void feed_blind(TimerHandle_t t)
{
    (void)t;
    hw_watchdog_feed();
}

/* правильно: кормить, только если с прошлого раза отметились все */
static void feed_checked(TimerHandle_t t)
{
    (void)t;
    EventBits_t was = xEventGroupClearBits(alive, ALIVE_ALL);   /* вернёт значение до сброса */
    if ((was & ALIVE_ALL) == ALIVE_ALL)
        hw_watchdog_feed();
}

The experiment runs on the real kernel with the POSIX port. Three tasks check in once every 100 ms, the timer fires once every 200 ms, and a model of the hardware watchdog resets the chip if there has been no feeding for 500 ms. At the 1000th millisecond task 1 hangs — it waits for something that will never come.

Feeding What happened
"the daemon is alive — feed" no reset in 3 s: the device with a hung task keeps running
only when all have checked in reset at the 1510th millisecond: the last feeding was at the 1000th

The order of magnitudes is mandatory: tasks check in more often than the timer fires, and the timer fires more often than the watchdog expires: 100 < 200 < 500 ms. A task that waits on a queue or an event with no time limit cannot check in. Its wait is limited with a timeout shorter than the timer period — or it is not included in the mask.

A watchdog on a software timer: feed only if all tasks have checked in


29.8 The daemon as an on-demand executor

The daemon has one more role that has nothing to do with timers: running an arbitrary function.

static void process_sample(void *arg1, uint32_t arg2)
{
    /* arg2 — отсчёт, переданный из прерывания */
}

void ADC1_2_IRQHandler(void)
{
    BaseType_t woken = pdFALSE;

    xTimerPendFunctionCallFromISR(process_sample, NULL, ADC1->DR, &woken);
    portYIELD_FROM_ISR(woken);
}

The function is built only if the configuration has #define INCLUDE_xTimerPendFunctionCall 1 (0 by default); otherwise the linker will not find it.

This is deferred interrupt processing without a task of its own: the interrupt asks the daemon to run a function, and the daemon runs it when it gets the CPU. The saving is a whole task with its stack for every such interrupt.

The limitation is obvious: all requests are executed by one task, one after another and with the daemon's priority. For rare events this is excellent; for a stream where independent handlers matter — a task of its own.


29.9 Pitfalls

The timer does not fire. It was not started: creating does not start it. Or the scheduler has not started, or configUSE_TIMERS is disabled.

All timers stopped at once. A callback blocked and stopped the daemon. Look for a wait with a non-zero time.

The timer fires late. The daemon's priority is below the task that is occupying the CPU; or another timer has a long callback.

A stopped timer started by itself. xTimerChangePeriod starts it.

xTimerStart returned failure. The command queue is full — the daemon cannot keep up. Or the daemon is blocked (see above).

Stack overflow in the daemon. printf or a large array in a callback. The stack is shared by all timers.

The second argument of xTimerStart does not delay the start. It is a wait for a slot in the queue, not a pause before starting.

You cannot get better than a millisecond. And you will not: the resolution is the tick. You need a hardware timer.

A device with a hung task does not reboot. A software timer feeds the watchdog without checking, and the daemon is always alive. Feed only when all tasks have checked in (29.7).


29.10 Summary

Next: section 30 "Synchronization and thread safety" — races, critical sections, mutexes and that very priority inversion that kept rebooting Mars Pathfinder.

Open in the interactive handbook