-
Notifications
You must be signed in to change notification settings - Fork 2k
Power Management
It is crucial to first understand both the source(s) of energy consumption, and the influence software can have on these. A device’s total energy consumption is the sum of the power consumed by
- (i) the CPU,
- (ii) devices connected through peripherals, and
- (iii) various other passive components external to the CPU.
While (iii) is heavily influenced by a board’s hardware design, both (i) and (ii) are influenced by software. On one hand, the power management system in RIOT automatically sets the CPU in the deepest possible sleep mode when idle, which decreases energy consumption due to (i). On the other hand, user applications or other system modules are expected to deal with (ii) by managing the state of peripherals. For example, a network transceiver is managed by a MAC layer protocol module, while a sensor is managed by a sensing application.
At the microcontroller (MCU) level there are several ways to reduce power usage. Some methods for reducing power consumption are:
- Slow down the clocks, especially the main clock for the core (CPU and main logic) of the device.
- Reduce the supply voltage to the core of the device.
- Stop the main clock.
- Turn off power to the core.
- Turn off power to the clock generator(s).
- Turn off clocks to unused peripherals and peripheral bus controllers.
- Turn off power to unused peripherals.
- Put memory into a low-power (RAM data retained) “not instantly accessible” mode.
- Turn off power to unused sections of memory.
- Turn off power to the entire device except an external “wake-up” pin.
Each of these methods has consequences. The most significant consequence is that it can take up to several 10s of microseconds to recover or respond to an IRQ or event. Sometimes this is tolerable, other times it is not.
Most MCUs have some sort of “sleep modes”. These sleep modes invoke some combination of the methods above. Typically these modes progressively turn off clocks/power to sets of major functions of the device (ie. [CPU], [CPU+Main Logic], [CPU+Main Logic+Clock Gen], etc.). These sleep modes don’t generally address Active Mode clock speed and core voltage or turning off clocks and/or power to unused parts of the device. All of this is also passive - the application has to manage power on its own by setting each of these parameters when appropriate. This leads to non-portable application code - power management has to be implemented in the application for each specific MCU.
Understanding how the sleep modes and power reduction methods are implemented for each MCU, how all of the methods interact and affect a specific MCU, and how to architect the power management into the application is an arduous task that the application programmer shouldn’t have to go through.
Therefore the new power management api was born. A specific implementation called pm_layered
is provided to automatically switch to sleep modes. The pm_layered
module will be explained in the next sections.
The pm api provides three functions: pm_set_lowest
, pm_off
and pm_reboot
. As the name suggests, pm_off
powers down the node and stops all further operation. pm_reboot
initiates a reboot.
Finally, pm_set_lowest
is responsible for choosing the minimal applicable power level.
The implementation is not using any weak definitions. Furthermore, we use defines to determine if the cpu provides their own implementations using #define PROVIDES_PM_...
When used, the pm_layered
module superseeds the default pm
api, by introducing up to 4 different power modes that can be blocked or unblocked. It is up to the developer to choose the right number of these modes, as there might be more than 4 levels.
The core design element in the CPU’s power management system in RIOT is the so-called idle thread.
This thread is created during system startup and is scheduled when no other thread needs to run, i.e. when the CPU is idle.
pm_layered
is built around a single function, which triggers the CPU to go into the lowest possible power state (sleep mode).
Calling this function is the only task the idle thread performs.
This mechanism works transparently for user applications and other system modules, as follows.
pm_layered
implements the default mechanism which determines the lowest possible power state.
This module is shared by various CPU implementations.
The pm_layered
module uses a simple consensus mechanism (code-named Cascade), distributed between CPU peripherals and user threads.
Cascade is based on a strict hierarchy of power mode levels, that are defined on a per-CPU basis.
A lower power level means less power consumption.
The hierarchy is such that, if power level N is blocked, all power levels M ≤ N are implicitly also blocked, thus preventing the CPU to switch to any power level M ≤ N.
When using the pm_layered module, peripheral drivers or application code can each independently (un)block power modes that are (in)appropriate to run on.
For example, a UART driver can block all deep sleep CPU states, as these would prevent the UART peripheral to work correctly.
The UART driver would keep the deep sleep power state blocked while the UART peripheral is enabled, and unblock the power state once the peripheral is disabled.
Independently, when scheduled, the idle thread switches the CPU to the lowest power mode currently unblocked in pm_layered
.
The interface consists of three functions defined in drivers/include/periph/pm.h
. An additional module pm_layered
allows for an automatic usage of pre-defined power levels on the CPU level. In this case, an additional function pm_set
is required to be implemented by the CPU.
pm_layered shares the same idea of pm, to function as a fallback and defines in the form of PROVIDES_PM_???
.
CPUs and Families can define specific implementations.
A good example is the stm32_common and cortexm_common platform. The stm32_common CPU is providing pm_set and therefore is using pm_layered.