Tickless Idle on STM32L4 Nucleo and low power modes question

Hi all,
I’ve been actively using FreeRTOS for the last few months in various projects, and we’re very happy with the results.

At the moment I’m working on an STM32L4 nucleo board, and I have a few questions regarding low power modes and FreeRTOS. Some questions may actually be STM32 related, but I’ll take my chances here anyway!

I’ve set configUSE_TICKLESS_IDLE to 1 in this project. My understanding is the scheduler will try to go to sleep whenever the expected idle time is more than configEXPECTED_IDLE_TIME_BEFORE_SLEEP (2). Now, the application works as follows:

  • There is one task that calls vTaskDelay(250), which is an LED toggle.
  • All other tasks start either with xStreamBufferReceive(…) or ulTaskNotifyTake(…) in different shapes and sizes as they are all interrupt based (USB comms, UART, etc).

Can I expect FreeRTOS to actually enter:

traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();

?

Here’s another question on the same system. I’d like to put the system on sleep for an indefinite amount of time, and be woken up either by push button (GPIO) interrupt or the RTC alarm. When the system wakes up, it would call a system reset, it doesn’t really care for how long it was asleep.

There is a system task which wakes up with xTaskNotifyWait(…), within that task, the system gets deinitialised like this:

vTaskSuspendAll();
vPortRaiseBASEPRI();

HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFE); ///< Enters sleep mode here
HAL_NVIC_SystemReset();

That puts the system to sleep until the button is pushed and it restarts. RTC values are kept. So far so good.

If I change the stop mode entry to _WFI and enable the GPIO pin as interrupt instead of event:
vTaskSuspendAll();
vPortRaiseBASEPRI();
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); ///< Enters sleep mode here
HAL_NVIC_SystemReset();

The system goes to sleep but fails to wake up when I push the button!

Below, I remove vPortRaiseBASEPRI():

vTaskSuspendAll();
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); ///< Enters sleep mode here
HAL_NVIC_SystemReset();

And the system jumps from the enter power mode to reset, starting again.

The GPIO button interrupt is set to 7. if I add this line:

vTaskSuspendAll();
vPortSetBASEPRI(7);
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); ///< Enters sleep mode here
HAL_NVIC_SystemReset();

Same as before, the system goes directly to reset. Same thing if I add
HAL_SuspendTick();
after suspending the tasks.

My guess is there is an interrupt running configured by FreeRTOS which wakes the system up immediately after going to sleep in stop mode.

How would I go deactivating only those interrupts used by FreeRTOS and not the ones I need, the GPIO interrupt and the RTC interrupt?

Alternatively, I could disable all interrupts and just wake up on events, but for the sake of understanding, I’d like to get both methods to work.

Many thanks in advance!

Cheers,

Alberto

Hi Alberto,

Yes.

On CM4, interrupts masked with BASEPRI cannot wake the CPU from WFI sleep. But the corresponding event can still wake the system from WFE sleep.

Since you’re already using tickless idle, you could implement configPRE_SLEEP_PROCESSING() and use it to induce STOP2. When that macro executes, FreeRTOS has already temporarily suspended its tick interrupt (not to be confused with the HAL tick). You’ll want configPRE_SLEEP_PROCESSING() to induce STOP2 conditionally, only when application conditions require STOP2 instead of normal tickless idle. Using that macro, you’ll be able to implement with WFI or WFE just as you said. Also, as you said, it’s good to watch out for the HAL tick as it will also wake you unnecessarily from tickless idle. You can stop the HAL tick, or you can override the HAL tick functions to use the FreeRTOS tick (a little more advanced).

Hi Jeff,

Thanks for your reply, I will give the config_PRE_SLEEP_PROCESSING() macro a try.

I found the information to this a bit confusing, so, does

vPortSetBASEPRI(7)

effectively “disable” the higher priority (lower number) interrupts (0 to 7)? In that case, wouldn’t a prio-8 interrupt get through? Or is it the other way around.

Cheers,

Alberto

Just the opposite. vPortSetBASEPRI(7) disables priority numbers 7 and higher, leaving priority 0-6 (the higher priorities) enabled.

It acts like you are currently in a priority 7 interrupt.

It’s just as Richard says above.

That’s for sure. ARM really did a number on Cortex M interrupt priorities, and NVIC settings too. It’s very easy to get confused. But I think FreeRTOS does a nice job of abstracting it for application developers and of catching related configuration errors.

Low numbers being High Priority isn’t really that strange. After all, don’t you do your #1 priority thing before working on #10? It is more of a language issue.

The fact that the priority number is a variable number of bits (depending on the exact machine) makes some sense as a generic core, as most machines DON’T need 256 interrupt priority levels, and saving hardware costs by reducing bits makes sense. Putting them in the Upper bits also make sense, it says a priority register value of 255 is always the lowest, 128 is the middle and 1 is the highest, thus configurations that don’t require exact knowledge are portable. The system intentionally allows lower bits to be written to even if they don’t read back.

Should be

vPortSetBASEPRI( 7 << (8 - configPRIO_BITS) );

if you want leave priorities 0 through 6 unmasked.

So, for my understanding, does that mean that

vPortSetBASEPRI(7);

leaves interrupts 0-6 enabled, and

vPortSetBASEPRI(7 << (8 - configPRIO_BITS))

disabled?

When you say “0 to 6 unmasked” that means to me that interrupts 0-6 are active. I mean, that’s what it sounds like to me (as in non-maskable interrupt).

Cheers,

Alberto

First, I needed to double check which numbering vPortSetBASEPRI uses, and it wants the upshifted version of the priority, so for the typical machine with configPRIO_BITS of 4, calling it with a value of 6 is the same as calling it with a value of 0, as only the upper 4 bits are used.

When using a configPRIO_BITS value for the priority, you need to shift it with a call like vPortSetBASEPRI(n << (8-configPRIO_BITS))

When you do that, it sets the ‘current’ interrupt priority that that value, and interrupts with that value or larger (higher number, lower priority) will be blocked. Interrupts with smaller values (lower number, higher priority) will still be able to interrupt the system.

Just adding to what Richard said –

     BASEPRI in STM32L4 (4 priority bits):

+---+--...--+---+---+---+---+---+---+---+---+---+
| X |  ...  | X | P | P | P | P | X | X | X | X |
+---+--...--+---+---+---+---+---+---+---+---+---+
 31           8   7   6   5   4   3   2   1   0

Field [P] can hold a number from 0 through 15.

To set [P] = 7, the CPU must write BASEPRI = (7 << 4). With [P] = 7, all interrupts at priorities 7 through 15 are masked. (And they cannot wake the MCU from WFI sleep.)

If the CPU writes BASEPRI = 7, [P] ends up set to 0. Note that [P] = 0 is a special case. It does not mask all interrupts at priorities 0 through 15. Instead, it masks no interrupts at all. This special case is needed because all other values of [P] mask at least some interrupts, and we need some way not to mask any interrupts. So zero does that for us.