2025-09-07 10:31 AM
I am having a "how did this ever work" moment. Maybe I found a (horrible) bug, or maybe I'm overlooking something.
In tickless idle mode FreeRTOS uses the COUNTFLAG in the SysTick SYST_CSR to determine if WFI exited due to the systick, and then calculates how many RTOS ticks have elapsed:
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
__asm volatile( "dsb" ::: "memory" );
__asm volatile( "wfi" );
__asm volatile( "isb" );
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
/* Re-enable interrupts to allow the interrupt that brought the MCU
out of sleep mode to execute immediately. see comments above
__disable_interrupt() call above. */
__asm volatile( "cpsie i" ::: "memory" );
__asm volatile( "dsb" );
__asm volatile( "isb" );
/* Disable interrupts again because the clock is about to be stopped
and interrupts that execute while the clock is stopped will increase
any slippage between the time maintained by the RTOS and calendar
time. */
__asm volatile( "cpsid i" ::: "memory" );
__asm volatile( "dsb" );
__asm volatile( "isb" );
/* Disable the SysTick clock without reading the
portNVIC_SYSTICK_CTRL_REG register to ensure the
portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set. Again,
the time the SysTick is stopped for is accounted for as best it can
be, but using the tickless mode will inevitably result in some tiny
drift of the time maintained by the kernel with respect to calendar
time*/
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );
/* Determine if the SysTick clock has already counted to zero and
been set back to the current reload value (the reload back being
correct for the entire expected idle time) or if the SysTick is yet
to count to zero (in which case an interrupt other than the SysTick
must have brought the system out of sleep mode). */
if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ) <<<<<< Checks the count flag here
However, notice that FreeRTOS briefly enables and disables interrupts to allow whatever pended interrupt caused wfi to exit to be serviced. Usually this will be the systick interrupt.
cmsis_os2.c provides a "helpful" wrapper for the systick interrupt - and the very first thing it does is to clear the systick count flag bit:
#if (USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION == 0)
void SysTick_Handler (void) {
/* Clear overflow flag */
SysTick->CTRL; <<<<<<<<<<<<<<<<< this is not good
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
/* Call tick handler */
xPortSysTickHandler();
}
}
#endif
As far as I can tell this means that the default code generated by CubeMX for the case of configUSE_TICKLESS_IDLE == 1 cannot work because as soon as FreeRTOS finds the conditions where it can use tickless idle then the FreeRTOS tick will stop incrementing. This is because when the systick wakes the processor from wfi FreeRTOS will think that a different interrupt woke the processor and if you follow that code path, and realizing that the systick counter has just rolled over (since it caused the wake) FreeRTOS calculates that only a fraction of a tick has elapsed.
Like I said, this is so fundamental that this is causing me a "how can this have ever worked or am I missing something blindingly obvious" moment.
Thanks