vPortSuppressTicksAndSleep SysTick implementation wrong?

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

I really appreciate the depth of your report. It will take some time to dig into. From what you have written it seems the mistake is not taking into account the “on the next cycle”, implying there is one additional clock cycle as the code assumes the reload occurs within the same cycle.

If this is correct then it seems the difference will be minimal, although it should be corrected. By minimal I mean one cycle of a fast clock - normally in the tens of MHz. Is that correct, or is the difference greater than this?

Hi Richard,

Thanks for the response. You are correct: the question is whether VAL is reloaded right away or on the next cycle. I’m leaning towards it reloading on the next cycle for this reason - assume you want to generate a SysTick exception every 5 cycles (and thus set LOAD to 4). If VAL were reloaded instantly, and not on the next cycle, the first period would be short by 1 cycle, (due to the exception being generated on transition from 1 to 0). The Keil M4 SysTick man page (sorry, it won’t let me post links yet) also mentions VAL being wrapped on the next cycle, though this says nothing about the behavior when setting LOAD and enabling the timer.

As for the difference in behavior, it’s true that the side-effects are small in the average case - you wake up one cycle late. However, if you happen to enter sleep while VAL is 0 (probably a rare case), you wake up LOAD cycles early, which is a bit worse.

Hi @jefflongo,

Stopping SysTick on zero is an issue in the current implementation. As you noted, it causes an early wake up, or in other words, it causes xTickCount to jump ahead. In the worst case, it can cause xTickCount to jump ahead by the entire expected idle time.

This PR includes the fixes.

The other issue of being off by one systick count is actually swallowed up by ulStoppedTimerCompensation which addresses not only that issue but the duration of time the timer is stopped. For what it’s worth my own testing indicated the timer loads from the load register on the next cycle after the timer is enabled, just as you suspected.

Jeff T