Skip to main content
Explorer II
October 20, 2016
Question

STM32L1 FreeRTOS tickless idle with RTC wakeup

  • October 20, 2016
  • 10 replies
  • 3612 views
Posted on October 20, 2016 at 20:04

I'd like to implement vPortSuppressTicksAndSleep() to put the MCU into sleep or stop mode when there is nothing to do in FreeRTOS tasks. It should be woken up by an EXTI interrupt or RTC wakeup. In my implementation the RTC is configured to generate wakeup interrupt every 1 ms. By counting interrupts the function knows if given period of time has elapsed. At least in theory. In practice it seems that the variable xActualIdleTime is always zero. The funny thing is that when I put breakpoints and the code is executed line by line, the variable is incremented.

void
vPortSuppressTicksAndSleep(portTickType xExpectedIdleTime)
{
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; 
// systick IRQ off
PWR_ClearFlag(PWR_FLAG_WU);
watchdog_reset();
volatile
portTickType xActualIdleTime = 0;
eSleepModeStatus eSleepStatus = eTaskConfirmSleepModeStatus();
__asm 
volatile
( 
''cpsid i''
);
if
(eSleepStatus != eAbortSleep)
{
RTC_WakeUpCmd(ENABLE);
bool
_other_INT_Executed = 
false
;
while
((((xActualIdleTime + RTC_ClockRateInMs) / portTICK_RATE_MS) < xExpectedIdleTime) && !_other_INT_Executed)
{
__DSB();
__ISB();
PWR_EnterSleepMode(PWR_Regulator_LowPower, PWR_SLEEPEntry_WFI);
if
(RTC_GetFlagStatus(RTC_FLAG_WUTF)) {
RTC_ClearFlag(RTC_FLAG_WUTF);
xActualIdleTime++;
} 
else
{
_other_INT_Executed = 1;
}
}
RTC_WakeUpCmd(DISABLE);
}
if
(xActualIdleTime) vTaskStepTick(xActualIdleTime / portTICK_RATE_MS);
__asm 
volatile
( 
''cpsie i''
);
_sys_restore_after_lpm();
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; 
// systick IRQ on
}

What can cause such behavior? Maybe my approach is wrong, but I couldn't find good example for RTC tickless idle. What should happen with interrupts inside

vPortSuppressTicksAndSleep()

? What is the role of cpsid? Interrupts are necessary to wake up the MCU, but IRQ handlers shouldn't be called? #rtc #freertos #stm32l1 #tick
    This topic has been closed for replies.

    10 replies

    grzegorzAuthor
    Explorer II
    October 22, 2016
    Posted on October 23, 2016 at 00:44

    I've rewritten the function and it seems to work correctly. The downside is however that due to low RTC resolution I don't know exactly how long the MCU was sleeping if it was woken ip by other interrupt than RTC WU. In this case I can only use RTC subsecond register which has 1/128s (around 8 ms) resolution. Does anybody know better solution?

    void
    vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime )
    {
    SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; 
    // systick IRQ off
    PWR_ClearFlag(PWR_FLAG_WU);
    watchdog_reset();
    portTickType xActualIdleTime = 0;
    uint32_t subSecond = RTC_GetSubSecond();
    eSleepModeStatus eSleepStatus = eTaskConfirmSleepModeStatus();
    __asm 
    volatile
    (
    ''cpsid i''
    );
    __DSB();
    __ISB();
    if
    (eSleepStatus != eAbortSleep)
    {
    RTC_ClearFlag(RTC_FLAG_WUTF);
    // RTC Set WakeUp Counter
    RTC_SetWakeUpCounter((xExpectedIdleTime * (LSE_FREQUENCY / RTC_WakeUpClock_RTCCLK_Div)) / 1000);
    RTC_WakeUpCmd(ENABLE);
    __DSB();
    PWR_EnterSleepMode(PWR_Regulator_LowPower, PWR_SLEEPEntry_WFI);
    __ISB();
    RTC_WakeUpCmd(DISABLE);
    }
    if
    (RTC_GetFlagStatus(RTC_FLAG_WUTF) == SET) {
    xActualIdleTime = xExpectedIdleTime;
    } 
    else
    {
    xActualIdleTime = (RTC_GetSubSecond() - subSecond) * 8;
    if
    (xActualIdleTime > xExpectedIdleTime) xActualIdleTime = xExpectedIdleTime;
    }
    vTaskStepTick(xActualIdleTime / portTICK_RATE_MS);
    __asm 
    volatile
    (
    ''cpsie i''
    );
    __DSB();
    __ISB();
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; 
    // systick IRQ on
    }

    Visitor II
    August 22, 2018

    Hi ,

    good JOB! Ia m working on the same thing. Have you made another solution with better ressolution of time?

    Jan.

    October 14, 2018

    Hi! I’m working too to the same topic. I use L4 series but the concept is the same. To increase resolution I sat up rtc asynchronous prescaler to 7 and synchronous one to 4095. This way the micro is consuming a bit more but the accuracy using ssr register is a lot better. Anyway I still have troubles maintaining accuracy in the order of milliseconds (which I need for my application). Any suggestion is really appreciated, I’ll probably post some code soon

    Graduate II
    October 14, 2018

    Perhaps use a LPTIM so you can resolve sub-millisecond.

    March 28, 2019

    Hi @angeletti2​  ,

    did you manage to increase the resolution? anyways, it would be great if you could share your code on the tickless mode including the correction of the systick after wakeup from sleep mode...

    I guess there are quite a lot of people interested in how this can be solved.

    Michael

    Visitor II
    February 18, 2020

    I just finished a solution here, using LPTIM. It's for STM32L4, but is easily adapted.

    https://gist.github.com/jefftenney/02b313fe649a14b4c75237f925872d72

    • It doesn't use systick or the RTC at all.
    • It never stops the LPTIM.
    • It eliminates drift in kernel time normally associated with tickless idle.
    • It eliminates drift due to rounding the OS tick width to a whole number of timer counts.

    Jeff

    February 19, 2020

    cool, thanks for sharing!

    Visitor II
    March 16, 2021

    @Jeff Tenney​ 

    The link is broken.

    In STOP2 mode, if the LPTM clock source uses LSE, the timing time is very short. If you need to sleep for a long time, such as 10 hours, how to configure it? Or can only replace other clock sources?

    Visitor II
    March 16, 2021

    Hi @WCore.1​ the link above still works for me. If it's not working for you, you can google lptimTick.c.

    Using lptimTick.c, you would use normal code to delay 10 hours without worrying about the range of the timer. Like this:

     #define TIMEOUT_TICKS_10_HOURS ( pdMS_TO_TICKS( 10U * 60 * 60 * 1000 ) )
     
     // A simple delay:
     vTaskDelay( TIMEOUT_TICKS_10_HOURS );
     
     // A timeout while waiting for a signal:
     if ( xTaskNotifyTake( pdTRUE, TIMEOUT_TICKS_10_HOURS ) )
     {
     // Signal detected
     }
     else
     {
     // Timeout occurred (no signal)
     }

    FreeRTOS takes care of the "long" sleep for you by executing many short sleeps (2 seconds each in the case of LPTIM/LSE with no prescaler). It sleeps the maximum time supported by the timer over and over again until the time left no longer exceeds the maximum time supported by the timer.

    The amount of run time required to start another short sleep is negligible. You'll still get your very low STOP2 currents.

    Visitor II
    March 16, 2021

    @Jeff Tenney​ 

    You inspired me a lot. Thanks.