Core module reference

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

Description

This module manages the internal features of the Cortex M core itself, such as interrupts, exceptions and the SysTick timer, give information about the chip model and features, and provides some helper functions, such as time() and sleep(), for which it depends on the AST module.

API

General purpose

void init()
Initialize some core features, such as the exceptions table and low-power oscillators. This function should be called before anything else in your main (for Carbide it's called inside Carbide::init()).
void reset()
Immediately reset the microcontroller by issuing a SYSRESETREQ request. After reset, PM::resetCause() will return PM::ResetCause::SYSRESETREQ.
void resetToBootloader()
Immediately reset the microcontroller into bootloader mode using a special fuse and the reset() function above.
void resetToBootloader(unsigned int delayMs)
Reset the microcontroller into bootloader mode after the given delay using a special fuse and the watchdog timer.

Microcontroller information

Package package()
Return the number of pins of this package : PCK_48PIN, PCK_64PIN or PCK_100PIN.
RAMSize ramSize()
Return the amount of RAM in this package : RAM_32K or RAM_64K.
FlashSize flashSize()
Return the amount of Flash memory in this package : FLASH_128K, FLASH_256K or FLASH_512K.
void serialNumber(uint8_t* sn)

Copy the unique 120-bit (15-byte) long serial number of the microcontroller into sn :

uint8_t sn[Core::SERIAL_NUMBER_LENGTH];
Core::serialNumber(sn);
// Use sn...

Interrupts

void setExceptionHandler(Exception exception, void (*handler)())
Define a handler to call when the specified exception is triggered. Most default handlers for errors are simple infinite loops. Exception can be
  • NMI
  • HARDFAULT
  • MEMMANAGE
  • BUSFAULT
  • USAGEFAULT
  • SVCALL
  • DEBUGMONITOR
  • PENDSV
  • SYSTICK
void setInterruptHandler(Interrupt interrupt, void (*handler)())
Define a handler to call when the specified interrupt is triggered (provided this interrupt is enabled with enableInterrupt()). See the Interrupt enumeration in the header below for the list of available interrupts.
void enableInterrupt(Interrupt interrupt, uint8_t priority)
Enable the specified interrupt channel, with the specified priority number. Lower priority number means higher priority : an interrupt can only be interrupted by another interrupt with a strictly lower priority number.
void disableInterrupt(Interrupt interrupt)
Disable the specified interrupt channel.
void enableInterrupts()
Globally enable interrupts.
void disableInterrupts()
Globally disable interrupts. This is useful for critical sections of code.
Interrupt currentInterrupt()
Inside an interrupt, this returns the current interrupt number. This can be used when a handler is mapped to multiple interrupts to determine which one was triggered.

Time and low-power

Time time()
Return the number of milliseconds since the init() function has been called. This is merely a convenient proxy for the AST::time() function.
void sleep(SleepMode mode=SleepMode::SLEEP0, unsigned long length=0, TimeUnit unit=TimeUnit::MILLISECONDS)

Put the microcontroller in the specified low-power mode. If length is greater than 0, this function will configure an alarm in the AST module to wake up the microcontroller after the specified amount of time, specified in SECONDS or MILLISECONDS. Otherwise, the microcontroller will sleep for an undefined amount of time. In any case, the microcontroller can be woken up by any event that has been configured using PM::enableWakeUpSource() or BPM::enableBackupWakeUpSource().

The maximum possible value for length is about 131000 seconds, approximately 36 hours.

When using the DMA or any module that uses it internally (e.g. all the data transfer peripherals, such as USART, I2C or SPI), make sure not to activate a sleep mode higher than SLEEP0. Otherwise, the RAM clock and Peripheral clocks will be disabled and the DMA will freeze in an undefined state.

void sleep(unsigned long length=0, TimeUnit unit=TimeUnit::MILLISECONDS)

Call the sleep() function above with the default SLEEP0 sleep mode.

The maximum possible value for length is about 131000 seconds, approximately 36 hours.

void waitMicroseconds(unsigned long length)
Actively wait for the specified amount of time, in microseconds, without using any low-power mode. Because of the function overhead, there is a minimum time possible to wait for, which is dependant on the CPU clock frequency. As an example, this minimum is about 10µs with a 12MHz CPU clock.
void enableSysTick()
Enable the SysTick system timer. The SysTick runs on the same clock as the CPU and is used as a high-precision clock for waitMicroseconds(). This function is called by init().
void disableSysTick()
Disable the SysTick system timer.

Internal exception handlers

More details can be found in the ARM Architecture Reference Manual §B1.5.1 Overview of the exceptions supported.
void handlerNMI()
Non-Maskable Interrupt exception handler, the exception with the highest priority : it can't be masked nor preempted.
void handlerHardFault()
HardFault exception handler. A HardFault exception is the most severe exception available, it is triggered when another exception has been triggered but couldn't be recovered from.
void handlerMemManage()
MemManage exception handler. A MemManage exception is triggered when trying to access a segment of memory protected by the MPU (Memory Protection Unit).
void handlerBusFault()
BusFault exception handler. A BusFault exception is triggered on a memory-related fault, such as trying to access an undefined address.
void handlerUsageFault()
UsageFault exception handler. A UsageFault exception is triggered for other faults, such as trying to execute an undefined instruction.
void handlerSVCall()
Supervisor Call exception handler. Currently unused.
void handlerDebugMonitor()
DebugMonitor exception handler. Currently unused.
void handlerPendSV()
PendSV exception handler. Currently unused.
void handlerSysTick()
SysTick exception handler. Currently unused.

Hacking

The datasheet doesn't provide many details about the registers and features managed by this module. This is because the CPU and the core features associated with it are not designed by Atmel but are "standard" designs provided by ARM (the company) itself. Many other microcontroller manufacturers use the same core (ST, Texas Instruments, ...). The datasheet is therefore a separate document published by ARM : the ARM v7-M Architecture Reference Manual.

There are lots of information in this document, but not everything is interesting for us. The most interesting chapter is B3: System Address Map :

  • B3.1: The system address map gives some important information about the different sections of memory used by the system, as well as some access rules
  • B3.2: System Control Space (SCS) describes all the interesting registers related to the Vector Table, interrupts, exceptions and the CPU itself
  • B3.3: The system timer, SysTick describes this special 24-bit timer that can be used for different purposes (the introduction of the section gives a few examples)
  • B3.4: Nested Vectored Interrupt Controller, NVIC gives more information about interrupts and their priorities
If you need advanced control or status information about the CPU and instruction execution, you should start by looking at the SCS in §B3.2 in order to see if a register provides what you need. If so, take a look at how the SCS registers are accessed in the other functions of the module, and it should be easy to implement a new function that covers your needs.

The PDBG (Peripheral Debug) register at address 0xE0042000 can be customized if necessary to freeze some peripherals when the Core is halted during a debug session.

In case you are doing advanced optimization, low-level debugging or reverse-engineering and you need more information about the machine instructions, the A7.7: Alphabetical list of ARMv7-M Thumb instructions chapter will tell you everything you need about available instructions and their encodings, just like a dictionary.

Code

Header

#ifndef _CORE_H_
#define _CORE_H_

#include <stdint.h>
#include "ast.h"
#include "error.h"

// This module manages the core features of the Cortex microcontroller, 
// such as interrupts and the SysTick. Most of the registers are defined 
// in the ARMv7-M Architecture Reference Manual.
// Doc : https://libtungsten.io/reference/core
namespace Core {

    // System Control Space (SCS) Registers
    const uint32_t CPUID = 0xE000ED00;      // CPUID Base Register
    const uint32_t ICSR = 0xE000ED04;       // Interrupt Control and State Register
    const uint32_t VTOR = 0xE000ED08;       // Vector table offset
    const uint32_t AIRCR = 0xE000ED0C;      // Application Interrupt and Reset Control Register
    const uint32_t SCR = 0xE000ED10;        // System Control Register
    const uint32_t CCR = 0xE000ED14;        // Configuration and Control Register
    const uint32_t CFSR = 0xE000ED28;       // Configurable Fault Status Register
    const uint32_t HFSR = 0xE000ED2C;       // HardFault Status Register
    const uint32_t DFSR = 0xE000ED30;       // Debug Fault Status Register
    const uint32_t MMFAR = 0xE000ED30;      // MemManage Fault Address Register
    const uint32_t BFAR = 0xE000ED38;       // BusFault Address Register

    // SysTick Registers
    const uint32_t SYST_CSR = 0xE000E010;   // SysTick Control and Status Register
    const uint32_t SYST_RVR = 0xE000E014;   // SysTick Reload Value Register
    const uint32_t SYST_CVR = 0xE000E018;   // SysTick Current Value Register
    const uint32_t SYST_CALIB = 0xE000E01C; // SysTick Calibration value register

    // Nested Vectored Interrupt Controller (NVIC) Registers
    const int N_NVIC_IMPLEMENTED = 3;
    const uint32_t NVIC_ISER0 = 0xE000E100; // Interrupt Set-Enable Register 0
    const uint32_t NVIC_ICER0 = 0xE000E180; // Interrupt Clear-Enable Register 0
    const uint32_t NVIC_ISPR0 = 0xE000E200; // Interrupt Set-Pending Register 0
    const uint32_t NVIC_ICPR0 = 0xE000E280; // Interrupt Clear-Pending Register 0
    const uint32_t NVIC_IABR0 = 0xE000E300; // Interrupt Active Bit Register 0
    const uint32_t NVIC_IPR0 = 0xE000E400;  // Interrupt Priority Register 0

    // Peripheral Debug
    const uint32_t PDBG = 0xE0042000; // Peripheral Debug Register

    // ChipID and Serial number
    const uint32_t CHIPID_CIDR = 0x400E0740;
    const uint32_t CHIPID_EXID = 0x400E0744;
    const uint32_t SERIAL_NUMBER_ADDR = 0x0080020C;
    const uint32_t SERIAL_NUMBER_LENGTH = 15; // bytes


    // Subregisters
    const uint32_t SCR_SLEEPONEXIT = 1; // Enter sleep state when no interrupt is being handled
    const uint32_t SCR_SLEEPDEEP = 2; // Select between sleep and wait/retention/backup mode
    const uint32_t SYST_CSR_ENABLE = 0; // SysTick enabled
    const uint32_t SYST_CSR_TICKINT = 1; // SysTick interrupt enabled
    const uint32_t SYST_CSR_CLKSOURCE = 2; // SysTick clock source
    const uint32_t SYST_CSR_COUNTFLAG = 6; // SysTick timer has reached 0
    const uint32_t AIRCR_SYSRESETREQ = 2; // System reset request
    const uint32_t AIRCR_VECTKEY = 0x05FA << 16; // AIRCR access key
    const uint32_t PDBG_WDT = 0; // Freeze WDT when Core is halted in debug mode
    const uint32_t PDBG_AST = 1; // Freeze AST when Core is halted in debug mode
    const uint32_t PDBG_PEVC = 2; // Freeze PEVX when Core is halted in debug mode
    const uint32_t CHIPID_CIDR_NVPSIZ = 8; // Flash size
    const uint32_t CHIPID_CIDR_SRAMSIZ = 16; // RAM size
    const uint32_t CHIPID_CIDR_EXT = 31; // Extension flag
    const uint32_t CHIPID_EXID_PACKAGE = 24; // Package type

    // Constant values
    const uint8_t CHIPID_CIDR_NVPSIZ_128K = 7;
    const uint8_t CHIPID_CIDR_NVPSIZ_256K = 9;
    const uint8_t CHIPID_CIDR_NVPSIZ_512K = 10;
    const uint8_t CHIPID_CIDR_SRAMSIZ_32K = 10;
    const uint8_t CHIPID_CIDR_SRAMSIZ_64K = 11;
    const uint8_t CHIPID_EXID_PACKAGE_48 = 2;
    const uint8_t CHIPID_EXID_PACKAGE_64 = 3;
    const uint8_t CHIPID_EXID_PACKAGE_100 = 4;


    // Exceptions
    const int N_INTERNAL_EXCEPTIONS = 16;
    enum class Exception {
        NMI = 2,
        HARDFAULT = 3,
        MEMMANAGE = 4,
        BUSFAULT = 5,
        USAGEFAULT = 6,
        SVCALL = 11,
        DEBUGMONITOR = 12,
        PENDSV = 14,
        SYSTICK = 15,
    };

    // Interrupts
    const int N_EXTERNAL_INTERRUPTS = 80;
    enum class Interrupt {
        DMA0  = 1,
        DMA1  = 2,
        DMA2  = 3,
        DMA3  = 4,
        DMA4  = 5,
        DMA5  = 6,
        DMA6  = 7,
        DMA7  = 8,
        DMA8  = 9,
        DMA9  = 10,
        DMA10  = 11,
        DMA11  = 12,
        DMA12  = 13,
        DMA13  = 14,
        DMA14  = 15,
        DMA15  = 16,
        CRCCU  = 17,
        USBC  = 18,
        PEVC_TR  = 19,
        PEVC_OV  = 20,
        AESA  = 21,
        PM  = 22,
        SCIF  = 23,
        FREQM  = 24,
        GPIO0 = 25, // PORTA
        GPIO1 = 26,
        GPIO2 = 27,
        GPIO3 = 28,
        GPIO4 = 29, // PORTB
        GPIO5 = 30,
        GPIO6 = 31,
        GPIO7 = 32,
        GPIO8 = 33, // PORTC
        GPIO9 = 34,
        GPI010 = 35,
        GPIO11 = 36,
        BPM = 37,
        BSCIF = 38,
        AST_ALARM = 39,
        AST_PER = 40,
        AST_OVF = 41,
        AST_READY = 42,
        AST_CLKREADY = 43,
        WDT = 44,
        EIC1 = 45,
        EIC2 = 46,
        EIC3 = 47,
        EIC4 = 48,
        EIC5 = 49,
        EIC6 = 50,
        EIC7 = 51,
        EIC8 = 52,
        IISC = 53,
        SPI = 54,
        TC00 = 55,
        TC01 = 56,
        TC02 = 57,
        TC10 = 58,
        TC11 = 59,
        TC12 = 60,
        TWIM0 = 61,
        TWIS0 = 62,
        TWIM1 = 63,
        TWIS1 = 64,
        USART0 = 65,
        USART1 = 66,
        USART2 = 67,
        USART3 = 68,
        ADCIFE = 69,
        DACC = 70,
        ACIFC = 71,
        ABDACB = 72,
        TRNG = 73,
        PARC = 74,
        CATB = 75,
        TWIM2 = 77,
        TWIM3 = 78,
        LCDCA = 79,
    };

    enum class TimeUnit {
        SECONDS,
        MILLISECONDS,
    };

    // See the doc for more details about the different sleep modes
    enum class SleepMode {
        SLEEP0,
        SLEEP1,
        SLEEP2,
        SLEEP3,
        WAIT,
        RETENTION,
        BACKUP,
    };

    // Chip information
    enum class Package {
        PCK_48PIN,
        PCK_64PIN,
        PCK_100PIN,
        UNKNOWN
    };

    enum class RAMSize {
        RAM_32K,
        RAM_64K,
        UNKNOWN
    };

    enum class FlashSize {
        FLASH_128K,
        FLASH_256K,
        FLASH_512K,
        UNKNOWN
    };

    using Time = AST::Time;


    // General purpose functions
    void init();
    void reset();
    void resetToBootloader();
    void resetToBootloader(unsigned int delayMs);

    // Microcontroller information
    Package package();
    RAMSize ramSize();
    FlashSize flashSize();
    void serialNumber(uint8_t* sn);

    // Interrupts
    void setExceptionHandler(Exception exception, void (*handler)());
    void setInterruptHandler(Interrupt interrupt, void (*handler)());
    void enableInterrupt(Interrupt interrupt, uint8_t priority);
    void disableInterrupt(Interrupt interrupt);
    inline void enableInterrupts() { __asm__("CPSIE I"); } // Change Program State Interrupt Enable
    inline void disableInterrupts() { __asm__("CPSID I"); } // Change Program State Interrupt Disable
    void setInterruptPriority(Interrupt interrupt, uint8_t priority);
    void stashInterrupts();
    void applyStashedInterrupts();
    Interrupt currentInterrupt();

    // Time and power related functions
    inline Time time() { return AST::time(); }
    void sleep(unsigned long length, TimeUnit unit=TimeUnit::MILLISECONDS);
    void sleep(SleepMode mode=SleepMode::SLEEP0, unsigned long length=0, TimeUnit unit=TimeUnit::MILLISECONDS);
    void waitMicroseconds(unsigned long length);
    void enableSysTick();
    void disableSysTick();

    // Default exception handlers
    void handlerNMI();
    void handlerHardFault();
    void handlerMemManage();
    void handlerBusFault();
    void handlerUsageFault();
    void handlerSVCall();
    void handlerDebugMonitor();
    void handlerPendSV();
    void handlerSysTick();

}

#endif

Module

#include "core.h"
#include "bscif.h"
#include "pm.h"
#include "bpm.h"
#include "gpio.h"
#include "flash.h"
#include "ast.h"
#include "wdt.h"
#include "error.h"
#include <string.h>

namespace Core {

    // The ISR vector stores the pointers to the functions called when a fault or an interrupt is triggered.
    // It must be aligned to the next power of 2 following the size of the vector table size. The SAM4L has 80
    // external interrupts + the 16 standard Cortex M4 exceptions, so the table length is 96 words, or 384 bytes.
    // The table must therefore must be aligned to 512 bytes boundaries.
    // See ARMv7-M Architecture Reference Manual, B3.2.5 "Vector Table Offset Register, VTOR", page 657,
    // and http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Ciheijba.html.
    uint32_t _isrVector[N_INTERNAL_EXCEPTIONS + N_EXTERNAL_INTERRUPTS] __attribute__ ((aligned (512)));

    // This array is used to store the NVIC ISERs (Interrupt Set-Enable Registers) when interrupts are stashed
    uint32_t _nvicStash[N_NVIC_IMPLEMENTED];


    // Initialize the core components : exceptions/interrupts table, 
    // clocks, SysTick (system timer), ...
    void init() {
        // Initialize the ISR vector
        memset(_isrVector, 0, sizeof(_isrVector));

        // Change the default handlers
        setExceptionHandler(Exception::NMI, handlerNMI);
        setExceptionHandler(Exception::HARDFAULT, handlerHardFault);
        setExceptionHandler(Exception::MEMMANAGE, handlerMemManage);
        setExceptionHandler(Exception::BUSFAULT, handlerBusFault);
        setExceptionHandler(Exception::USAGEFAULT, handlerUsageFault);
        setExceptionHandler(Exception::SVCALL, handlerSVCall);
        setExceptionHandler(Exception::DEBUGMONITOR, handlerDebugMonitor);
        setExceptionHandler(Exception::PENDSV, handlerPendSV);
        setExceptionHandler(Exception::SYSTICK, handlerSysTick);

        // Change the core vector pointer to the new table
        (*(volatile uint32_t*) VTOR) = (uint32_t) _isrVector;

        // Configure the Peripheral Debug register
        (*(volatile uint32_t*) PDBG)
            = 1 << PDBG_WDT     // Freeze WDT when Core is halted in debug mode
            | 0 << PDBG_AST     // Don't freeze AST when Core is halted in debug mode
            | 0 << PDBG_PEVC;   // Don't freeze PEVC when Core is halted in debug mode

        // Init the error reporting system
        Error::init();

        // Init the GPIO module
        GPIO::init();

        // Enable the default clocks
        BSCIF::enableOSC32K();

        // Init the AST timer which is used as a low-speed time reference (>10ms)
        // This is required by time() and sleep()
        AST::init();

        // Init the SysTick which is used as a high-speed time reference (based on CPU clock)
        // This is required by waitMicroseconds()
        enableSysTick();

        // Clear the watchdog interrupt in case this was the cause of reset
        WDT::clearInterrupt();

        // Init the stashed interrupts array
        memset(_nvicStash, 0, N_NVIC_IMPLEMENTED * sizeof(uint32_t));
    }

    // Reset the chip
    // PM::resetCause will be SYSRESETREQ
    void reset() {
        // AIRCR (Application Interrupt and Reset Control Register)
        uint32_t aircr = (*(volatile uint32_t*) AIRCR);
        (*(volatile uint32_t*) AIRCR)
            = (aircr & 0xFFFF)          // Keep the previous register value without the key
            | 1 << AIRCR_SYSRESETREQ    // SYSRESETREQ : request reset
            | AIRCR_VECTKEY;            // Write protection key
    }

    // Reset the chip in bootloader mode
    void resetToBootloader() {
        Flash::writeFuse(Flash::FUSE_BOOTLOADER_FORCE, true);
        reset();
    }

    // Reset the chip in bootloader mode after a delay using the watchdog timer
    void resetToBootloader(unsigned int delayMs) {
        Flash::writeFuse(Flash::FUSE_BOOTLOADER_FORCE, true);
        WDT::enable(delayMs, WDT::Unit::MILLISECONDS);
    }

    // Get the chip's package type
    Package package() {
        bool ext = ((*(volatile uint32_t*) CHIPID_CIDR) >> CHIPID_CIDR_EXT) & 1;
        if (ext) {
            uint8_t pck = ((*(volatile uint32_t*) CHIPID_EXID) >> CHIPID_EXID_PACKAGE) & 0b111;
            if (pck == CHIPID_EXID_PACKAGE_48) {
                return Package::PCK_48PIN;
            } else if (pck == CHIPID_EXID_PACKAGE_64) {
                return Package::PCK_64PIN;
            } else if (pck == CHIPID_EXID_PACKAGE_100) {
                return Package::PCK_100PIN;
            }
        }
        return Package::UNKNOWN;
    }

    // Get the chip's RAM size
    RAMSize ramSize() {
        uint8_t sramsiz = ((*(volatile uint32_t*) CHIPID_CIDR) >> CHIPID_CIDR_SRAMSIZ) & 0b1111;
        if (sramsiz == CHIPID_CIDR_SRAMSIZ_32K) {
            return RAMSize::RAM_32K;
        } else if (sramsiz == CHIPID_CIDR_SRAMSIZ_64K) {
            return RAMSize::RAM_64K;
        }
        return RAMSize::UNKNOWN;
    }

    // Get the chip's Flash size
    FlashSize flashSize() {
        uint8_t nvpsiz = ((*(volatile uint32_t*) CHIPID_CIDR) >> CHIPID_CIDR_NVPSIZ) & 0b1111;
        if (nvpsiz == CHIPID_CIDR_NVPSIZ_128K) {
            return FlashSize::FLASH_128K;
        } else if (nvpsiz == CHIPID_CIDR_NVPSIZ_256K) {
            return FlashSize::FLASH_256K;
        } else if (nvpsiz == CHIPID_CIDR_NVPSIZ_512K) {
            return FlashSize::FLASH_512K;
        }
        return FlashSize::UNKNOWN;
    }

    // Copy the 120-bit unique serial number of the chip into the user buffer
    void serialNumber(uint8_t* sn) {
        memcpy(sn, (uint32_t*)SERIAL_NUMBER_ADDR, SERIAL_NUMBER_LENGTH);
    }

    // Set the handler for the specified exception
    void setExceptionHandler(Exception exception, void (*handler)()) {
        _isrVector[static_cast<int>(exception)] = (uint32_t)handler;
    }

    // Set the handler for the specified interrupt
    void setInterruptHandler(Interrupt interrupt, void (*handler)()) {
        _isrVector[N_INTERNAL_EXCEPTIONS + static_cast<int>(interrupt)] = (uint32_t)handler;
    }

    // Enable the specified interrupt with the given priority
    void enableInterrupt(Interrupt interrupt, uint8_t priority) {
        // For ICPR and ISER : 
        // 32 channels and 4 bytes per register :
        // (channel / 32) * 4 = (channel >> 5) << 2 = (channel >> 3) & 0xFFFC
        // channel % 32 = channel & 0x1F

        // For IPR, each channel is coded on a single byte (4 channels in each 4-byte register),
        // we can therefore access each channel directly

        const int channel = static_cast<int>(interrupt);

        // ICPR (Interrupt Clear-Pending Register) : clear any pending interrupt
        (*(volatile uint32_t*) (NVIC_ICPR0 + ((channel >> 3) & 0xFFFC))) = 1 << (channel & 0x1F);

        // IPR (Interrupt Priority Register) : set the interrupt priority
        (*(volatile uint8_t*) (NVIC_IPR0 + channel)) = priority;

        // ISER (Interrupt Set-Enable Register) : enable this interrupt
        (*(volatile uint32_t*) (NVIC_ISER0 + ((channel >> 3) & 0xFFFC))) = 1 << (channel & 0x1F);
    }

    // Disable the specified interrupt
    void disableInterrupt(Interrupt interrupt) {
        // For ICER and ICPR :
        // 32 channels and 4 bytes per register :
        // (channel / 32) * 4 = (channel >> 5) << 2 = (channel >> 3) & 0xFFFC
        // channel % 32 = channel & 0x1F

        const int channel = static_cast<int>(interrupt);

        // ICER (Interrupt Clear-Enable Register) : disable this interrupt
        (*(volatile uint32_t*) (NVIC_ICER0 + ((channel >> 3) & 0xFFFC))) = 1 << (channel & 0x1F);

        // ICPR (Interrupt Clear-Pending Register) : clear any pending interrupt
        (*(volatile uint32_t*) (NVIC_ICPR0 + ((channel >> 3) & 0xFFFC))) = 1 << (channel & 0x1F);
    }

    // Change the priority for the specified interrupt
    void setInterruptPriority(Interrupt interrupt, uint8_t priority) {
        // IPR (Interrupt Priority Register) : set the interrupt priority
        (*(volatile uint8_t*) (NVIC_IPR0 + static_cast<int>(interrupt))) = priority;
    }

    // Disable temporarily all the interrupts at the NVIC level. The user is still
    // able to re-enable some interrupts manually. This is useful to create a code
    // section where only some selected interrupts can be triggered. Use applyStashedInterrupts()
    // to enable the interrupts as they were before.
    void stashInterrupts() {
        disableInterrupts();

        for (int i = 0; i < N_NVIC_IMPLEMENTED; i++) {
            _nvicStash[i] = (*(volatile uint32_t*) (NVIC_ISER0 + i * sizeof(uint32_t)));
            (*(volatile uint32_t*) (NVIC_ICER0 + i * sizeof(uint32_t))) = 0xFFFFFFFF;
        }

        enableInterrupts();
    }

    // Re-enable stashed interrupts. Note that this will not disable interrupts that were
    // manually enabled after stashInterrupts()
    void applyStashedInterrupts() {
        disableInterrupts();

        for (int i = 0; i < N_NVIC_IMPLEMENTED; i++) {
            (*(volatile uint32_t*) (NVIC_ISER0 + i * sizeof(uint32_t))) = _nvicStash[i];
        }

        enableInterrupts();
    }

    Interrupt currentInterrupt() {
        // ICSR (Interrupt Control and State Register) : get the currently active
        // interrupt number from the VECTACTIVE field
        return static_cast<Interrupt>(((*(volatile uint32_t*) ICSR) & 0x1FF) - N_INTERNAL_EXCEPTIONS);
    }

    // Sleep for a specified amount of time
    void sleep(SleepMode mode, unsigned long length, TimeUnit unit) {
        // Select the correct sleep mode
        if (mode >= SleepMode::SLEEP0 && mode <= SleepMode::SLEEP3) {
            *(volatile uint32_t*) SCR &= ~(uint32_t)(1 << SCR_SLEEPDEEP);
        } else {
            *(volatile uint32_t*) SCR |= 1 << SCR_SLEEPDEEP;
        }
        BPM::setSleepMode(mode);

        // The length is optional, if not specified the chip will sleep until an event
        // wakes it up. See PM::enableWakeUpSource() and BPM::enableBackupWakeUpSource().
        if (length > 0) {
            // Convert between units
            if (unit == TimeUnit::SECONDS) {
                length *= 1000;
            }

            // Enable an alarm to wake up the chip after a specified
            // amount of time
            AST::enableAlarm(length);
        }

        // Sleep until a known event happens
        do {
            // WFI : Wait For Interrupt
            // This special ARM instruction can put the chip in sleep mode until
            // an interrupt with a sufficient priority is triggered
            // See §B1.5.17 Power Management in the ARMv7-M Architecture Reference Manual
            __asm__ __volatile__("WFI");
        } while (!(length > 0 
                    ? AST::alarmPassed()                    // If length is specified, wait until the AST alarm is triggered
                    : PM::wakeUpCause() != PM::WakeUpCause::UNKNOWN)); // Otherwise, wait for any interrupt
    }

    // The default sleep mode is SLEEP0
    void sleep(unsigned long length, TimeUnit unit) {
        sleep(SleepMode::SLEEP0, length, unit);
    }

    // Enable SysTick as a simple counter clocked on the CPU clock
    void enableSysTick() {
        // Reset reload and current values (this is a 24-bit timer)
        *(volatile uint32_t*) SYST_RVR = 0xFFFFFF;
        *(volatile uint32_t*) SYST_CVR = 0xFFFFFF;

        *(volatile uint32_t*) SYST_CSR
            = 1 << SYST_CSR_ENABLE      // Enable counter
            | 0 << SYST_CSR_TICKINT;    // Disable interrupt when the counter reaches 0
    }

    // Disable SysTick
    void disableSysTick() {
        *(volatile uint32_t*) SYST_CSR = 0;
    }

    // Waste CPU clock cycles to wait for a specified amount of time
    void waitMicroseconds(unsigned long length) {
        // Save the last CVR (Current Value Register) value at each loop iteration
        uint32_t last = *(volatile uint32_t*) SYST_CVR;

        // Save the RVR (Reset Value Register) register
        uint32_t rvr = *(volatile uint32_t*) SYST_RVR;

        // Compute the number of CPU clock counts to wait for
        uint64_t t = 0;
        uint64_t delay = 0;
        delay = (length * (uint64_t)PM::getCPUClockFrequency()) / 1000000UL;

        // Compensate (approximately) for the function overhead : about 50 clock cycles
        const int OVERHEAD = 50;
        if (delay > OVERHEAD) {
            delay -= OVERHEAD;
        }

        // Do the actual waiting
        while (1) {
            if (t >= delay) {
                break;
            }
            uint32_t current = *(volatile uint32_t*) SYST_CVR;
            if (current < last) {
                t += last - current;
            } else {
                t += last + (rvr - current);
            }
            last = current;
        }
    }


    // Default exception handlers
    void handlerNMI() {
        while (1);
    }

    void handlerHardFault() {
        while (1);
    }

    void handlerMemManage() {
        while (1);
    }

    void handlerBusFault() {
        while (1);
    }

    void handlerUsageFault() {
        while (1);
    }

    void handlerSVCall() {
        while (1);
    }

    void handlerDebugMonitor() {
        while (1);
    }

    void handlerPendSV() {
        while (1);
    }

    void handlerSysTick() {
    }
}