AST module reference

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

Description

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).

API

void init()
Initialize the timer with the default settings to provide a 1kHz clock to the counter. This is called internally by Core::init().
Time time()
Return the current counter value, which represents the amount of milliseconds that have passed since the timer has been enabled.
void enableAlarm(Time time, bool relative=true, void (*handler)()=nullptr, bool wake=true)

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.

void disableAlarm()
Disable the alarm interrupt.

Hacking

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.

Code

Header

#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

Module

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

}