TC module reference

Header :
#include <tc.h>
Makefile :
MODULES+=tc

Description

This module provides a set of generic 16-bit timer/counters (TC) that can be used for a wide range of purposes. A timer/counter is a register fed by a clock signal that is incremented or decremented at each tick of the input clock. Every time the counter is updated, depending on its configuration, its value can be compared to other registers and a few actions can be triggered in case of a match (such as switching a GPIO, resetting the counter, or triggering an interrupt). For example, these TCs can be used for :

  • generating PWM signals
  • reading PWM inputs
  • measuring the characteristics of an input signal (frequency, duty cycle, ...)
  • counting time accurately relatively to a reference clock
  • executing a task after a specified delay has elapsed (synchronously or asynchronously)

The SAM4L offers 1 or 2 TC controllers (1 for the 48-pin and 64-pin packages, and 2 for the 100-pin packages), TC0 and (optionally) TC1; each controller provides 3 independant counters, numbered 0 to 2; and each counter has two channels A and B that can be used independently in some cases (mainly generating PWM signals). This gives you 3 counters and 6 channels for the 48-pin and 64-pin packages, or 6 counters and 12 channels in total for the 100-pin packages. Two types are defined, Counter and Channel, and the functions in this module use either one of them depending on the level at which they operate. To get the parent Counter of a Channel, simply use channel.counter. Helper constants are provided for these types : for instance, TC::TC0_1 is a Counter which represents the counter 1 of the controller 0, and TC::TC1_2A is a Channel which represents the channel A of the counter 2 of the controller 1.

Input clock

For each counter, the source of the input clock is independently selectable among a range of options. The frequency of the input clock is important and must be matched to the operation of the counter : if the clock is too quick, the counter will overflow before the delay needed, and if the clock is too slow, it will lead to a poor temporal resolution.

The available options are :

  • An internal clock derived from the PBA clock (which is usually the same as the main clock selected with PM::setMainClockSource()). In order to accomodate a wide range of frequencies, this clock can be divided by 2, 8, 32 or 128 :
    • SourceClock::PBA_OVER_2
    • SourceClock::PBA_OVER_8, which is the default
    • SourceClock::PBA_OVER_32
    • SourceClock::PBA_OVER_128
  • A generic clock : this is the Generic Clock 5 for TCO (SCIF::GCLKChannel::GCLK5_GLOC_TC0) or Generic Clock 8 for TC1 (SCIF::GCLKChannel::GCLK8_TC1_PEVC0). In this case, the clock must be properly configured and started using the SCIF module before using it as an input for a counter, and the clock frequency must be given to the counter through the sourceClockFrequency argument.
    • SourceClock::GENERIC_CLOCK
  • An external clock, connected to one of the three TCx CLKy lines. If the frequency is known, it must be given to the counter through the sourceClockFrequency argument, otherwise the latter can be left to zero but the only useful feature available will be the simple counter mode.
    • SourceClock::CLK0
    • SourceClock::CLK1
    • SourceClock::CLK2

As an example, let's say that we want to generate a PWM signal with a high-time of 1.5ms and a period of 15ms, which is typical for a servomotor, and that the main clock frequency is 12MHz, which is also typical for the microcontroller. With a source clock of PBA_OVER_2, the input frequency will be 6MHz and the counter will be incremented approximately every 0.167µs. Since the counter is 16-bit and its maximum value is 65535, it will overflow after 10.92ms and never reach the period of 15ms. We therefore need a slower input clock. For the extreme opposite, with a source clock of PBA_OVER_128, the counter will be incremented every 10.67µs and will overflow after 699ms, which is way more than needed; however, the temporal resolution will also be 10.67µs, which is approximately 1% of the 1ms of useful range for the servomotor, and may represent a significant error. As a general rule, in order to have the most precise signals or measurements possible, it is best to choose the quickest clock that will never overflow the counter, in this case PBA_OVER_8.

As a quick reference, here is a list of period and overflow delays for the available clocks derived from PBA and a few common Main Clock frequencies :

Input clock 4MHz 8MHz 12MHz 20MHz
resolution overflow resolution overflow resolution overflow resolution overflow
PBA_OVER_2 0.500µs 32.77ms 0.250µs 16.38ms 0.167µs 10.92ms 0.100µs 6.55ms
PBA_OVER_8 2.000µs 131.1ms 1.000µs 65.54ms 0.667µs 43.69ms 0.400µs 26.21ms
PBA_OVER_32 8.000µs 524.3ms 4.000µs 262.1ms 2.667µs 174.8ms 1.600µs 104.9ms
PBA_OVER_128 32.00µs 2.097s 16.00µs 1.049s 10.67µs 699.1ms 6.400µs 414.4ms

Simple counter mode

This mode is the most simple : the counter value is incremented at each rising edge of the input clock and cycles back to zero after the maximum value is reached (65535 by default). A few options are provided, see enableSimpleCounter() below for more information.

This mode can be used for example to measure elapsed time in steps of the input period clock (when the input clock frequency is known), or to count the edges of an external signal connected to the CLKn line (the number of times that a button has been pressed for instance) :

TC::enableSimpleCounter(counter, 0xFFFF, TC::SourceClock::CLK0, 0, true); // Invert the signal to increment on falling edge, which is when the button is pressed

PWM mode

In PWM mode, the channels A and B are configured as outputs and are driven to generate a square signal according to the counter configuration. For a given counter, the two channels can have different duty cycle, but always share the same input clock and the same output period and frequency. This allows for instance to control two servomotors or two leds independently with a single counter.

When a channel is configured in PWM mode, its corresponding output will rise at the beginning of the period and will fall after the configured high-time delay has elapsed. At the end of the period, the counter is reset and the cycle repeats indefinitely.

The default input clock (PBA_OVER_8) is perfect for controlling a servomotor or for dimming an LED :

  • for a servomotor :
    TC::enablePWM(TC::TC0_0A, 10000, 1500); // 10ms period, 1.5ms high-time
    and control the angle with setHighTime() between 1000 and 2000
  • for an LED :
    TC::enablePWM(TC::TC0_0A, 1000, 0); // 1ms period, 0ms high-time (LED turned off)
    and control the luminosity with setDutyCycle() between 0 and 100

For even easier servomotor control, take a look at the Servo util module.

Measure mode

In measure mode, the counter will start counting when a rising edge is detected on the A channel input, save the counter value when a falling edge is encountered, and stop counting at the next rising edge. Channel B is not used. This allows to measure the high time and the period of the signal, and therefore its frequency and duty cycle.

It can be used for instance to read a PWM input from an RC receiver, or to measure the rotation speed of a motor with an optical or magnetic sensor.

Timing mode

In timing mode, the counter can be used for synchronous or asynchronous waiting, mainly in order to execute a function after a specified delay.

This can be used in order to execute a task at a regular interval for miscellanous purposes, independently from the main loop execution.

API

Counter mode

void enableSimpleCounter(Counter counter, uint16_t maxValue=0xFFFF, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0, bool invertClock=false, bool upDown=false)

Use counter as a simple counter incremented at each tick of the clock. When maxValue is reached the counter is reset to zero, except if upDown is set, in which case the counter will start decrementing down to zero, then repeat. If invertClock is set, the input clock is inverted before reaching the counter : it effectively means that the counter is incremented at the falling edges of the clock.

Used with an external clock, this function allows to count the number of times a signal rises, such as the number of presses on a button or the number of detections of an optical or magnetic sensor.

PWM mode

bool enablePWM(Channel channel, float period=0, float highTime=0, bool output=true, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0)

Use channel (and its related counter) to output a PWM signal with the given period and highTime in microseconds. If output is set to false, it must be enabled later with enableOutput() in order to see the signal on the pin.

bool setPeriod(Counter counter, float period)
Set the period of the PWM signal of the given counter, in microseconds.
bool setHighTime(Channel channel, float highTime)
Set the high-time of the PWM signal of the given counter, in microseconds.
bool setDutyCycle(Channel channel, int percent)
Set the duty cycle of the PWM signal of the given counter, in percent. This will call setHighTime() with the correct value based on the current period.
void enableOutput(Channel channel)
Configure the pin to output the PWM signal of the specified channel.
void disableOutput(Channel channel)
Disable the output of the signal. This does not stop the counter but only disable the pin mapping.

Measure mode

void enableMeasure(Counter counter, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0)
Configure the specified counter to measure a signal wired on the channel A input.
void measure(Counter counter, bool continuous=false)
Start a measurement on the specified counter at the next rising edge of the input signal. enableMeasure() must have previously been called. If continuous is set, the measurement will be continuously repeated; otherwise, a single-shot measurement will be made and the counter will stop at the end of the period.
uint16_t measuredPeriodRaw(Counter counter)
Return the period of the signal of the last measurement in clock ticks.
unsigned long measuredPeriod(Counter counter)
Return the period of the signal of the last measurement in microseconds.
uint16_t measuredHighTimeRaw(Counter counter)
Return the high-time of the signal of the last measurement in clock ticks.
unsigned long measuredHighTime(Counter counter)
Return the high-time of the signal of the last measurement in microseconds.
unsigned int measuredDutyCycle(Counter counter)
Return the duty cycle of the signal of the last measurement in percent.
bool isMeasureOverflow(Counter counter)
Return true if the counter overflowed during the last measurement. In this case, the values returned by the functions above are not relevant.

Low-level counter functions

bool setRX(Channel channel, unsigned int rx)
In PWM mode, set the value of the RA or RB register of the specified channel. If rx is greater than the maximum value 0xFFFF, it will be truncated.
bool setRC(Counter counter, unsigned int rc)
In PWM mode, set the value of the RC register of the specified counter. If rc is greater than the maximum value 0xFFFF, it will be truncated.
uint16_t counterValue(Counter counter)
Return the current value of the specified counter.
uint16_t raValue(Counter counter)
Return the value of the RA register of the specified counter.
uint16_t rbValue(Counter counter)
Return the value of the RB register of the specified counter.
uint16_t rcValue(Counter counter)
Return the value of the RC register of the specified counter.
unsigned long sourceClockFrequency(Counter counter)
Return the frequency of the source clock of the specified counter.

Timing functions

void wait(Counter counter, unsigned long delay, Unit unit=Unit::MILLISECONDS, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0)
Wait for the specified amount of time based on the specified counter. In most cases, this has no advantage over Core::sleep() and the latter should be used instead.
void execDelayed(Counter counter, void (*handler)(), unsigned long delay, Unit unit=Unit::MILLISECONDS, bool repeat=false, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0)
Asynchronously call the given handler function after the specified delay has expired. If repeat is set, the handler will be called at regular interval; otherwise, the counter will stop after the first time.

Functions common to all modes

void start(Counter counter)
Start the specified counter. This will also reset its value.
void stop(Counter counter)
Stop the specified counter.
void sync()
Start all the counters simultaneously.
void setPin(Counter counter, PinFunction function, GPIO::Pin pin)

Set the GPIO pin used for this port. PinFunction can be OUT or CLK.

Hacking

The TC peripherals, even though relatively simple, are very versatile and could be useful for other purposes. Take a look at the datasheet for more information on what can be achieved with a custom configuration.

Code

Header

#ifndef _TC_H_
#define _TC_H_

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

// Timers/Counters
// This module manages timers, which are counting registers that are automatically
// incremented (or decremented) by a specified clock and can trigger events
// based on the output of comparators.
// For example, they are to generate a PWM signal to control a servomotor or to
// execute a callback function after a specified delay.
namespace TC {

    // Peripheral memory space base address
    const uint32_t TC_BASE = 0x40010000;
    const uint32_t TC_SIZE = 0x4000; // Meaning that TC1 is at TC_BASE + TC_SIZE = 0x40014000


    // Registers addresses
    const uint32_t OFFSET_COUNTER_SIZE = 0x040;
    const uint32_t OFFSET_CCR0 =     0x000; // Counter 0 Control Register
    const uint32_t OFFSET_CMR0 =     0x004; // Counter 0 Mode Register
    const uint32_t OFFSET_SMMR0 =    0x008; // Counter 0 Stepper Motor Mode Register
    const uint32_t OFFSET_CV0 =      0x010; // Counter 0 Counter Value
    const uint32_t OFFSET_RA0 =      0x014; // Counter 0 Register A
    const uint32_t OFFSET_RB0 =      0x018; // Counter 0 Register B
    const uint32_t OFFSET_RC0 =      0x01C; // Counter 0 Register C
    const uint32_t OFFSET_SR0 =      0x020; // Counter 0 Status Register
    const uint32_t OFFSET_IER0 =     0x024; // Interrupt Enable Register
    const uint32_t OFFSET_IDR0 =     0x028; // Counter 0 Interrupt Disable Register
    const uint32_t OFFSET_IMR0 =     0x02C; // Counter 0 Interrupt Mask Register
    const uint32_t OFFSET_CCR1 =     0x040; // Counter 1 Control Register
    const uint32_t OFFSET_CMR1 =     0x044; // Counter 1 Mode Register
    const uint32_t OFFSET_SMMR1 =    0x048; // Counter 1 Stepper Motor Mode Register
    const uint32_t OFFSET_CV1 =      0x050; // Counter 1 Counter Value
    const uint32_t OFFSET_RA1 =      0x054; // Counter 1 Register A
    const uint32_t OFFSET_RB1 =      0x058; // Counter 1 Register B
    const uint32_t OFFSET_RC1 =      0x05C; // Counter 1 Register C
    const uint32_t OFFSET_SR1 =      0x060; // Counter 1 Status Register
    const uint32_t OFFSET_IER1 =     0x064; // Counter 1 Interrupt Enable Register
    const uint32_t OFFSET_IDR1 =     0x068; // Counter 1 Interrupt Disable Register
    const uint32_t OFFSET_IMR1 =     0x06C; // Counter 1 Interrupt Mask Register
    const uint32_t OFFSET_CCR2 =     0x080; // Counter 2 Control Register
    const uint32_t OFFSET_CMR2 =     0x084; // Counter 2 Mode Register
    const uint32_t OFFSET_SMMR2 =    0x088; // Ch 2 Stepper Motor Mode Register
    const uint32_t OFFSET_CV2 =      0x090; // Counter 2 Counter Value
    const uint32_t OFFSET_RA2 =      0x094; // Counter 2 Register A
    const uint32_t OFFSET_RB2 =      0x098; // Counter 2 Register B
    const uint32_t OFFSET_RC2 =      0x09C; // Counter 2 Register C
    const uint32_t OFFSET_SR2 =      0x0A0; // Counter 2 Status Register
    const uint32_t OFFSET_IER2 =     0x0A4; // Counter 2 Interrupt Enable Register
    const uint32_t OFFSET_IDR2 =     0x0A8; // Counter 2 Interrupt Disable Register
    const uint32_t OFFSET_IMR2 =     0x0AC; // Counter 2 Interrupt Mask Register
    const uint32_t OFFSET_BCR =      0x0C0; // Block Control Register
    const uint32_t OFFSET_BMR =      0x0C4; // Block Mode Register
    const uint32_t OFFSET_WPMR =     0x0E4; // Write Protect Mode Register
    const uint32_t OFFSET_FEATURES = 0x0F8; // Features Register


    // Subregisters
    const uint32_t CMR_TCCLKS = 0;
    const uint32_t CMR_CLKI = 3;
    const uint32_t CMR_BURST = 4;
    const uint32_t CMR_CPCSTOP = 6;
    const uint32_t CMR_CPCDIS = 7;
    const uint32_t CMR_EEVTEDG = 8;
    const uint32_t CMR_EEVT = 10;
    const uint32_t CMR_ENETRG = 12;
    const uint32_t CMR_WAVSEL = 13;
    const uint32_t CMR_WAVE = 15;
    const uint32_t CMR_ACPA = 16;
    const uint32_t CMR_ACPC = 18;
    const uint32_t CMR_AEEVT = 20;
    const uint32_t CMR_ASWTRG = 22;
    const uint32_t CMR_BCPB = 24;
    const uint32_t CMR_BCPC = 26;
    const uint32_t CMR_BEEVT = 28;
    const uint32_t CMR_BSWTRG = 30;
    const uint32_t CMR_LDBSTOP = 6;
    const uint32_t CMR_LDBDIS = 7;
    const uint32_t CMR_ETRGEDG = 8;
    const uint32_t CMR_ABETRG = 10;
    const uint32_t CMR_CPCTRG = 14;
    const uint32_t CMR_LDRA = 16;
    const uint32_t CMR_LDRB = 18;
    const uint32_t CCR_CLKEN = 0;
    const uint32_t CCR_CLKDIS = 1;
    const uint32_t CCR_SWTRG = 2;
    const uint32_t SR_COVFS = 0;
    const uint32_t SR_LOVRS = 1;
    const uint32_t SR_CPAS = 2;
    const uint32_t SR_CPBS = 3;
    const uint32_t SR_CPCS = 4;
    const uint32_t SR_LDRAS = 5;
    const uint32_t SR_LDRBS = 6;
    const uint32_t SR_ETRGS = 7;
    const uint32_t SR_CLKSTA = 16;
    const uint32_t SR_MTIOA = 17;
    const uint32_t SR_MTIOB = 18;
    const uint32_t BCR_SYNC = 0;
    const uint32_t WPMR_WPEN = 0;
    const uint32_t WPMR_WPKEY = 8;


    // Constants
    const uint32_t UNLOCK_KEY = 0x54494D;
    const uint8_t TC0 = 0;
    const uint8_t TC1 = 1;
    const uint8_t CH0 = 0;
    const uint8_t CH1 = 1;
    const uint8_t CH2 = 2;
    const uint8_t TIOA = 0;
    const uint8_t TIOB = 1;

    enum class Unit {
        MILLISECONDS,
        MICROSECONDS
    };
    
    // The actual number of TCs available is package-dependant
    // and is defined in pins_sam4l_XX.cpp
    extern const uint8_t N_TC;
    const uint8_t MAX_N_TC = 2;
    const uint8_t N_COUNTERS_PER_TC = 3;
    const uint8_t N_CHANNELS_PER_COUNTER = 2;
    const uint8_t N_EXTERNAL_CLOCKS_PER_TC = 3;

    struct Counter {
        uint8_t tc;
        uint8_t n;
    };

    struct Channel {
        Counter counter;
        uint8_t line;
    };

    enum class SourceClock {
        GENERIC_CLOCK,
        PBA_OVER_2,
        PBA_OVER_8,
        PBA_OVER_32,
        PBA_OVER_128,
        CLK0,
        CLK1,
        CLK2,
    };

    enum class PinFunction {
        OUT,
        CLK
    };

    // Quick helpers for each counter and channel
    const Counter TC0_0 = {TC::TC0, TC::CH0};
    const Channel TC0_0A = {TC::TC0, TC::CH0, TC::TIOA};
    const Channel TC0_0B = {TC::TC0, TC::CH0, TC::TIOB};
    const Counter TC0_1 = {TC::TC0, TC::CH1};
    const Channel TC0_1A = {TC::TC0, TC::CH1, TC::TIOA};
    const Channel TC0_1B = {TC::TC0, TC::CH1, TC::TIOB};
    const Counter TC0_2 = {TC::TC0, TC::CH2};
    const Channel TC0_2A = {TC::TC0, TC::CH2, TC::TIOA};
    const Channel TC0_2B = {TC::TC0, TC::CH2, TC::TIOB};
    const Counter TC1_0 = {TC::TC1, TC::CH0};
    const Channel TC1_0A = {TC::TC1, TC::CH0, TC::TIOA};
    const Channel TC1_0B = {TC::TC1, TC::CH0, TC::TIOB};
    const Counter TC1_1 = {TC::TC1, TC::CH1};
    const Channel TC1_1A = {TC::TC1, TC::CH1, TC::TIOA};
    const Channel TC1_1B = {TC::TC1, TC::CH1, TC::TIOB};
    const Counter TC1_2 = {TC::TC1, TC::CH2};
    const Channel TC1_2A = {TC::TC1, TC::CH2, TC::TIOA};
    const Channel TC1_2B = {TC::TC1, TC::CH2, TC::TIOB};

    // Error codes
    const Error::Code ERR_INVALID_TC = 0x0001;


    // Simple counter mode
    void enableSimpleCounter(Counter counter, uint16_t maxValue=0xFFFF, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0, bool invertClock=false, bool upDown=false);

    // PWM mode
    bool enablePWM(Channel channel, float period=0, float highTime=0, bool output=true, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0);
    bool setPeriod(Counter counter, float period);
    bool setHighTime(Channel channel, float highTime);
    bool setDutyCycle(Channel channel, int percent);
    void enableOutput(Channel channel);
    void disableOutput(Channel channel);

    // Measure mode
    void enableMeasure(Counter counter, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0);
    void measure(Counter counter, bool continuous=false);
    uint16_t measuredPeriodRaw(Counter counter);
    unsigned long measuredPeriod(Counter counter);
    uint16_t measuredHighTimeRaw(Counter counter);
    unsigned long measuredHighTime(Counter counter);
    unsigned int measuredDutyCycle(Counter counter);
    bool isMeasureOverflow(Counter counter);

    // Low-level counter functions
    bool setRX(Channel channel, unsigned int rx);
    bool setRC(Counter counter, unsigned int rc);
    uint16_t counterValue(Counter counter);
    uint16_t raValue(Counter counter);
    uint16_t rbValue(Counter counter);
    uint16_t rcValue(Counter counter);
    unsigned long sourceClockFrequency(Counter counter);

    // Timing functions
    void wait(Counter counter, unsigned long delay, Unit unit=Unit::MILLISECONDS, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0);
    void execDelayed(Counter counter, void (*handler)(), unsigned long delay, Unit unit=Unit::MILLISECONDS, bool repeat=false, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0);

    // Functions common to all modes
    void start(Counter counter);
    void stop(Counter counter);
    void sync();
    void setPin(Channel channel, PinFunction function, GPIO::Pin pin);

}


#endif

Module

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

namespace TC {

    // Package-dependant, defined in pins_sam4l_XX.cpp
    extern struct GPIO::Pin PINS[MAX_N_TC][N_COUNTERS_PER_TC * N_CHANNELS_PER_COUNTER];
    extern struct GPIO::Pin PINS_CLK[MAX_N_TC][N_COUNTERS_PER_TC * N_CHANNELS_PER_COUNTER];

    // Used to save the counters' current configuration
    struct CounterConfig {
        SourceClock sourceClock;
        unsigned long sourceClockFrequency;
    };
    CounterConfig _countersConfig[MAX_N_TC][N_COUNTERS_PER_TC];

    // Internal list of delayed callbacks to execute
    extern uint8_t INTERRUPT_PRIORITY;
    struct ExecDelayedData {
        uint32_t handler;
        int skipPeriods;
        int skipPeriodsReset;
        int rest;
        int restReset;
        bool repeat;
    };
    ExecDelayedData execDelayedData[MAX_N_TC][N_COUNTERS_PER_TC];

    void execDelayedHandlerWrapper();

    // Internal functions
    inline void checkTC(Counter counter) {
        if (counter.tc + 1 > N_TC) {
            Error::happened(Error::Module::TC, ERR_INVALID_TC, Error::Severity::CRITICAL);
        }
    }
    inline void checkTC(Channel channel) {
        checkTC(channel.counter);
    }

    void initCounter(Counter counter, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        // Save the config
        _countersConfig[counter.tc][counter.n].sourceClock = sourceClock;
        _countersConfig[counter.tc][counter.n].sourceClockFrequency = sourceClockFrequency;

        // Enable the module clock
        PM::enablePeripheralClock(PM::CLK_TC0 + counter.tc);

        // Enable the divided clock powering the counter
        if (sourceClock == SourceClock::PBA_OVER_2) {
            PM::enablePBADivClock(1); // 2^1 = 2
        } else if (sourceClock == SourceClock::PBA_OVER_8) {
            PM::enablePBADivClock(3); // 2^3 = 8
        } else if (sourceClock == SourceClock::PBA_OVER_32) {
            PM::enablePBADivClock(5); // 2^5 = 32
        } else if (sourceClock == SourceClock::PBA_OVER_128) {
            PM::enablePBADivClock(7); // 2^7 = 128
        }

        // Enable the external input clock pin
        if (sourceClock == SourceClock::CLK0) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][0]);
        } else if (sourceClock == SourceClock::CLK1) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][1]);
        } else if (sourceClock == SourceClock::CLK2) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][2]);
        }
    }


    // Simple counter mode

    void enableSimpleCounter(Counter counter, uint16_t maxValue, SourceClock sourceClock, unsigned long sourceClockFrequency, bool invert, bool upDown) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Initialize the counter and its clock
        initCounter(counter, sourceClock, sourceClockFrequency);

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect disabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // CCR (Channel Control Register) : disable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKDIS;       // CLKDIS : disable the clock

        // CMR (Channel Mode Register) : setup the counter in Waveform Generation Mode
        (*(volatile uint32_t*)(REG + OFFSET_CMR0))
            =                                 // TCCLKS : clock selection
              (static_cast<int>(sourceClock) & 0b111) << CMR_TCCLKS
            | invert << CMR_CLKI              // CLKI : clock invert
            | (upDown ? 3 : 2) << CMR_WAVSEL  // WAVSEL : UP or UP/DOWN mode with automatic trigger on RC Compare
            | 1 << CMR_WAVE;                  // WAVE : waveform generation mode

        // Set the max value in RC
        (*(volatile uint32_t*)(REG + OFFSET_RC0)) = maxValue;

        // CCR (Channel Control Register) : enable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKEN;        // CLKEN : enable the clock

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // Start the counter
        start(counter);
    }


    // PWM mode

    // Initialize a TC channel and counter in PWM mode with the given period and hightime in microseconds
    bool enablePWM(Channel channel, float period, float highTime, bool output, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        checkTC(channel);
        uint32_t REG = TC_BASE + channel.counter.tc * TC_SIZE + channel.counter.n * OFFSET_COUNTER_SIZE;

        // Initialize the counter and its clock
        initCounter(channel.counter, sourceClock, sourceClockFrequency);

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + channel.counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect disabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // CCR (Channel Control Register) : disable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKDIS;       // CLKDIS : disable the clock

        // CMR (Channel Mode Register) : setup the counter in Waveform Generation Mode
        (*(volatile uint32_t*)(REG + OFFSET_CMR0))
            =                   // TCCLKS : clock selection
              (static_cast<int>(sourceClock) & 0b111) << CMR_TCCLKS
            | 0 << CMR_CLKI     // CLKI : disable clock invert
            | 0 << CMR_BURST    // BURST : disable burst mode
            | 0 << CMR_CPCSTOP  // CPCSTOP : clock is not stopped with RC compare
            | 0 << CMR_CPCDIS   // CPCDIS : clock is not disabled with RC compare
            | 1 << CMR_EEVT     // EEVT : external event selection to XC0 (TIOB is therefore an output)
            | 2 << CMR_WAVSEL   // WAVSEL : UP mode with automatic trigger on RC Compare
            | 1 << CMR_WAVE     // WAVE : waveform generation mode
            | 2 << CMR_ACPA     // ACPA : RA/TIOA : clear
            | 1 << CMR_ACPC     // ACPC : RC/TIOA : set
            | 1 << CMR_ASWTRG   // ASWTRG : SoftwareTrigger/TIOA : set
            | 2 << CMR_BCPB     // BCPA : RA/TIOB : clear
            | 1 << CMR_BCPC     // BCPC : RC/TIOB : set
            | 1 << CMR_BSWTRG;  // BSWTRG : SoftwareTrigger/TIOB : set

        // Set the period and high time
        bool isValueValid = true;
        isValueValid = isValueValid && setPeriod(channel.counter, period);
        isValueValid = isValueValid && setHighTime(channel, highTime);

        // CCR (Channel Control Register) : enable and start the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKEN;        // CLKEN : enable the clock

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + channel.counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // If output is enabled, set the pin in peripheral mode
        if (output) {
            GPIO::enablePeripheral(PINS[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line]);
        }

        // Start the counter
        start(channel.counter);

        return isValueValid;
    }

    // Set the period in microseconds for both TIOA and TIOB of the specified counter
    bool setPeriod(Counter counter, float period) {
        checkTC(counter);

        uint64_t clockFrequency = sourceClockFrequency(counter);
        if (clockFrequency == 0) {
            return false;
        }
        return setRC(counter, period * clockFrequency / 1000000L);
    }

    // Set the high time of the specified channel in microseconds
    bool setHighTime(Channel channel, float highTime) {
        checkTC(channel);

        uint64_t clockFrequency = sourceClockFrequency(channel.counter);
        if (clockFrequency == 0) {
            return false;
        }
        return setRX(channel, highTime * clockFrequency / 1000000L);
    }

    // Set the duty cycle of the specified channel in percent
    bool setDutyCycle(Channel channel, int percent) {
        checkTC(channel);
        uint32_t REG = TC_BASE + channel.counter.tc * TC_SIZE + channel.counter.n * OFFSET_COUNTER_SIZE;
        uint32_t rc = (*(volatile uint32_t*)(REG + OFFSET_RC0));
        return setRX(channel, rc * percent / 100);
    }

    // Enable the output of the selected channel
    void enableOutput(Channel channel) {
        checkTC(channel);
        GPIO::enablePeripheral(PINS[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line]);
    }

    // Disable the output of the selected channel
    void disableOutput(Channel channel) {
        checkTC(channel);
        GPIO::disablePeripheral(PINS[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line]);
    }


    // Measure mode

    void enableMeasure(Counter counter, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Initialize the counter and its clock
        initCounter(counter, sourceClock, sourceClockFrequency);

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect disabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // CCR (Channel Control Register) : disable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKDIS;       // CLKDIS : disable the clock

        // CMR (Channel Mode Register) : setup the counter in Capture Mode
        (*(volatile uint32_t*)(REG + OFFSET_CMR0))
            =                   // TCCLKS : clock selection
              (static_cast<int>(sourceClock) & 0b111) << CMR_TCCLKS
            | 0 << CMR_CLKI     // CLKI : disable clock invert
            | 0 << CMR_BURST    // BURST : disable burst mode
            | 1 << CMR_LDBSTOP  // LDBSTOP : stop clock after RB load
            | 1 << CMR_LDBDIS   // LDBSTOP : disable clock after RB load
            | 1 << CMR_ETRGEDG  // ETRGEDG : external trigger on rising edge
            | 1 << CMR_ABETRG   // ABETRG : external trigger on TIOA
            | 0 << CMR_CPCTRG   // CPCTRG : RC disabled
            | 0 << CMR_WAVE     // WAVE : capture mode
            | 2 << CMR_LDRA     // LDRA : load RA on falling edge of TIOA
            | 1 << CMR_LDRB;    // LDRB : load RB on rising edge of TIOA

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // Enable the input pin for TIOA
        GPIO::enablePeripheral(PINS[counter.tc][N_CHANNELS_PER_COUNTER * counter.n]);
    }

    void measure(Counter counter, bool continuous) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // CCR (Channel Control Register) : disable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKDIS;       // CLKDIS : disable the clock

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect disabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // CMR (Channel Mode Register) : in one-shot mode, configure the TC to 
        // disable the clock after a measure
        if (!continuous) {
            (*(volatile uint32_t*)(REG + OFFSET_CMR0))
                |= 1 << CMR_LDBSTOP    // LDBSTOP : stop clock after RB load
                |  1 << CMR_LDBDIS;    // LDBSTOP : disable clock after RB load
        } else {
            (*(volatile uint32_t*)(REG + OFFSET_CMR0))
                &= ~(uint32_t)(
                      1 << CMR_LDBSTOP // LDBSTOP : do not stop clock after RB load
                    | 1 << CMR_LDBDIS  // LDBSTOP : do not disable clock after RB load
                );
        }

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // CCR (Channel Control Register) : enable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKEN;        // CLKEN : enable the clock
    }

    uint16_t measuredPeriodRaw(Counter counter) {
        return rbValue(counter);
    }

    unsigned long measuredPeriod(Counter counter) {
        unsigned long clockFrequency = sourceClockFrequency(counter);
        if (clockFrequency == 0) {
            return false;
        }
        return (uint64_t)measuredPeriodRaw(counter) * 1000000L / clockFrequency;
    }

    uint16_t measuredHighTimeRaw(Counter counter) {
        return raValue(counter);
    }

    unsigned long measuredHighTime(Counter counter) {
        unsigned long clockFrequency = sourceClockFrequency(counter);
        if (clockFrequency == 0) {
            return false;
        }
        return (uint64_t)measuredHighTimeRaw(counter) * 1000000L / clockFrequency;
    }

    unsigned int measuredDutyCycle(Counter counter) {
        return measuredHighTimeRaw(counter) * 100 / measuredPeriodRaw(counter);
    }

    bool isMeasureOverflow(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;
        return ((*(volatile uint32_t*)(REG + OFFSET_SR0)) >> SR_COVFS) & 1;
    }


    // Low-level counter functions

    // Set the RA or RB register of the given channel
    bool setRX(Channel channel, unsigned int rx) {
        checkTC(channel);
        uint32_t REG = TC_BASE + channel.counter.tc * TC_SIZE + channel.counter.n * OFFSET_COUNTER_SIZE;

        // Clip value
        bool isValueValid = true;
        if (rx > 0xFFFF) {
            rx = 0xFFFF;
            isValueValid = false;
        }

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + channel.counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // If the counter compare register (RA or RB) is zero, the output will be set by the RC compare
        // (CMR0.ACPC or CMR0.BCPC) but not immediately cleared by the RA/RB compare, and the output will
        // stay high instead of staying low. To match the expected behaviour the CMR register need to be
        // temporarily reconfigured to clear the output on RC compare.
        // When quitting this edge case (current RA or RB is 0), the default behaviour must be reset.
        // Depending on the case, the RA/RB value must be set either before of after configuring CMR.
        if (rx == 0) {
            // CMR (Channel Mode Register) : set RC compare over TIOA to 2
            uint32_t cmr = (*(volatile uint32_t*)(REG + OFFSET_CMR0));
            cmr &= ~(uint32_t)(0b11 << (channel.line == TIOB ? CMR_BCPC : CMR_ACPC));
            cmr |= 2 << (channel.line == TIOB ? CMR_BCPC : CMR_ACPC);
            (*(volatile uint32_t*)(REG + OFFSET_CMR0)) = cmr;

            // Set the signal high time *after* configuring CMR
            (*(volatile uint32_t*)(REG + (channel.line == TIOB ? OFFSET_RB0 : OFFSET_RA0))) = rx;
            
        } else if ((*(volatile uint32_t*)(REG + (channel.line == TIOB ? OFFSET_RB0 : OFFSET_RA0))) == 0) {
            // Set the signal high time *before* configuring CMR
            (*(volatile uint32_t*)(REG + (channel.line == TIOB ? OFFSET_RB0 : OFFSET_RA0))) = rx;

            // CMR (Channel Mode Register) : set RC compare over TIOA to 2
            uint32_t cmr = (*(volatile uint32_t*)(REG + OFFSET_CMR0));
            cmr &= ~(uint32_t)(0b11 << (channel.line == TIOB ? CMR_BCPC : CMR_ACPC));
            cmr |= 1 << (channel.line == TIOB ? CMR_BCPC : CMR_ACPC);
            (*(volatile uint32_t*)(REG + OFFSET_CMR0)) = cmr;

        } else {
            // Set the signal high time
            (*(volatile uint32_t*)(REG + (channel.line == TIOB ? OFFSET_RB0 : OFFSET_RA0))) = rx;
        }

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + channel.counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        return isValueValid;
    }

    // Set the RC register of the given counter
    bool setRC(Counter counter, unsigned int rc) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Clip value
        bool isValueValid = true;
        if (rc > 0xFFFF) {
            rc = 0xFFFF;
            isValueValid = false;
        }

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 0 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        // Set the signal period
        (*(volatile uint32_t*)(REG + OFFSET_RC0)) = rc;

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR))
            = 1 << WPMR_WPEN            // WPEN : write protect enabled
            | UNLOCK_KEY << WPMR_WPKEY; // WPKEY : write protect key

        return isValueValid;
    }

    // Get the value of the given counter
    uint16_t counterValue(Counter counter) {
        checkTC(counter);
        return (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE + OFFSET_CV0));
    }

    // Get the value of the RA register for the given counter
    uint16_t raValue(Counter counter) {
        checkTC(counter);
        return (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE + OFFSET_RA0));
    }

    // Get the value of the RB register for the given counter
    uint16_t rbValue(Counter counter) {
        checkTC(counter);
        return (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE + OFFSET_RB0));
    }

    // Get the value of the RC register for the given counter
    uint16_t rcValue(Counter counter) {
        checkTC(counter);
        return (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE + OFFSET_RC0));
    }

    unsigned long sourceClockFrequency(Counter counter) {
        switch (_countersConfig[counter.tc][counter.n].sourceClock) {
            case SourceClock::GENERIC_CLOCK:
            case SourceClock::CLK0:
            case SourceClock::CLK1:
            case SourceClock::CLK2:
                return _countersConfig[counter.tc][counter.n].sourceClockFrequency;

            case SourceClock::PBA_OVER_2:
                return PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc) / 2;

            case SourceClock::PBA_OVER_8:
                return PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc) / 8;

            case SourceClock::PBA_OVER_32:
                return PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc) / 32;

            case SourceClock::PBA_OVER_128:
                return PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc) / 128;
        }
        return 0;
    }

    // Wait for the specified delay
    void wait(Counter counter, unsigned long delay, Unit unit, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Initialize the counter and its clock
        initCounter(counter, sourceClock, sourceClockFrequency);

        // Compute timing
        if (unit == Unit::MILLISECONDS) {
            delay *= 1000;
        }
        unsigned int basePeriod = 80000000L / PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc);
        delay = delay * 10 / basePeriod;
        unsigned int repeat = delay / 0x10000; // Max counter value
        unsigned int rest = delay % 0x10000;

        // WPMR (Write Protect Mode Register) : disable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR)) = 0 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;

        for (unsigned int i = 0; i <= repeat; i++) {
            // Set the period length
            (*(volatile uint32_t*)(REG + OFFSET_RC0)) = (i == repeat ? rest : 0xFFFF);

            // Software trigger
            (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_SWTRG;

            // Wait for RC value to be reached
            while (!((*(volatile uint32_t*)(REG + OFFSET_SR0)) & (1 << SR_CPCS)));
        }

        // WPMR (Write Protect Mode Register) : re-enable write protect
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR)) = 1 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
    }

    // Call the given handler after the specified delay
    void execDelayed(Counter counter, void (*handler)(), unsigned long delay, bool repeat, Unit unit, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Initialize the counter and its clock
        initCounter(counter, sourceClock, sourceClockFrequency);

        // Stop the timer
        (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_CLKDIS;

        // Set the handler
        ExecDelayedData& data = execDelayedData[counter.tc][counter.n];
        data.handler = (uint32_t)handler;

        // Compute timings
        // If the requested delay is longer than a full period of the counter, compute and save the number
        // of periods to skip before calling the user handler
        if (unit == Unit::MILLISECONDS) {
            delay *= 1000;
        }
        unsigned int basePeriod = 80000000L / PM::getModuleClockFrequency(PM::CLK_TC0 + counter.tc);
        unsigned long value = delay * 10 / basePeriod;
        unsigned int skipPeriods = value / 0x10000; // 0x10000 is the max counter value
        unsigned int rest = value % 0x10000;
        data.skipPeriods = data.skipPeriodsReset = skipPeriods;
        data.rest = data.restReset = (skipPeriods > 0 ? rest : 0);
        data.repeat = repeat;
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR)) = 0 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
        (*(volatile uint32_t*)(REG + OFFSET_RC0)) = (skipPeriods > 0 ? 0xFFFF : rest);
        (*(volatile uint32_t*)(TC_BASE + counter.tc * TC_SIZE + OFFSET_WPMR)) = 1 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;

        // Enable the interrupt at the core level
        Core::Interrupt interrupt = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::TC00) + counter.tc * N_COUNTERS_PER_TC + counter.n);
        Core::setInterruptHandler(interrupt, &execDelayedHandlerWrapper);
        Core::enableInterrupt(interrupt, INTERRUPT_PRIORITY);

        // IER (Interrupt Enable Register) : enable the CPCS (RC value reached) interrupt
        (*(volatile uint32_t*)(REG + OFFSET_IER0)) = 1 << SR_CPCS;

        // Start the timer
        (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_CLKEN;
        (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_SWTRG;
    }

    void execDelayedHandlerWrapper() {
        // Get the channel which generated the interrupt
        int interrupt = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::TC00);
        int tc = interrupt / N_COUNTERS_PER_TC;
        int counter = interrupt % N_COUNTERS_PER_TC;
        uint32_t REG = TC_BASE + tc * TC_SIZE + counter * OFFSET_COUNTER_SIZE;
        ExecDelayedData& data = execDelayedData[tc][counter];

        (*(volatile uint32_t*)(REG + OFFSET_SR0));

        // Decrease the counters
        if (data.skipPeriods > 0) {
            // If there are still periods to skip, decrease the periods counter
            data.skipPeriods--;

        } else if (data.rest > 0) {
            // Otherwise, if rest > 0, this is the last period : configure the counter with the remaining time
            (*(volatile uint32_t*)(TC_BASE + tc * TC_SIZE + OFFSET_WPMR)) = 0 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
            (*(volatile uint32_t*)(REG + OFFSET_RC0)) = data.rest;
            (*(volatile uint32_t*)(TC_BASE + tc * TC_SIZE + OFFSET_WPMR)) = 1 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
            (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_SWTRG;
            data.rest = 0; // There will be no time remaining after this

        } else {
            // Otherwise, if skipPeriods == 0 and rest == 0, the time has expired

            // Call the user handler
            void (*handler)() = (void (*)())data.handler;
            if (handler) {
                handler();
            }

            // Repeat
            if (data.repeat) {
                // Reset the data structure with their initial value
                data.skipPeriods = data.skipPeriodsReset;
                data.rest = data.restReset;
                (*(volatile uint32_t*)(TC_BASE + tc * TC_SIZE + OFFSET_WPMR)) = 0 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
                (*(volatile uint32_t*)(REG + OFFSET_RC0)) = (data.skipPeriods > 0 ? 0xFFFF : data.rest);
                (*(volatile uint32_t*)(TC_BASE + tc * TC_SIZE + OFFSET_WPMR)) = 1 << WPMR_WPEN | UNLOCK_KEY << WPMR_WPKEY;
                (*(volatile uint32_t*)(REG + OFFSET_CCR0)) = 1 << CCR_SWTRG;

            } else {
                // Disable the interrupt
                (*(volatile uint32_t*)(REG + OFFSET_IDR0)) = 1 << SR_CPCS;
            }
        }
    }

    // Start the counter and reset its value by issuing a software trigger
    void start(Counter counter) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // CCR (Channel Control Register) : issue a software trigger
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_SWTRG;        // SWTRG : software trigger
    }

    // Stop the clock of the given counter and freeze its value.
    // If the output is currently high, it will stay that way. Use disableOutput() if necessary.
    void stop(Counter counter) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // CCR (Channel Control Register) : disable and reenable the clock to stop it
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKDIS;       // CLKDIS : disable the clock
        (*(volatile uint32_t*)(REG + OFFSET_CCR0))
            = 1 << CCR_CLKEN;       // CLKEN : enable the clock
    }

    // Start all the enabled counters simultaneously
    void sync() {
        // BCR (Block Control Register) : issue a sync command
        (*(volatile uint32_t*)(TC_BASE + OFFSET_BCR))
            = 1 << BCR_SYNC;        // SYNC : synch command
    }

    void setPin(Channel channel, PinFunction function, GPIO::Pin pin) {
        checkTC(channel);

        switch (function) {
            case PinFunction::OUT:
                PINS[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line] = pin;
                break;

            case PinFunction::CLK:
                PINS_CLK[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line] = pin;
                break;
        }
    }


}