Skip to content

Commit 316b656

Browse files
committed
Fix tickless idle with alternate systick clocking
Prior to this commit, in configurations using the alternate SysTick clocking, vPortSuppressTicksAndSleep() might cause xTickCount to jump ahead as much as the entire expected idle time or fall behind as much as one full tick compared to time as measured by the SysTick. SysTick ------- The SysTick is the hardware timer that provides the OS tick interrupt in the official ports for Cortex M. SysTick starts counting down from the value stored in its reload register. When SysTick reaches zero, it requests an interrupt. On the next SysTick clock cycle, it loads the counter again from the reload register. The SysTick has a configuration option to be clocked by an alternate clock besides the core clock. This alternate clock is MCU dependent. Scenarios Fixed --------------- The new code in this commit handles the following scenarios that were not handled correctly prior to this commit. 1. Before the sleep, vPortSuppressTicksAndSleep() stops the SysTick on zero, long after SysTick reached zero. Prior to this commit, this scenario caused xTickCount to jump ahead one full tick for the same reason documented here: 0c7b04b 2. After the sleep, vPortSuppressTicksAndSleep() stops the SysTick before it loads the counter from the reload register. Prior to this commit, this scenario caused xTickCount to jump ahead by the entire expected idle time (xExpectedIdleTime) because the current-count register is zero before it loads from the reload register. 3. Prior to return, vPortSuppressTicksAndSleep() attempts to start a short SysTick period when the current SysTick clock cycle has a lot of time remaining. Prior to this commit, this scenario could cause xTickCount to fall behind by as much as nearly one full tick because the short SysTick cycle never started. Note that #3 is partially fixed by 967acc9 even though that commit addresses a different issue. So this commit completes the partial fix.
1 parent 967acc9 commit 316b656

File tree

1 file changed

+39
-25
lines changed

1 file changed

+39
-25
lines changed

portable/GCC/ARM_CM4F/port.c

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ void xPortSysTickHandler( void )
515515

516516
__attribute__((weak)) void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
517517
{
518-
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
518+
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickDecrementsLeft;
519519
TickType_t xModifiableIdleTime;
520520

521521
/* Make sure the SysTick reload value does not overflow the counter. */
@@ -546,26 +546,27 @@ void xPortSysTickHandler( void )
546546
kernel with respect to calendar time. */
547547
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT );
548548

549+
/* Use the SysTick current-value register to determine the number of
550+
SysTick decrements remaining until the next tick interrupt. If the
551+
current-value register is zero, then there are actually
552+
ulTimerCountsForOneTick decrements remaining, not zero. */
553+
ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG;
554+
if( ulSysTickDecrementsLeft == 0 )
555+
{
556+
ulSysTickDecrementsLeft = ulTimerCountsForOneTick;
557+
}
558+
549559
/* Calculate the reload value required to wait xExpectedIdleTime
550560
tick periods. -1 is used because this code normally executes part
551561
way through the first tick period. But if the SysTick IRQ is now
552562
pending, then clear the IRQ, suppressing the first tick, and correct
553563
the reload value to reflect that the second tick period is already
554564
underway. The expected idle time is always at least two ticks. */
555-
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * (xExpectedIdleTime - 1UL) );
565+
ulReloadValue = ulSysTickDecrementsLeft + ( ulTimerCountsForOneTick * (xExpectedIdleTime - 1UL) );
556566
if( ( portNVIC_ICSR_REG & portNVIC_PEND_SYSTICK_SET_BIT ) != 0 )
557567
{
558568
portNVIC_ICSR_REG = portNVIC_PEND_SYSTICK_CLEAR_BIT;
559-
560-
/* Skip the correction described above if SysTick happened to
561-
stop on zero. In that case, there are still
562-
ulTimerCountsForOneTick ticks left in the second tick period,
563-
not zero, so the value in portNVIC_SYSTICK_CURRENT_VALUE_REG
564-
already includes the correction normally done here. */
565-
if( portNVIC_SYSTICK_CURRENT_VALUE_REG != 0 )
566-
{
567-
ulReloadValue -= ulTimerCountsForOneTick;
568-
}
569+
ulReloadValue -= ulTimerCountsForOneTick;
569570
}
570571
if( ulReloadValue > ulStoppedTimerCompensation )
571572
{
@@ -621,19 +622,14 @@ void xPortSysTickHandler( void )
621622
time*/
622623
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT );
623624

624-
/* Determine if the SysTick clock has already counted to zero and
625-
been set back to the current reload value (the reload back being
626-
correct for the entire expected idle time) or if the SysTick is yet
627-
to count to zero (in which case an interrupt other than the SysTick
628-
must have brought the system out of sleep mode). */
625+
/* Determine whether the SysTick has already counted to zero. */
629626
if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
630627
{
631628
uint32_t ulCalculatedLoadValue;
632629

633-
/* The tick interrupt is already pending, and the SysTick count
634-
reloaded with ulReloadValue. Reset the
635-
portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
636-
period. */
630+
/* The tick interrupt ended the sleep (or is now pending), and
631+
a new tick period has started. Reset portNVIC_SYSTICK_LOAD_REG
632+
with whatever remains of the new tick period. */
637633
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
638634

639635
/* Don't allow a tiny value, or values that have somehow
@@ -654,11 +650,29 @@ void xPortSysTickHandler( void )
654650
}
655651
else
656652
{
657-
/* Something other than the tick interrupt ended the sleep.
658-
Work out how long the sleep lasted rounded to complete tick
653+
/* Something other than the tick interrupt ended the sleep. */
654+
655+
/* Use the SysTick current-value register to determine the
656+
number of SysTick decrements remaining until the expected idle
657+
time would have ended. */
658+
ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG;
659+
#if( portNVIC_SYSTICK_CLK_BIT_SETTING != portNVIC_SYSTICK_CLK_BIT )
660+
{
661+
/* If the SysTick is not using the core clock, the current-
662+
value register might still be zero here. In that case, the
663+
SysTick didn't load from the reload register, and there are
664+
ulReloadValue + 1 decrements remaining, not zero. */
665+
if( ulSysTickDecrementsLeft == 0 )
666+
{
667+
ulSysTickDecrementsLeft = ulReloadValue + 1UL;
668+
}
669+
}
670+
#endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */
671+
672+
/* Work out how long the sleep lasted rounded to complete tick
659673
periods (not the ulReload value which accounted for part
660674
ticks). */
661-
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
675+
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - ulSysTickDecrementsLeft;
662676

663677
/* How many complete tick periods passed while the processor
664678
was waiting? */
@@ -678,12 +692,12 @@ void xPortSysTickHandler( void )
678692
to receive the standard value immediately. */
679693
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
680694
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
695+
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
681696
#if( portNVIC_SYSTICK_CLK_BIT_SETTING != portNVIC_SYSTICK_CLK_BIT )
682697
{
683698
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
684699
}
685700
#endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */
686-
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
687701

688702
/* Step the tick to account for any tick periods that elapsed. */
689703
vTaskStepTick( ulCompleteTickPeriods );

0 commit comments

Comments
 (0)