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 ) );
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?