cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L1 FreeRTOS tickless idle with RTC wakeup

grzegorz
Associate III
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
10 REPLIES 10
grzegorz
Associate III
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
}

JAN R
Associate III

Hi ,

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

Jan.

angeletti2
Associate II

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

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

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Schmid.M.
Senior

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

Jeff Tenney
Senior

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

Schmid.M.
Senior

cool, thanks for sharing!

WCore.1
Associate

@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?

Jeff Tenney
Senior

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.