AST
module reference#include <ast.h>Makefile :
ast
is already included by default
The AST (Asynchronous Timer) is a special timer that stays enabled in all power modes and is able to wake the microcontroller from any low-power mode. It is used by the Core
module as the main time reference and as the backend behind the Core::sleep()
function.
The AST consists of a customizable clock input, a prescaler with a digital tuner to adjust the clock frequency, and a 32-bit counter that is incremented at each rising edge of the output clock of the prescaler.
By default, the AST operates on the 32kHz low-power clock that is prescaled and tuned to increment the counter at a 1kHz frequency. When the 32-bit counter overflows, another internal register is incremented in order to provide a global 64-bit time-keeping counter. This counter is used directly for the time()
function to provide the number of milliseconds that have passed since the timer was enabled.
The module also defines a Time
type, that is merely an uint64_t
that can be used globally to store time in a consistent and overflow-proof manner (the microcontroller will be long dead before an 1kHz clock overflows a 64-bit counter).
Core::init()
.Enable an alarm interrupt to be triggered at the specified time (in ms), either absolute or relative to the current time. If an handler is specified, it will be called when the alarm time is reached. If wake
is set, the alarm event will be able to wake the microcontroller from any low-power mode. This is used internally by Core::sleep()
when a amount of time is specified.
The maximum possible value for time
is about 131000 seconds, approximately 36 hours.
By default, the AST is not stopped when the Core is halted in debug mode. You can change this behaviour by modifying the configuration of the Peripheral Debug register (PDBG
) in Core::init()
.
Otherwise, there isn't much to customise in this module. The clock configuration could be modified, but this would disrupt the behaviour of the widely-used Core::time()
and Core::sleep()
functions.
#ifndef _AST_H_ #define _AST_H_ #include <stdint.h> // Asynchronous Timer // This module manages the 32-bit asynchronous timer/counter // which is used as a system time (with a 2-ms resolution) and is able // to wake up the chip at a specific time namespace AST { // Peripheral memory space base address const uint32_t BASE = 0x400F0800; // Registers addresses const uint32_t OFFSET_CR = 0x000; // Control Register const uint32_t OFFSET_CV = 0x004; // Control Value Register const uint32_t OFFSET_SR = 0x008; // Status Register const uint32_t OFFSET_SCR = 0x00C; // Status Clear Register const uint32_t OFFSET_IER = 0x010; // Interrupt Enable Register const uint32_t OFFSET_IDR = 0x014; // Interrupt Disable Register const uint32_t OFFSET_IMR = 0x018; // Interrupt Mask Register const uint32_t OFFSET_WER = 0x01C; // Wake Enable Register const uint32_t OFFSET_AR0 = 0x020; // Alarm Register 0 //const uint32_t OFFSET_AR1 = 0x024; // Alarm Register 1 -- not implemented const uint32_t OFFSET_PIR0 = 0x030; // Periodic Interval Register 0 //const uint32_t OFFSET_PIR1 = 0x034; // Periodic Interval Register 1 -- not implemented const uint32_t OFFSET_CLOCK = 0x040; // Clock Control Register const uint32_t OFFSET_DTR = 0x044; // Digital Tuner Register const uint32_t OFFSET_EVE = 0x048; // Event Enable Register const uint32_t OFFSET_EVD = 0x04C; // Event Disable Register const uint32_t OFFSET_EVM = 0x050; // Event Mask Register const uint32_t OFFSET_CALV = 0x054; // Calendar Value Register // Subregisters const uint32_t CR_EN = 0; const uint32_t CR_PCLR = 1; const uint32_t CR_CAL = 2; const uint32_t CR_CA0 = 8; const uint32_t CR_PSEL = 16; const uint32_t SR_OVF = 0; const uint32_t SR_ALARM0 = 8; const uint32_t SR_PER0 = 16; const uint32_t SR_BUSY = 24; const uint32_t SR_READY = 25; const uint32_t SR_CLKBUSY = 28; const uint32_t SR_CLKRDY = 29; const uint32_t CLOCK_CEN = 0; const uint32_t CLOCK_CSSEL = 8; const uint32_t DTR_EXP = 0; const uint32_t DTR_ADD = 5; const uint32_t DTR_VALUE = 8; using Time = volatile uint64_t; extern Time _currentTimeHighBytes; // Module API void init(); inline volatile Time time() { return ((_currentTimeHighBytes + (*(volatile uint32_t*)(BASE + OFFSET_CV))) * 1000) / (32768/2); }; void enableAlarm(Time time, bool relative=true, void (*handler)()=nullptr, bool wake=true); void disableAlarm(); inline volatile bool alarmPassed() { return *(volatile uint32_t*)(BASE + OFFSET_CV) >= *(volatile uint32_t*)(BASE + OFFSET_AR0); } } #endif
#include "ast.h" #include "core.h" namespace AST { // Represents the highest 32 bits of the 64-bit current time // The lowest 32 bits are taken from the Current Value Register (CV) // See time() Time _currentTimeHighBytes = 0; // User handler for the Alarm interrupt void (*_alarmHandler)(); extern uint8_t INTERRUPT_PRIORITY; // Internal functions void overflowHandler(); void alarmHandlerWrapper(); inline void waitWhileBusy() { while ((*(volatile uint32_t*)(BASE + OFFSET_SR)) & (1 << SR_BUSY)); }; // Note : the CR, CV, SCR, WER, EVE, EVD, ARn, PIRn, and DTR registers // cannot be read or written when SR.BUSY is set, because the peripheral // is synchronizing them between the two clock domains // Initialize and enable the AST counter with the 32768Hz clock divided by 2 as input void init() { // See datasheet ยง19.5.1 Initialization // Select the clock (*(volatile uint32_t*)(BASE + OFFSET_CLOCK)) = 1 << CLOCK_CSSEL; // 32kHz clock (OSC32 or RC32) while ((*(volatile uint32_t*)(BASE + OFFSET_SR)) & (1 << SR_CLKBUSY)); // Enable the clock (*(volatile uint32_t*)(BASE + OFFSET_CLOCK)) |= 1 << CLOCK_CEN; while ((*(volatile uint32_t*)(BASE + OFFSET_SR)) & (1 << SR_CLKBUSY)); // IER (Interrupt Enable Register) : enable the Overflow interrupt (*(volatile uint32_t*)(BASE + OFFSET_IER)) = 1 << SR_OVF; // Set the handler and enable the module interrupt at the Core level Core::setInterruptHandler(Core::Interrupt::AST_OVF, overflowHandler); Core::enableInterrupt(Core::Interrupt::AST_OVF, INTERRUPT_PRIORITY); // Enable wake-up for the OVF interrupt waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_WER)) = 1 << SR_OVF; // Reset the counter value waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_CV)) = 0; // Reset the prescaler value (*(volatile uint32_t*)(BASE + OFFSET_CR)) |= 1 << CR_PCLR; // Disable the digital tuner waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_DTR)) = 0; // CR.PSEL is kept to 0, which means the prescaler will divide // the input clock frequency by 2 // Enable the counter waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_CR)) = 1 << CR_EN; } void enableAlarm(Time time, bool relative, void (*handler)(), bool wake) { // Critical section Core::disableInterrupts(); // Set the user handler _alarmHandler = handler; // Enable wake-up for the ALARM interrupt if (wake) { waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_WER)) = 1 << SR_ALARM0; } // IDR (Interrupt Disable Register) : disable the Alarm interrupt (*(volatile uint32_t*)(BASE + OFFSET_IDR)) = 1 << SR_ALARM0; // SCR (Status Clear Register) : clear the interrupt waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_SCR)) = 1 << SR_ALARM0; // Convert the time from ms to clock cycles // Remember that the 32768Hz input clock is prescaled by a factor of 2 time = (time * (32768/2)) / 1000; // Substract 2 clock cycles from the time to account for the function overhead if (time > 2) { time -= 2; } else { time = 0; } // Set the alarm time // The 32-bit truncate is not a problem as long as the alarm is not more than // ~ 36 hours in the future : (2^32) cycles / 36768 = 131072s ~= 36h waitWhileBusy(); if (relative) { if (time < 2) { time = 2; } time += *(volatile uint32_t*)(BASE + OFFSET_CV); } (*(volatile uint32_t*)(BASE + OFFSET_AR0)) = (uint32_t)time; waitWhileBusy(); // IER (Interrupt Enable Register) : enable the Alarm interrupt (*(volatile uint32_t*)(BASE + OFFSET_IER)) = 1 << SR_ALARM0; // Enable the module interrupt at the Core level Core::setInterruptHandler(Core::Interrupt::AST_ALARM, alarmHandlerWrapper); Core::enableInterrupt(Core::Interrupt::AST_ALARM, INTERRUPT_PRIORITY); // End of critical section Core::enableInterrupts(); } void disableAlarm() { // IDR (Interrupt Disable Register) : disable the Alarm interrupt (*(volatile uint32_t*)(BASE + OFFSET_IDR)) = 1 << SR_ALARM0; } void overflowHandler() { // Increment the high bytes of the 64-bit counter _currentTimeHighBytes += (uint64_t)1 << 32; // SCR (Status Clear Register) : clear the interrupt waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_SCR)) = 1 << SR_OVF; } void alarmHandlerWrapper() { // Call the user handler if defined if (_alarmHandler != nullptr) { _alarmHandler(); } // Disable the alarm so it is not triggered again after // the next counter overflow disableAlarm(); // SCR (Status Clear Register) : clear the interrupt waitWhileBusy(); (*(volatile uint32_t*)(BASE + OFFSET_SCR)) = 1 << SR_ALARM0; } }