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).
⚠️
xTimerChangePeriodstarts 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 withxTimerStopright 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.
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.
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
- A software timer is an entry in a list serviced by one daemon task; it costs 44 bytes, and their number is unlimited.
- The callback runs in the daemon: not in an interrupt and not in the task that started the timer.
- All actions are commands through a queue; the second argument of
xTimerStartis the wait for a slot in the queue, not a start delay. - Auto-reload repeats, one-shot fires once and goes dormant; a newly created timer is not running.
- A callback must never block: if the daemon blocks, all timers stop.
- The daemon's priority is the maximum, its stack is shared by all callbacks.
xTimerChangePeriodstarts a stopped timer.- The ID lets one function serve a group of timers.
- A one-shot timer restarted on each event is a ready-made link watchdog; restarting it on every byte at 115200 baud eats 12% of an F103's CPU, and on a fast line it is restarted per frame.
- A watchdog fed by a timer is useful only with task check-ins: the daemon keeps running even when tasks have hung (29.7).
xTimerPendFunctionCallFromISRgives deferred processing without a task of its own, but all requests go one after another through the daemon.
Next: section 30 "Synchronization and thread safety" — races, critical sections, mutexes and that very priority inversion that kept rebooting Mars Pathfinder.