PM module reference

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

Description

The PM (Power Manager) has two main purposes : routing clocks from clock sources to peripherals, and managing the reset controller of the device.

API

General-purpose

void setMainClockSource(MainClockSource clockSource, unsigned long cpudiv=0)
The main clock is used by the CPU and the peripheral buses and can be connected to any of the clock sources listed in MainClockSource. If cpudiv is greater than 0, the CPU frequency will be equal to the main clock frequency divided by cpudiv.
unsigned long getMainClockFrequency()
Return the frequency of the main clock.
unsigned long getCPUClockFrequency()
Return the frequency of the CPU clock.
unsigned long getModuleClockFrequency(uint8_t peripheral)
Return the frequency of the specified peripheral. See the CLK_* constants in the header for more details.
void enablePeripheralClock(uint8_t peripheral)
Enable the clock for the specified peripheral. See the CLK_* constants in the header for more details.
void enablePBADivClock(uint8_t pow)
Enable the divided PBA peripheral clock \(2^{pow}\), with pow between 1 and 7.

Wake-up and reset

ResetCause resetCause()
Return the cause of the last reset among :
  • POR : power-on reset (default)
  • BOD : brown-out detector, there was probably a glitch in the power supply
  • EXT : external reset using the dedicated RESET_N pin
  • WDT : watchdog timer reset
  • BKUP : reset from backup (low-power) mode when a wake up source occurs
  • SYSRESETREQ : reset using the Core::reset() function
  • POR33 : 3.3V power-on reset
  • BOD33 : 3.3V brown-out detector
void enableWakeUpSource(WakeUpSource src)
Enable a wake-up source for a peripheral among :
  • TWIS0
  • TWIS1
  • USBC
  • PSOK
  • BOD18
  • BOD33
  • PICOUART
  • LCDA
void disableWakeUpSource(WakeUpSource src)
Disable a wake-up source for a peripheral.
void disableWakeUpSources()
Disable all wake-up sources.

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

This module doesn't cover all the features offered by the peripheral. The different clocks could be differentiated and run at different frequencies in order to reduce power consumption. There are a few more settings that could be used or customized, such as the Peripheral Power Control Register or the Fast Sleep Register.

Code

Header

#ifndef _PM_H_
#define _PM_H_

#include <stdint.h>

// Power Manager
// This module controls the clock gating from clock sources to peripherals
// as well as the reset logic.
namespace PM {

    // Peripheral memory space base address
    const uint32_t BASE = 0x400E0000;


    // Registers addresses
    const uint32_t OFFSET_MCCTRL =     0x000; // Main Clock Control
    const uint32_t OFFSET_CPUSEL =     0x004; // CPU Clock Select
    const uint32_t OFFSET_PBASEL =     0x00C; // PBA Clock Select
    const uint32_t OFFSET_PBBSEL =     0x010; // PBB Clock Select
    const uint32_t OFFSET_PBCSEL =     0x014; // PBC Clock Select
    const uint32_t OFFSET_PBDSEL =     0x018; // PBD Clock Select
    const uint32_t OFFSET_CPUMASK =    0x020; // CPU Mask
    const uint32_t OFFSET_HSBMASK =    0x024; // HSB Mask
    const uint32_t OFFSET_PBAMASK =    0x028; // PBA Mask
    const uint32_t OFFSET_PBBMASK =    0x02C; // PBB Mask
    const uint32_t OFFSET_PBCMASK =    0x030; // PBC Mask
    const uint32_t OFFSET_PBDMASK =    0x034; // PBD Mask
    const uint32_t OFFSET_PBADIVMASK = 0x040; // PBA Divided Mask
    const uint32_t OFFSET_CFDCTRL =    0x054; // Clock Failure Detector Control
    const uint32_t OFFSET_UNLOCK =     0x058; // Unlock Register
    const uint32_t OFFSET_IER =        0x0C0; // Interrupt Enable Register
    const uint32_t OFFSET_IDR =        0x0C4; // Interrupt Disable Register
    const uint32_t OFFSET_IMR =        0x0C8; // Interrupt Mask Register
    const uint32_t OFFSET_ISR =        0x0CC; // Interrupt Status Register
    const uint32_t OFFSET_ICR =        0x0D0; // Interrupt Clear Register
    const uint32_t OFFSET_SR =         0x0D4; // Status Register
    const uint32_t OFFSET_PPCR =       0x160; // Peripheral Power Control Register
    const uint32_t OFFSET_RCAUSE =     0x180; // Reset Cause Register
    const uint32_t OFFSET_WCAUSE =     0x184; // Wake Cause Register
    const uint32_t OFFSET_AWEN =       0x188; // Asynchronous Wake Enable
    const uint32_t OFFSET_PROTCTRL =   0x18C; // Protection Control Register
    const uint32_t OFFSET_FASTSLEEP =  0x194; // Fast Sleep Register
    const uint32_t OFFSET_CONFIG =     0x3F8; // Configuration Register


    // Subregisters
    const uint32_t CPUSEL_CPUSEL = 0;
    const uint32_t CPUSEL_CPUDIV = 7;
    const uint32_t MCCTRL_MCSEL = 0;
    const uint32_t SR_CFD = 0;
    const uint32_t SR_CKRDY = 5;
    const uint32_t SR_WAKE = 8;


    // Constants
    const uint32_t UNLOCK_KEY = 0xAA << 24;
    const uint32_t MCCTRL_MCSEL_RCSYS = 0;
    const uint32_t MCCTRL_MCSEL_OSC0 = 1;
    const uint32_t MCCTRL_MCSEL_PLL = 2;
    const uint32_t MCCTRL_MCSEL_DFLL = 3;
    const uint32_t MCCTRL_MCSEL_RC80M = 4;
    const uint32_t MCCTRL_MCSEL_RCFAST = 5;
    const uint32_t MCCTRL_MCSEL_RC1M = 6;

    const uint32_t HSBMASK = 0;
    const uint32_t HSBMASK_PDCA = 0;
    const uint32_t HSBMASK_FLASHCALW = 1;
    const uint32_t HSBMASK_FLASHCALW_PICOCACHE = 2;
    const uint32_t HSBMASK_USBC = 3;
    const uint32_t HSBMASK_CRCCU = 4;
    const uint32_t HSBMASK_AESA = 9;
    const uint32_t PBAMASK = 32;
    const uint32_t PBAMASK_IISC = 0;
    const uint32_t PBAMASK_SPI = 1;
    const uint32_t PBAMASK_TC0 = 2;
    const uint32_t PBAMASK_TC1 = 3;
    const uint32_t PBAMASK_TWIM0 = 4;
    const uint32_t PBAMASK_TWIS0 = 5;
    const uint32_t PBAMASK_TWIM1 = 6;
    const uint32_t PBAMASK_TWIS1 = 7;
    const uint32_t PBAMASK_USART0 = 8;
    const uint32_t PBAMASK_USART1 = 9;
    const uint32_t PBAMASK_USART2 = 10;
    const uint32_t PBAMASK_USART3 = 11;
    const uint32_t PBAMASK_ADCIFE = 12;
    const uint32_t PBAMASK_DACC = 13;
    const uint32_t PBAMASK_ACIFC = 14;
    const uint32_t PBAMASK_GLOC = 15;
    const uint32_t PBAMASK_ABDACB = 16;
    const uint32_t PBAMASK_TRNG = 17;
    const uint32_t PBAMASK_PARC = 18;
    const uint32_t PBAMASK_CATB = 19;
    const uint32_t PBAMASK_TWIM2 = 21;
    const uint32_t PBAMASK_TWIM3 = 22;
    const uint32_t PBAMASK_LCDCA = 23;
    const uint32_t PBBMASK = 64;
    const uint32_t PBBMASK_HRAMC1 = 1;
    const uint32_t PBBMASK_HMATRIX = 2;
    const uint32_t PBBMASK_PDCA = 3;
    const uint32_t PBBMASK_CRCCU = 4;
    const uint32_t PBBMASK_USBC = 5;
    const uint32_t PBBMASK_PEVC = 6;

    // Default clock frequencies
    const unsigned long RCSYS_FREQUENCY = 115000UL;
    const unsigned long RC80M_FREQUENCY = 80000000UL;
    const unsigned long PBA_MAX_FREQUENCY = 8000000UL;

    // Peripheral clocks
    const int CLK_DMA = HSBMASK + HSBMASK_PDCA;
    const int CLK_USB_HSB = HSBMASK + HSBMASK_USBC; // The USB has 2 clocks
    const int CLK_CRC_HSB = HSBMASK + HSBMASK_CRCCU; // The CRC has 2 clocks
    const int CLK_AES = HSBMASK + HSBMASK_AESA;
    const int CLK_IIS = PBAMASK + PBAMASK_IISC;
    const int CLK_SPI = PBAMASK + PBAMASK_SPI;
    const int CLK_TC0 = PBAMASK + PBAMASK_TC0;
    const int CLK_TC1 = PBAMASK + PBAMASK_TC1;
    const int CLK_I2CM0 = PBAMASK + PBAMASK_TWIM0;
    const int CLK_I2CS0 = PBAMASK + PBAMASK_TWIS0;
    const int CLK_I2CM1 = PBAMASK + PBAMASK_TWIM1;
    const int CLK_I2CS1 = PBAMASK + PBAMASK_TWIS1;
    const int CLK_I2CM2 = PBAMASK + PBAMASK_TWIM2;
    const int CLK_I2CM3 = PBAMASK + PBAMASK_TWIM3;
    const int CLK_USART0 = PBAMASK + PBAMASK_USART0;
    const int CLK_USART1 = PBAMASK + PBAMASK_USART1;
    const int CLK_USART2 = PBAMASK + PBAMASK_USART2;
    const int CLK_USART3 = PBAMASK + PBAMASK_USART3;
    const int CLK_ADC = PBAMASK + PBAMASK_ADCIFE;
    const int CLK_DAC = PBAMASK + PBAMASK_DACC;
    const int CLK_GLOC = PBAMASK + PBAMASK_GLOC;
    const int CLK_TRNG = PBAMASK + PBAMASK_TRNG;
    const int CLK_CRC = PBBMASK + PBBMASK_CRCCU;
    const int CLK_USB = PBBMASK + PBBMASK_USBC;


    // The main clock is used by the CPU and the peripheral buses
    // and can be connected to any of these clock sources
    enum class MainClockSource {
        RCSYS = MCCTRL_MCSEL_RCSYS,
        OSC0 = MCCTRL_MCSEL_OSC0,
        PLL = MCCTRL_MCSEL_PLL,
        DFLL = MCCTRL_MCSEL_DFLL,
        RC80M = MCCTRL_MCSEL_RC80M,
        RCFAST = MCCTRL_MCSEL_RCFAST,
    };

    // Some peripherals can wake up the chip from sleep mode
    // See enableWakeUpSource() for more details
    enum class WakeUpSource {
        I2C_SLAVE_0 = 0,
        I2C_SLAVE_1 = 1,
        USBC = 2,
        PSOK = 3,
        BOD18 = 4,
        BOD33 = 5,
        PICOUART = 6,
        LCDA = 7
    };

    // The PM can be used to determine the cause of the last reset
    // See resetCause() for more details
    enum class ResetCause {
        UNKNOWN = -1,
        POR = 0,    // Power-on reset (core power supply)
        BOD = 1,    // Brown-out reset (core power supply)
        EXT = 2,    // External reset pin
        WDT = 3,    // Watchdog reset
        BKUP = 6,   // Backup reset
        SYSRESETREQ = 8, // Software reset
        POR33 = 10, // Power-on reset (IO 3.3V supply)
        BOD33 = 13  // Brown-out reset (IO 3.3V supply)
    };

    enum class WakeUpCause {
        UNKNOWN = -1,
        I2C_SLAVE_0 = 0,
        I2C_SLAVE_1 = 1,
        USB = 2,
        PSOK = 3,
        BOD18 = 4,
        BOD33 = 5,
        PICOUART = 6,
        LCD = 7,
        EIC = 16,
        AST = 17,
    };

    const int N_INTERRUPTS = 3;
    enum class Interrupt {
        CLOCK_FAILURE = 0,
        CLOCK_READY = 5,
        WAKE = 8,
    };

    // Clock frequency values used by inline functions below
    extern unsigned long _mainClockFrequency;
    extern unsigned long _cpuClockFrequency;


    // Module API

    // Clock management
    void setMainClockSource(MainClockSource clockSource, unsigned long cpudiv=0);
    inline unsigned long getMainClockFrequency() { return _mainClockFrequency; }
    inline unsigned long getCPUClockFrequency() { return _cpuClockFrequency; }
    unsigned long getModuleClockFrequency(uint8_t peripheral);
    void enablePeripheralClock(uint8_t peripheral, bool enabled=true);
    void disablePeripheralClock(uint8_t peripheral);
    void enablePBADivClock(uint8_t pow);

    // Wake-up and reset
    ResetCause resetCause();
    WakeUpCause wakeUpCause();
    inline volatile bool isWakeUpCauseUnknown() { return (*(volatile uint32_t*)(BASE + OFFSET_WCAUSE)) == 0; }
    void enableWakeUpSource(WakeUpSource src);
    void disableWakeUpSource(WakeUpSource src);
    void disableWakeUpSources();

    // Interrupts
    void enableInterrupt(void (*handler)(), Interrupt interrupt=Interrupt::WAKE);
    void disableInterrupt(Interrupt interrupt=Interrupt::WAKE);

}


#endif

Module

#include "pm.h"
#include "scif.h"
#include "core.h"
#include "error.h"

namespace PM {

    // Clock frequencies
    unsigned long _mainClockFrequency = RCSYS_FREQUENCY;
    unsigned long _cpuClockFrequency = RCSYS_FREQUENCY;
    unsigned long _hsbClockFrequency = RCSYS_FREQUENCY;
    unsigned long _pbaClockFrequency = RCSYS_FREQUENCY;

    // Interrupt handlers
    extern uint8_t INTERRUPT_PRIORITY;
    uint32_t _interruptHandlers[N_INTERRUPTS];
    const int _interruptBits[N_INTERRUPTS] = {SR_CFD, SR_CKRDY, SR_WAKE};
    void interruptHandlerWrapper();
    

    // The main clock is used by the CPU and the peripheral buses and can be connected
    // to any of the clock sources listed in MainClockSource
    void setMainClockSource(MainClockSource clockSource, unsigned long cpudiv) {
        if (cpudiv >= 1) {
            // Unlock the CPUSEL register
            (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK))
                    = UNLOCK_KEY           // KEY : Magic word (see datasheet)
                    | OFFSET_CPUSEL;        // ADDR : unlock CPUSEL

            // Configure the CPU clock divider
            if (cpudiv > 7) {
                cpudiv = 7;
            }
            (*(volatile uint32_t*)(BASE + OFFSET_CPUSEL))
                    = (cpudiv - 1) << CPUSEL_CPUSEL  // CPUSEL : select divider factor
                    | 1 << CPUSEL_CPUDIV;         // CPUDIV : enable divider

            // Wait for the divider to be ready
            while (!((*(volatile uint32_t*)(BASE + OFFSET_SR)) & (1 << SR_CKRDY)));
        }

        // Unlock the MCCTRL register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY               // KEY : Magic word (see datasheet)
                | OFFSET_MCCTRL;           // ADDR : unlock MCCTRL

        // Change the main clock source
        (*(volatile uint32_t*)(BASE + OFFSET_MCCTRL))
                = static_cast<int>(clockSource) << MCCTRL_MCSEL; // MCSEL : select clock source

        // Save the frequency
        switch (clockSource) {
            case MainClockSource::RCSYS:
                _mainClockFrequency = RCSYS_FREQUENCY;
                break;
            
            case MainClockSource::OSC0:
                _mainClockFrequency = SCIF::getOSC0Frequency();
                break;
            
            case MainClockSource::PLL:
                _mainClockFrequency = SCIF::getPLLFrequency();
                break;
            
            case MainClockSource::DFLL:
                _mainClockFrequency = SCIF::getDFLLFrequency();
                break;

            case MainClockSource::RCFAST:
                _mainClockFrequency = SCIF::getRCFASTFrequency();
                break;

            case MainClockSource::RC80M:
                _mainClockFrequency = RC80M_FREQUENCY;
                break;
        }
        _cpuClockFrequency = _mainClockFrequency / (1 << cpudiv);
        _hsbClockFrequency = _mainClockFrequency;
        _pbaClockFrequency = _mainClockFrequency;
    }

    unsigned long getModuleClockFrequency(uint8_t peripheral) {
        if (peripheral >= HSBMASK && peripheral < PBAMASK) {
            return _hsbClockFrequency;
        } else if (peripheral >= PBAMASK && peripheral < PBBMASK) {
            return _pbaClockFrequency;
        }
        return 0;
    }

    void enablePeripheralClock(uint8_t peripheral, bool enabled) {
        // Select the correct register
        int offset = 0;
        if (peripheral >= HSBMASK && peripheral < PBAMASK) {
            offset = OFFSET_HSBMASK;
            peripheral -= HSBMASK;
        } else if (peripheral >= PBAMASK && peripheral < PBBMASK) {
            offset = OFFSET_PBAMASK;
            peripheral -= PBAMASK;
        } else if (peripheral >= PBBMASK) {
            offset = OFFSET_PBBMASK;
            peripheral -= PBBMASK;
        }

        // Unlock the selected register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY                // KEY : Magic word (see datasheet)
                | offset;                   // ADDR : unlock the selected register

        if (enabled) {
            // Unmask the corresponding clock
            (*(volatile uint32_t*)(BASE + offset)) |= 1 << peripheral;
        } else {
            // Unmask the corresponding clock
            (*(volatile uint32_t*)(BASE + offset)) &= ~(uint32_t)(1 << peripheral);
        }
    }

    void disablePeripheralClock(uint8_t peripheral) {
        enablePeripheralClock(peripheral, false);
    }

    void enablePBADivClock(uint8_t pow) {
        // Unlock the selected register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY                // KEY : Magic word (see datasheet)
                | OFFSET_PBADIVMASK;        // ADDR : unlock PBADIVMASK

        // Check that the power of two selected is in range
        if (pow < 1 || pow > 7) {
            return;
        }

        // Unmask the corresponding divided clock
        (*(volatile uint32_t*)(BASE + OFFSET_PBADIVMASK)) |= 1 << (pow - 1);
    }


    // Returns the cause of the last reset. This is useful for example to handle faults
    // detected by the watchdog or the brown-out detectors.
    ResetCause resetCause() {
        uint32_t rcause = (*(volatile uint32_t*)(BASE + OFFSET_RCAUSE));
        if (rcause != 0) {
            for (int i = 0; i < 32; i++) {
                if (rcause & 1 << i) {
                    return static_cast<ResetCause>(i);
                }
            }
        }
        return ResetCause::UNKNOWN;
    }

    // Returns the cause of the last wake up
    WakeUpCause wakeUpCause() {
        uint32_t wcause = (*(volatile uint32_t*)(BASE + OFFSET_WCAUSE));
        if (wcause != 0) {
            for (int i = 0; i < 32; i++) {
                if (wcause & 1u << i) {
                    return static_cast<WakeUpCause>(i);
                }
            }
        }
        return WakeUpCause::UNKNOWN;
    }

    void enableWakeUpSource(WakeUpSource src) {
        // AWEN (Asynchronous Wake Up Enable Register) : set the corresponding bit
        (*(volatile uint32_t*)(BASE + OFFSET_AWEN))
            |= 1 << static_cast<int>(src);
    }

    void disableWakeUpSource(WakeUpSource src) {
        // AWEN (Asynchronous Wake Up Enable Register) : clear the corresponding bit
        (*(volatile uint32_t*)(BASE + OFFSET_AWEN))
            &= ~(uint32_t)(1 << static_cast<int>(src));
    }

    void disableWakeUpSources() {
        // AWEN (Asynchronous Wake Up Enable Register) : clear the register
        (*(volatile uint32_t*)(BASE + OFFSET_AWEN)) = 0;
    }


    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 (WAKE 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::PM, interruptHandlerWrapper);
        Core::enableInterrupt(Core::Interrupt::PM, INTERRUPT_PRIORITY);
    }

    void disableInterrupt(Interrupt interrupt) {
        // IDR (Interrupt Disable Register) : disable the requested interrupt (WAKE 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::PM);
        }
    }

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

}