A while back I was tasked with debugging a projtect that used FreeRTOS and the previous developer had used the software timers ALOT. At first glance the system seemd fine and it took me a while to reproduce the aledged bugs that but when i scaled up the workload for the system I reproduced it. In essance; it boiled down to waiting on “things” (queues, semaphores etc.) in timer callback functions that were set by other callback functions. Obviously that caused a lockup. There were no RAM shortage of any kind, so I just reworked the project into separate tasks.
I was however tempted to try another approach, and thats where this thread comes in.
It would have worked if I had had two daemon timer tasks and the ability to choose which daemon each timer should be run on.
Just food for thought: What if FreeeRTOS had support for multiple timer task queues.
Hmm … I can’t see the fundamental benefit of having multiple timer tasks.
The root cause problem you identified was the wrong usage of timers i.e. blocking calls in timer callbacks. This breaks all multiplexed timer implementations except having multiple but limited independent timer instances.
So really solving the original problem would imply a (timer) task per timer which is an overkill for all applications following the recommendations regarding timers resp. using them correctly.
To quote the documentation for timers at https://forums.freertos.org/t/multiple-daemon-timer-tasks/10318
Important information on writing timer callback functions
Timer callback functions execute in the context of the timer service task. It is therefore essential that timer callback functions never attempt to block. For example, a timer callback function must not call vTaskDelay(), vTaskDelayUntil(), or specify a non zero block time when accessing a queue or a semaphore.
At one time (I’m not sure if it is still there) there were conditions where a timer callback blocking could trigger an assert. Basically, the previous developer broke the rule it caused the expected problems.
Generally, anything that needs to wait for something should be a task (or a piece of a task).
The main benefit would have been to be able to have different priorities for the callbacks. But as richard-damon says by quouting the documentation: It is a bad idea to block or delay the timer daemon task.
It would not be a bad idea to have asserts on blocktime to only be 0 for idletask and timer daemon task. I would have been very helpful for me in this particular project as I initially had no idea that the code was executed in a timer callback when i was expecting normal dedicated task context.
The one big issue on having an assert on block time != 0 in the timer would be that the timer task itself does block, so any assert would need to have a communication back channel from the timer task to the rest of the system. Thus, it likely would need to be something explicitly requested, and not just enable by defining configASSERT, but yes, it could be useful. I would also think you would want it to be on block time != 0, and not just on actually blocking to avoid race conditions possibly passing.
As far as different priorities of callbacks, my impression has always been that the timer/service task is supposed to be one of (if not the) highest priority tasks, as one of its purposes is to handle requests pended from an ISR, and that includes setting events group bits from an ISR. That implies that timer callbacks should be short and quick, so distinct priorities aren’t that vital. The implication seems to be that a low and slow callback would actually be a a short fast callback that signals a task to do the slow job.
I have at times setup a related task manager at idle priority to enable doing slow low priority operations on a periodic basis.
Besides the secondary role of the FreeRTOS timer task doing certain deferred ISR processing at a high priority I think that timers in general don’t really have priorities because their basic purpose is to signal points in time. This in turn should happen preferably immediately on expiration.
I’ve learned that timers doing (much) more than just signaling time events to (prioritized) worker tasks tend to be a misconception and/or could cause unwanted side effects.
Although it sometimes seems tempting to do so…
I just wanted to follow up on this as I have always (before at least) used the software timers as really low priority tasks. Whenever i had something that did not “deserve” to be a task of its own and had no timecritical issues at all I placed thoose actions in timercallbacks. Examples could be:
- once every 500 ms invert the led (it does not matter if i have to wait 1 second for it)
- once ever 3 seconds or so when nothing else is being done, update the display.
Whenever i needed a “point in time” marked, i have always used a dedicated hardware timer and a high priority interrupt, so i never needed to use the software timer that way.
This also worked well as the things made in the timer callbacks for me normally didnt communicate with any other RTOS objects.
So, that was basically how I used to use timers. I just wanted to point out that this is another way to see the functionality of the software timer features.
In the future though, i will however take notes from this thread and keep the callbacks very thin like Hartmut and possibly throw in a lowprio taskmanager like Richard-damon
I find I normally have at least SOME use for a high priority timer, one that won’t be delayed by most of the other tasks, so I need the timer task as a high priority task. I suppose part of this is that the timer task also handles the xTimerPendFunctionCallFromISR actions and the EventGroupxxxFromISR functions.
The two options I have used for what you describe is either create a single idle priority tasks to handle these, or put them in the IdleHook (the latter requires that they don’t need to do anything that blocks even for a moment).