Hello,
I have been taking a look at the default implementation of vPortSuppressTicksAndSleep
for ARM_CM4F which uses SysTick and can’t help but feeling that the code is incorrect (freertos-kernel-10.4.1/portable/GCC/ARM_CM4F/port.c).
In summary, the default implementation changes the reload value of the SysTick timer to wake up on the scheduled RTOS tick. The relevant line of code is here:
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
Where ulTimerCountsForOneTick
is the ratio of the SysTick frequency to the RTOS tick frequency, and xExpectedIdleTime
is the difference between the next task unblock time and the current RTOS tick.
Then later, when it has been decided to sleep:
portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
The portion of this code ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) )
computes the number complete RTOS ticks to wait, in units of SysTick. Then portNVIC_SYSTICK_CURRENT_VALUE_REG
is added to compensate for the currently-in-progress RTOS tick. However, the flaw that I’m seeing is in the way that the SysTick timer works: According to ARM’s Cortex-M4 Generic User Manual (DUI0553) the SysTick timer value starts at 0, reloads to the current LOAD
value on the next cycle, and downcounts each cycle, triggering an exception on the cycle that transitions VAL
from 1 to 0.
Take the simple example below, with the system tick frequency at 2Hz and the RTOS frequency at 1Hz. The LOAD
value for the SysTick is set to the desired frequency - 1 (i.e. LOAD = 1
).
On SysTick cycle 0, VAL
is set to 0. On cycle 1, VAL
is reloaded to 1. On cycle 2, VAL
transitions from 1 to 0, triggering an interrupt, incrementing the RTOS tick by 1. This then repeats. Assume the RTOS decides to sleep at the time marked “now” (current RTOS tick = 1), and xExpectedIdleTime
is 2 ticks (wake up on or before RTOS tick 3).
The reload value computation computes the number of complete RTOS ticks to wait as ( 2 * ( 2 - 1) == 2
correctly. However, consider the tick-in-progress portion of the computation: portNVIC_SYSTICK_CURRENT_VALUE_REG
will return 0, leading ulReloadValue
to be 2. VAL
is set to 0 and LOAD
is set to 2. On SysTick cycle 3, VAL
is set to 2. VAL
will transition from 1 to 0 on SysTick cycle 5, earlier than the expected SysTick cycle 6. If you were to instead mark the “now” point to be just after SysTick cycle 3, and perform the same computation, you wake up 1 SysTick tick too late.
In summation, the computation is incorrect as a result of SysTick starting from 0 on the first cycle, and interrupting on transition from 1 to 0 as opposed to starting from LOAD
, and interrupting from 0 to LOAD
. If the latter case were true, the computation would be correct.
Sorry for the long post. Am I missing something? Or is the computation just wrong?
Best,
Jeff