I believe there is a problem with the recommended tickless idle implementation i.e. here: http://freertos.org/low-power-tickless-rtos.html, or with the supporting tick update calls.
I have had timing issues with a project on a Nordic nRF52832 (Arm cortex M4), and I have tracked it down to the tickless idle. The project uses Nordic’s tickless idle implementation, but they appear to have followed the implementation recommendations (from the link above).
The problem is that upon waking from a tickless idle sleep, it does not immediately execute the tasks that should now be pending (i.e. the ones that scheduled the wakeup time in the first place). I have spent days trying to track down the exact cause, and I think I have found the main problem.
Normally (without tickless idle), a tick interrupt happens on every time tick, and does the following:
- (Tick ISR) Calls xTaskIncrementTick(), which:
- Increments time by exactly 1 tick.
- Moves tasks from the delayed list to the ready list (if task time is up)
- (Tick ISR) Requests a context switch (if the result of xTaskIncrementTick() indicates something was unblocked)
The key here is that tasks get moved from the delayed list to the ready list, and the context switch executes the ready list (if higher priority than the current task)
The sequence with the tickless idle implementation (both freeRTOS’s recommendation, and Nordic’s implementation) is:
- (Idle task) Suspends scheduler
- (Idle task) Checks if delayed time is long enough to sleep
- (Idle task) Calls portSUPPRESS_TICKS_AND_SLEEP(), which (among a few other things):
- Sets up a wake interrupt
- Wakes Up
- Calculates time difference
- Corrects time by calling vTaskStepTick() which:
- Increments time by the specified time difference
- (Idle task) Resumes scheduler
So the problem seems to be the fact that incrementing time with vTaskStepTick() fixes the system time, but does not unblock tasks (i.e. the task that scheduled the wakeup). So the tasks wait until the next time tick that processes normally, and then calls xTaskIncrementTick() (in the Tick ISR) which will then unblock the waiting task (now a tick delayed).
In digging deeper, I have found examples of tickless idle implementations that use vTaskStepTick() to increment by one less tick than the time elapsed, and then rely on the normal tick interrupt to do the final tick. This seems to be an ok workaround for some implementations, but the Nordic implementation suppresses the normal tick interrupt during tickless idle, and so there is no pending interrupt upon waking.
I have found a similar workaround, by calling vTaskStepTick(timeDiff-1), then calling xTaskIncrementTick() (inside of portSUPPRESS_TICKS_AND_SLEEP()). Technically this causes the final tick to be pended (since the scheduler is still suspended), but it seems to work.
Ideally, I feel like vTaskStepTick() (freeRTOS non-portable code) should be updated to also handle unblocking of tasks, in the same way that xTaskIncrementTick() does. The main difference still being that vTaskStepTick() would not do this for every discrete time step, but only once at the end. It could be implemented very simply using the same diff-1 and xTaskIncrementTick() combination in my workaround, or the unblocking code could separated from xTaskIncrementTick() and called separately by each function. It makes most sense to me that whether you are stepping time by 1 tick or multiple ticks, the freeRTOS kernel should then decide if the time change should unblock anything.
Obviously some people have figured this problem out (hence SOME implementations account for it), but the recommended implementation (the link above) does not show anything to handle it, and Nordic’s implementation follows it.
Is there a reason the kernel doesn’t handle unblocking the tasks in vTaskStepTick()? It seems odd to me that there seems to be so much confusion on how to handle it, and that even a company like Nordic, with a large user base and many iterations of code using FreeRTOS, still doesn’t seem to do it right.