Portenta H7 low power mode - project

Hi all,
it looks like, the Portenta H7 is prepared (HW wise) for lower power modes (MCU supports it, also PMIC on board does). And I saw also Arduino system_clock.c is prepared a bit for two different MCU core clocks possible.

But there is not really an API to use low power modes (how to enter, how to wakeup and come back). Esp., there is not any code to do a "voltage scaling".

Let's start a project to add low power mode API (and functions to bring Portenta H7 into low power consumption mode(s)).

I start this as a project, to come up with API calls. Please join.

What I know and where to start:

  • Portenta H7 supports low power modes, e.g. to bring Domains into STOP mode, have wakeup INTs (or events) etc.
  • also: the PMIC on board (controlling the voltages) has a pin connection with MCU: so, the PMIC could act on this external trigger signal to lower the voltages (e.g. when MCU has lowered the clock speed, toggle on PMIC to other profile).

What to achieve:
I am thinking about an API to lower the power consumption, but via different steps (modes).

There should be several modes, such as:

  • DVS (Dynamic Voltage Scaling):
    lower the MCU core frequency and scale the supply voltages down. Here just as one step:
    full performance and low performance (slower clock and also lowered voltage)
    DOZE mode (but full retention)
  • Sleep mode:
    retain the memory content, but stop the MCU, wait for a wakeup (via WFI)
  • Deep Sleep mode:
    stop the MCU, but stop also Domains, power down, NO retention, also stop the clocks - data in memories will be lost - a very low power mode (but tough to come back)

Some helpful links:
Low Power Modes in STM MCUs
MCU power modes

I want to keep it simple.
First approach to "try":

  • lower the MCU clock
  • add to lower also the voltages (via PMIC and using the external control/trigger signal)
    So, a DVS single step mode

Second approach to "try":

  • use WFI instruction and hold the MCU (until next wakeup INT)
  • power down not needed Domains before MCU enters WFI, but keep retention (memory content not lost)

Third approach to "try":

  • disable domains (no retention, data is lost), stop clocks
  • how to start over? How to reload memory content (or re-initialize system)?

Easiest approach to test:

  • use RESET to recover from low power mode
  • or use an user GPIO pin (like a user button) to power up again (but not for Deep Sleep Mode, better to reset all and start over, for simplification)

The "problem" not addressed here:
what about all the components on board, e.g. WiFi, USB chip, Crypto chip, QSPI?
In order to see a reduction in power consumption, actually all not needed chips should be disabled (or bring to their low power mode). I will not address (yet) this topic, just let's focus on the MCU itself. Maybe, the results in current reduction might not be so dramatic. But we can add anytime later (when implementing the "Deep Sleep Mode").

I try to create sample code here (a demo project) with the intention to have an API for low power modes.

Project Update - 1

provide 5.0V on VIN, power from external power supply.
I just change the clock speed for MCU - nothing else:

  • 490 MHz on full speed (I overclock a bit)
  • 60 MHz on "DVS mode"
    It brings the current down from 256mA to 139mA. So, 117mA lower just on running with lower clock speed on MCU (BTW: I cannot go too slow - the USB-C starts failing, 50 MHz MCU starts to fail).

Arduino has a similar file, "system_clock.c":

int SetSysClock_PLL_HSE(uint8_t bypass, int lowspeed);

My one (attached here) is tweaked for other clocks and also lower low speed divider setting as original file:
system_clock.c (14.1 KB)
The bypass parameter should be always 1 (it means: there is an external digital XTAL oscillator, 0 means: there is just a XTAL. But you provide "lowspeed" as 0 for "full speed" and 1 "for slower clock (low speed)".

So, even just lowering the MCU core clock has a huge impact (as I have expected).

Next step:
When clock is lowered - lower also the voltage (as "DVS"), config and trigger the PMIC to enter the profile to provide "low power voltage".

Project Update - 2
I see the STANDBY signal (as GPIO PJ0 from MCU to PMIC) working:
It toggles from RUN mode to STDBY mode (in PMIC).
Actually a bit strange: it switches off the 3V3 on headers (e.g. breakout board). The 3V3 disappears completely (should not, but OK for now, the MCU is still alive).

It does not change yet the MCU core voltage: my Vref on MCU is still the same (I measure with MCU itself, using internal ADC channels). It just kills the 3V3 on external components. So, I guess: it powers down some other chip on board (just to measure the power consumption again).

Next steps:
Understand the Power Distribution (which voltage comes as what?, e.g. there are 3 switched regulators and 3 LDOs in PMIC, one LDO seems to follow my STANDBY signal).

Bring also the MCU core voltage down. But this could mean also to check if the switching regulator (SMPS) is enabled and used in MCU. Possible, that it steps up again, even the external voltage was lowered.

And get more familiar with the PMIC: why it does not seem to change SW3 (the third LDO)? I guess, because it is fix pre-programmed by the OTP (and cannot be overwritten).

Also, I have to understand what the blue button on breakout will do: it seems to be PWRON signal for PMIC. With some PMIC config I saw it working like a reset (obvious: the PMIC seems to do a power cycle and the PMIC generates also the RESET signal for the MCU).

Never mind, making progress... but a very delicate topic.

Project Update - 3
I could bring down my current from 257mA to 248mA (tweak the PMIC, disable all not needed chips on board ..., see below).
I have many devices initialized: RTC, SDRAM, QSPI, SPI, I2C, SAI4 (PDM MICs), TIM (for PWM, not used), SDCard (prepared but not used), ETH (no cable connected).

I have three power modes, with these currents:
FULL
drains: 249mA

Low Power
drains: 83mA
(USB-C VCP UART still alive, for all not needed devices (e.g. SDRAM, ETH, QSPI, SPI ...) the clock is disabled.
I can enable the clocks again and all goes back to full functionality.
The MCU core clock is lowered (60 MHz) and the PMIC is brought to STANDBY mode (via GPIO pin from MCU to PMIC to toggle the profile setting).

STOP
drains: 49mA
all off, MCU in STOP mode, with WFI instruction

When I do a cross-check:

  1. power cycle board, but hold MCU in reset: 19mA
  2. later, when all for FULL was configured, but hold MCU in reset: 46mA
    So, this is the consumption caused by all the other chips enabled (not in reset), just MCU in reset (the standby current for all active chips on board)
  3. put a WFI right in ResetHandler (do not continue): 17mA - BUT: the PMIC is not configured (shows the orange LED), potentially not correct voltages on MCU - not a reliable value.
  4. When STOP mode was entered: 49mA, when MCU hold in reset: 31mA
    so, the MCU seems to drain just additional 18mA - great, not so much difference (all the other current comes from stuff on board).

So, I assume:

  • up to 46mA are drained by chips on board, when not properly disabled/reset (when not needed but "active")
  • achieving 49mA with all the optimization and MCU in STOP mode - pretty good (maybe possible to tweak a little bit more, esp. via PMIC voltages in STANDBY mode).

Lessons learned
For reducing the power, things to do:

  • do not switch on LEDs - every LED contributes with 4..6mA!
  • disable all chips not needed, e.g. CRYPTO, MIPI bridge, WIFI/BT chip (I do not use):
    use the enable signals of those chips or hold these chips in reset (when never needed)
  • don't let chip input signals, driven by MCU, be floating
  • running RTOS: when I enter "Low Power" - do an WFI in idle thread:
    it stops MCU until next INT comes in, which is mainly the SysTick INT. So, it sleeps for 1 ms (we could also increase the time period for SysTick to improve further, longer period to stay in WFI)

When entering STOP mode - the biggest improvement comes from, or it has to be done:

  • stop the SysTick INT (otherwise the MCU wakes up again and is never "quiet")

  • disable the USB PHY, disable ETH PHY (hold both in reset) - a dramatic decrease in current!

  • configure the PMIC properly, esp. the STANDBY profile settings (lower voltages)

  • you have to use the PMIC_STDBY signal (to toggle) between FULL and Low Power (and also for STOP mode)

Remarks:
For current measurement I have powered Portenta H7 with exactly 5V on VIN (measured as 5V on the VCC pin, VIN is a bit larger due to diode on board).
In Low Power mode: MCU runs core clock: 60 MHz, in FULL: 490 MHz (and with 3.3V volts, not 3.1V set as default by Arduino).

Next steps:
Configure an EXTI as wakeup pin (GPIO with a button). But due to fact I use STOP mode - all memory content is lost. Potentially I will do a full MCU reset to recover from this mode.
In order to distinguish between "normal reset" and "back from low power mode": I intend to use the RTC BKUP registers (to leave a surviving pattern value there).

So, it looks like as:

  • my "Low Power" mode: 83mA - and I can go back from there to "FULL"
  • in "STOP" mode - lowest power consumption: 49mA
  • lower as 17 ... 19mA you can never come (current drained by the board, in idle state)
    Pretty cool (and impressive how low the board can go).

FIles:
Here the files, for your reference (not directly usable without other files and used in a STM32CubeIDE project, not in Arduino sketch).

system_clock.c (14.7 KB)
I2C_PMIC.c (12.5 KB)
power_mngt.h (486 Bytes)
power_mngt.c (3.5 KB)

Who could go lower as 49mA? Let's start a challenge... :grinning:

With a 1C (3.7V) LiPo, 700mAh - it can just run max.. 2.5 hours in FULL mode, max. 14 hours in STOP mode. Not very long.

As I understand the schematics: the coin cell on breakout board is just there to retain the RTC and Backup memory in MCU (not for MCU power to run).

Project Update - 4
I could reduce my average current consumption from 255 mA down to 180 mA - a reduction by 75 mA !
just by:

  • I have RTOS running: use the IdleThread to power down during the period until next INT

It works fine. Just not really optimized (could be improved):

  • my SysTick INT runs, used to wakeup the scheduler, looking for a pending thread, and my IdleThread is such one becoming pending every 1 ms
  • so, I enter SLEEP mode for a period of 1 ms, very 1 ms back to FULL (see below for more optimization "tricks")

So, my IdleThread looks as this:

void StartDefaultTask(void *argument)
{
  (void)argument;
  static int cnt = 0;
  for(;;)
  {
	  //this will be woken up every 1 ms, due to SysTick
	  if (PWR_GetMode() == PWR_FULL)
  	  {
  		  /* green blinking LED as "still alive" */
		  if (cnt == 0)
			  HAL_GPIO_WritePin(GPIOK, GPIO_PIN_6, GPIO_PIN_SET);		//off
  		  if (cnt == 1000)
  			  HAL_GPIO_WritePin(GPIOK, GPIO_PIN_6, GPIO_PIN_RESET);		//on
  		  cnt++;
  		  if (cnt >= 1200)
  			  cnt = 0;
  	  }

	  //go to sleep, nothing to do, wait for an INT
	  HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
}

Configure RTOS for dynamic power handling
The RTOS supports low power mode, but potentially not enabled (and not implemented by user functions needed).

The macro "configUSE_IDLE_HOOK" can be used to define your own "vApplicationIdleHook()" function. Not enabled (by default, not for me) - I use my own "StartDefaultTask()" (but works in the same way).

I saw in RTOS source code:

  • there is a macro "configUSE_TICKLESS_IDLE"
  • it means: it should not wake up every 1 ms, with SysTick, instead it should stay "calm" unless something has to be done (e.g. an INT comes and a thread is activated) - so, it can stay in SLEEP mode much longer (not just every 1 ms again)
  • this feature, when enabled, seems to use two functions (defined via macros):
    configPRE_SLEEP_PROCESSING() and configPOST_SLEEP_ PROCESSING()
  • and it would do for you the WFI in between

So, enabling this RTOS feature and using the PRE and POST functions:

  • I could also lower the clock speed, change the voltage (DVS) etc.
    Cool (but not tried yet). It would save even more power.

Arduino/mbed
I do not use (my project is not an Arduino sketch). I am sure, Arduino mbed uses also RTOS.
But no idea if this "power saving trick" is used (as default), part of Arduino code (but they could add).

All works fine. All my threads are triggered by a real HW INT, also the USB-C VCP UART. So, I can go to SLEEP mode when all is idle, MCU will wake up automatically by the next INT.

Just, if I want to lower even more the current consumption - I have to configure RTOS properly for it: instead of waking up on every SysTick - just wakeup on next INT. And use the PRE and POST functions to toggle also the MCU core clock, the voltage, in between.

This should go even further down with the current.
(but the peak current, when all is on, is not reduced, just the average over time, but resulting in less power drained from a battery, extending the life time when running from a battery, still a need to make sure the high peak current can be drained from a battery).

I close this project. Good results, good achievements (even if still room left to optimize further).

check out

https://github.com/arduino/ArduinoCore-mbed/issues/619

Cool, it talks almost about the same... (approach and things to do)

WOW - good work !!

i play arround in Aduino with system_clock.c and it works good to switch between "highspeed" and "lowspeed" - works stable -exept the BLE seems to crash when you switch

i think that i have to resync BLE but dont know how

i know that your system clock code file is probably my solution but i am failing to get my sketch to compile with it. i actually am just trying to get the SDMMC bus clock for the uSD card slot to run at 25 or 27Mhz. it seems to be running at 1/4 of this rate right now. can you give me a quick nudge on how to use your files for this. i am running in the Arduino IDE with the Portenta H7 Lite connected to a Portenta Breakout board and i am trying to write a lot of data to a uSD card using the SDMMCblockdevice and FATFileSystem libraries. thanks in advance if you can help. you seem to be one of the few people i see trying to work with portenta fairly consistently. i am wondering if there is a dedicated user group for this thing. it is so complicated trying to understand how arduino integrated this thing into their eco system which i have been using since the beginning.