BPM
module reference#include <bpm.h>Makefile :
bpm
is already included by default
The BPM (Backup Power Manager) manages the low-power and wake up related configuration. It is primarily used to change between low-power modes using the power scaling and power save mode settings.
Power scaling is used to customize the power supply and bus speed settings to balance between high performance and low power consumption according to the current execution environment. Most importantly, this will affect the maximum CPU frequency.
There are 3 power scaling settings :
Power save modes are used to reduce the microcontroller power consumption when there is nothing to do. There are 8 power save modes :
Core::sleep()
, and a low-power mode can return to RUN when an interrupt is triggered, depending on the mode:
PM::enableWakeUpSource()
or BPM::enableBackupWakeUpSource()
can wake from WAIT and RETENTION modesBPM::enableBackupWakeUpSource()
can wake from BACKUP mode (mostly external interrupts with the EIC module and the AST and WDT timers)Any combination of power scaling and power save modes is available.
For more details, consult the §6 Low Power Techniques chapter in the datasheet, especially the useful §6.1.5 Power Save Mode Summary Table.
Core::sleep()
and shouldn't be called by the user.BackupWakeUpCause
enum for more details.BackupWakeUpSource
enum for the list of available sources.BackupWakeUpSource
enum for the list of available sources.EIC
module.Most of the features are implemented and there isn't much to be customized.
#ifndef _BPM_H_ #define _BPM_H_ #include <stdint.h> #include "core.h" // Backup Power Manager // This module manages power-saving features // TODO : finish this module namespace BPM { // Peripheral memory space base address const uint32_t BASE = 0x400F0000; // Registers addresses const uint32_t OFFSET_IER = 0x000; // Interrupt Enable Register const uint32_t OFFSET_IDR = 0x004; // Interrupt Disable Register const uint32_t OFFSET_IMR = 0x008; // Interrupt Mask Register const uint32_t OFFSET_ISR = 0x00C; // Interrupt Status Register const uint32_t OFFSET_ICR = 0x010; // Interrupt Clear Register const uint32_t OFFSET_SR = 0x014; // Status Register const uint32_t OFFSET_UNLOCK = 0x018; // Unlock Register const uint32_t OFFSET_PMCON = 0x01C; // Power Mode Control Register const uint32_t OFFSET_BKUPWCAUSE = 0x028; // Backup Wake up Cause Register const uint32_t OFFSET_BKUPWEN = 0x02C; // Backup Wake up Enable Register const uint32_t OFFSET_BKUPPMUX = 0x030; // Backup Pin Muxing Register const uint32_t OFFSET_IORET = 0x034; // Input Output Retention Register // Subregisters const uint32_t SR_PSOK = 0; const uint32_t SR_AE = 1; const uint32_t UNLOCK_ADDR = 0; const uint32_t UNLOCK_KEY = 0xAA << 24; const uint32_t PMCON_PS = 0; const uint32_t PMCON_PSCREQ = 2; const uint32_t PMCON_PSCM = 3; // Undocumented bit : set to 1 to make a no-halt change const uint32_t PMCON_BKUP = 8; const uint32_t PMCON_RET = 9; const uint32_t PMCON_SLEEP = 12; const uint32_t PMCON_CK32S = 16; const uint32_t PMCON_FASTWKUP = 24; // Error codes const Error::Code ERR_UNKNOWN_BACKUP_WAKEUP_CAUSE = 0x0001; // Constants enum class PowerScaling { PS0 = 0, PS1 = 1, PS2 = 2 }; enum class BackupWakeUpCause { EIC = 0, AST = 1, WDT = 2, BOD33 = 3, BOD18 = 4, PICOUART = 5, }; // Some peripherals can wake up the chip from backup mode // See enableBackupWakeUpSource() for more details enum class BackupWakeUpSource { EIC = 0, AST = 1, WDT = 2, BOD33 = 3, BOD18 = 4, PICOUART = 5, }; // Source for the reference 32KHz (and 1KHz) clocks, // used by the AST among others enum class CLK32KSource { OSC32K, // External crystal RC32K, // Internal RC }; const int N_INTERRUPTS = 1; enum class Interrupt { PSOK = 0, }; // Module API // Power scaling void setPowerScaling(PowerScaling ps); PowerScaling currentPowerScaling(); // Power save modes void setSleepMode(Core::SleepMode mode); BackupWakeUpCause backupWakeUpCause(); void enableBackupWakeUpSource(BackupWakeUpSource src); void disableBackupWakeUpSource(BackupWakeUpSource src); void disableBackupWakeUpSources(); void enableBackupPin(unsigned int eicChannel); void disableBackupPin(unsigned int eicChannel); // 32KHz clock selection void set32KHzClockSource(CLK32KSource source); // Interrupts void enableInterrupt(void (*handler)(), Interrupt interrupt=Interrupt::PSOK); void disableInterrupt(Interrupt interrupt=Interrupt::PSOK); } #endif
#include "bpm.h" #include "pm.h" #include "eic.h" #include "flash.h" #include "error.h" namespace BPM { // Current power scaling setting PowerScaling _currentPS = PowerScaling::PS0; // Interrupt handlers extern uint8_t INTERRUPT_PRIORITY; uint32_t _interruptHandlers[N_INTERRUPTS]; const int _interruptBits[N_INTERRUPTS] = {SR_PSOK}; void interruptHandlerWrapper(); void setPowerScaling(PowerScaling ps) { Core::stashInterrupts(); // Enable Flash High Speed mode if entering PS2 if (ps == PowerScaling::PS2) { Flash::enableHighSpeedMode(); } // Get the value of PMCON without PS uint32_t pmcon = (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) & ~(uint32_t)(0b11 << PMCON_PS); // Unlock the PMCON register, which is locked by default as a safety mesure (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK)) = UNLOCK_KEY // KEY : Magic word (see datasheet) | OFFSET_PMCON; // ADDR : unlock PMCON // Select the power scaling register and request a Power Scaling Change (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) = pmcon | static_cast<int>(ps) << PMCON_PS // PS : select Power Scaling mode | 1 << PMCON_PSCREQ // PSCREQ : Power Scaling Change Request | 1 << PMCON_PSCM; // PSCM : Power Scaling Change Request // Wait for the Power Scaling OK flag while (!(*(volatile uint32_t*)(BASE + OFFSET_SR) & (1 << SR_PSOK))); // Disable Flash High Speed mode if exiting PS2 if (_currentPS == PowerScaling::PS2) { Flash::disableHighSpeedMode(); } // Save the current setting _currentPS = ps; Core::applyStashedInterrupts(); } PowerScaling currentPowerScaling() { // Return the current power scaling setting return _currentPS; } void setSleepMode(Core::SleepMode mode) { // Read PMCON (Power Mode Control Register) and reset sleep-related fields uint32_t pmcon = (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) & 0xFFFF00FF; // Configure mode switch (mode) { case Core::SleepMode::SLEEP0: case Core::SleepMode::SLEEP1: case Core::SleepMode::SLEEP2: case Core::SleepMode::SLEEP3: pmcon |= (static_cast<int>(mode) - static_cast<int>(Core::SleepMode::SLEEP0)) << PMCON_SLEEP; break; case Core::SleepMode::WAIT: // SCR.SLEEPDEEP is already set in Core::sleep() break; case Core::SleepMode::RETENTION: pmcon |= 1 << PMCON_RET; break; case Core::SleepMode::BACKUP: pmcon |= 1 << PMCON_BKUP; break; } // Unlock the PMCON register, which is locked by default as a safety mesure (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK)) = UNLOCK_KEY // KEY : Magic word (see datasheet) | OFFSET_PMCON; // ADDR : unlock PMCON // Write PMCON (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) = pmcon; } // Returns the cause of the last backup wake up BackupWakeUpCause backupWakeUpCause() { uint32_t bkupwcause = (*(volatile uint32_t*)(BASE + OFFSET_BKUPWCAUSE)); for (int i = 0; i < 32; i++) { if (bkupwcause & 1 << i) { return static_cast<BackupWakeUpCause>(i); } } Error::happened(Error::Module::BPM, ERR_UNKNOWN_BACKUP_WAKEUP_CAUSE, Error::Severity::WARNING); return BackupWakeUpCause::EIC; // Default value that should not happen } void enableBackupWakeUpSource(BackupWakeUpSource src) { // BKUPWEN (Backup Wake Up Enable Register) : set the corresponding bit (*(volatile uint32_t*)(BASE + OFFSET_BKUPWEN)) |= 1 << static_cast<int>(src); } void disableBackupWakeUpSource(BackupWakeUpSource src) { // BKUPWEN (Backup Wake Up Enable Register) : clear the corresponding bit (*(volatile uint32_t*)(BASE + OFFSET_BKUPWEN)) &= ~(uint32_t)(1 << static_cast<int>(src)); } void disableBackupWakeUpSources() { // BKUPWEN (Backup Wake Up Enable Register) : clear the register (*(volatile uint32_t*)(BASE + OFFSET_BKUPWEN)) = 0; } void enableBackupPin(unsigned int eicChannel) { if (eicChannel < EIC::N_CHANNELS) { (*(volatile uint32_t*)(BASE + OFFSET_BKUPPMUX)) |= 1 << static_cast<int>(eicChannel); } } void disableBackupPin(unsigned int eicChannel) { if (eicChannel < EIC::N_CHANNELS) { (*(volatile uint32_t*)(BASE + OFFSET_BKUPPMUX)) &= ~(uint32_t)(1 << static_cast<int>(eicChannel)); } } void set32KHzClockSource(CLK32KSource source) { // Read PMCON (Power Mode Control Register) and reset the CK32S field uint32_t pmcon = (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) & ~(uint32_t)(1 << PMCON_CK32S); // Select the source if (source == CLK32KSource::RC32K) { pmcon |= 1 << PMCON_CK32S; } // Unlock the PMCON register, which is locked by default as a safety mesure (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK)) = UNLOCK_KEY // KEY : Magic word (see datasheet) | OFFSET_PMCON; // ADDR : unlock PMCON // Write PMCON back (*(volatile uint32_t*)(BASE + OFFSET_PMCON)) = pmcon; } void enableInterrupt(void (*handler)(), Interrupt interrupt) { // Save the user handler _interruptHandlers[static_cast<int>(interrupt)] = (uint32_t)handler; // IER (Interrupt Enable Register) : enable the requested interrupt (PSOK by default) (*(volatile uint32_t*)(BASE + OFFSET_IER)) = 1 << _interruptBits[static_cast<int>(interrupt)]; // Set the handler and enable the module interrupt at the Core level Core::setInterruptHandler(Core::Interrupt::BPM, interruptHandlerWrapper); Core::enableInterrupt(Core::Interrupt::BPM, INTERRUPT_PRIORITY); } void disableInterrupt(Interrupt interrupt) { // IDR (Interrupt Disable Register) : disable the requested interrupt (PSOK by default) (*(volatile uint32_t*)(BASE + OFFSET_IDR)) = 1 << _interruptBits[static_cast<int>(interrupt)]; // If no interrupt is enabled anymore, disable the module interrupt at the Core level if ((*(volatile uint32_t*)(BASE + OFFSET_IMR)) == 0) { Core::disableInterrupt(Core::Interrupt::BPM); } } void interruptHandlerWrapper() { // Call the user handler of every interrupt that is enabled and pending for (int i = 0; i < N_INTERRUPTS; i++) { if ((*(volatile uint32_t*)(BASE + OFFSET_IMR)) & (1 << _interruptBits[i]) // Interrupt is enabled && (*(volatile uint32_t*)(BASE + OFFSET_ISR)) & (1 << _interruptBits[i])) { // Interrupt is pending void (*handler)() = (void (*)())_interruptHandlers[i]; if (handler != nullptr) { handler(); } // Clear the interrupt by reading ISR (*(volatile uint32_t*)(BASE + OFFSET_ICR)) = 1 << _interruptBits[i]; } } } }