BPM module reference

Header :
#include <bpm.h>
Makefile : bpm is already included by default

Description

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 :

  • PS0 is the default, balanced setting, allowing a CPU maximum frequency of 36MHz
  • PS1 is the low-power setting, with a CPU maximum frequency of 12MHz (writing to Flash is also not possible)
  • PS2 is the high-performance setting, which allows the CPU to run at the full 48MHz

Power save modes are used to reduce the microcontroller power consumption when there is nothing to do. There are 8 power save modes :

  • RUN, the default, where the microcontroller is fully active
  • SLEEP0 through SLEEP3 turn off the CPU clock and some other clocks (see §6.1.1 SLEEP mode in the datasheet for more details)
  • WAIT and RETENTION are similar, all the clocks in the Core domain (which is everything except the Backup domain) are stopped but data, configuration and execution state are retained
  • BACKUP is the setting with the lowest power consumption possible : the Core domain is turned off (data, configuration and execution state are lost) and only the 32kHz clocks are still running, if enabled
The microcontroller can enter any low-power mode using Core::sleep(), and a low-power mode can return to RUN when an interrupt is triggered, depending on the mode:
  • any interrupt can wake the CPU from any SLEEP mode
  • only the interrupts that have been enabled using PM::enableWakeUpSource() or BPM::enableBackupWakeUpSource() can wake from WAIT and RETENTION modes
  • only the interrupts that have been enabled using BPM::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.

API

Power scaling

void setPowerScaling(PowerScaling ps)
Switch to the specified power scaling. When switching between PS1 and PS2, always come back to PS0 first.
PowerScaling currentPowerScaling()
Return the current power scaling setting.

Power save modes

void setSleepMode(Core::SleepMode mode)
Set up the given sleep mode in the BPM. Some sleep-mode-related settings are external to the BPM, and this function doesn't actually switch the mode. It is called by Core::sleep() and shouldn't be called by the user.
BackupWakeUpCause backupWakeUpCause()
Return the cause of the last wake up from backup mode. See the BackupWakeUpCause enum for more details.
void enableBackupWakeUpSource(BackupWakeUpSource src)
Allow the specified source to wake up the microcontroller from backup mode. See the BackupWakeUpSource enum for the list of available sources.
void disableBackupWakeUpSource(BackupWakeUpSource src)
Disallow the specified source to wake up the microcontroller from backup mode. See the BackupWakeUpSource enum for the list of available sources.
void disableBackupWakeUpSources()
Disallow all sources to wake up the microcontroller from backup mode.
void enableBackupPin(unsigned int eicChannel)
Route an EIC pin signal through the Backup domain. This is used internally by the EIC module.
void disableBackupPin(unsigned int eicChannel)
Route an EIC pin signal through the normal path.

Interrupts

void enableInterrupt(void (*handler)(), Interrupt interrupt=Interrupt::PSOK)
Enable the specified interrupt to call the specified handler.
void disableInterrupt(Interrupt interrupt=Interrupt::PSOK)
Disable the specified interrupt.

Hacking

Most of the features are implemented and there isn't much to be customized.

Code

Header

#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

Module

#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];
            }
        }
    }

}