Core
module reference#include <core.h>Makefile :
core
is already included by default
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.
main
(for Carbide it's called inside Carbide::init()
).PM::resetCause()
will return PM::ResetCause::SYSRESETREQ
.reset()
function above.PCK_48PIN
, PCK_64PIN
or PCK_100PIN
.RAM_32K
or RAM_64K
.FLASH_128K
, FLASH_256K
or FLASH_512K
.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...
Exception
can be
enableInterrupt()
). See the Interrupt
enumeration in the header below for the list of available interrupts.
init()
function has been called. This is merely a convenient proxy for the AST::time()
function.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.
Call the sleep()
function above with the default SLEEP0
sleep mode.
The maximum possible value for length
is about 131000 seconds, approximately 36 hours.
waitMicroseconds()
. This function is called by init()
.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 :
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.
#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 const uint32_t DEMCR = 0xE000EDFC; // Debug Exception and Monitor Control 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
#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> // Default exception vector defined in startup.cpp extern uint32_t exceptionVector[]; 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)); memcpy(_isrVector, exceptionVector, N_INTERNAL_EXCEPTIONS * sizeof(uint32_t)); // 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 // Disable DWT and ITM to free PA23 (used by I2C0) when a debugger is connected (*(volatile uint32_t*) DEMCR) = 0x00000000; // 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); // Wait until the alarm has passed while (!AST::alarmPassed()) { // 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"); } } else { // Wait until any known interrupt is triggered do { // See comment about WFI above __asm__ __volatile__("WFI"); } while (PM::isWakeUpCauseUnknown()); } } // 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() { } }