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 :

  • counting events
  • 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)
All of which without any CPU intervention. For example, counting the number of rising edges of a signal without a TC would mean setting up a GPIO interrupt to increment a variable, which can be very CPU-intensive and unreliable for high-frequency signals; with a TC, this can be done reliably in hardware for signals up to a few MHz and the counter can be read at any time.

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

An interrupt can be triggered when the maximum value is reached :

TC::enableSimpleCounterFullInterrupt(counter, onCounterFullHandler);

32-bit mode

Even though the counter is only 16 bits and will overflow after the 65535th count (0xFFFF), the driver implements a mechanism to work around this limit and virtually increase the counter to 32 bits. This is done by using interrupts which automatically handle counter overflows. In order to use this feature, you only need to call enableSimpleCounter() with a maximum value greater than 0xFFFF, for example 0xFFFFFFFF which is the maximum supported in this mode. This is roughly equal to 4 billion and should be more than enough for most application. However, keep in mind that this will set up interrupts in the background which might interfer with your application if you have strict timings requirements or rely heavily on interrupts.

TC::enableSimpleCounter(counter, 0xFFFFFFFF);

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.

Measurement mode

In measurement 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.

TC::enableMeasurement(TC::TC0_0); // Connect the input signal on TC0 A0
Core::sleep(100); // Wait for a few cycles of the input signal
uint32_t period = TC::measuredPeriod(TC::TC0_0);
unsigned int dutyCycle = TC::measuredDutyCycle(TC::TC0_0); // From 0 to 100 percent

When measuring signals, keep in mind the input clock frequency of the counter and the period of the expected signal (see the explaination in the Input Clock paragraph above), and try to match them so that the counter will typically not reach 90% of the counter's maximum value (0xFFFF) while keeping an acceptable resolution. This is because, in order to prevent counter overflows which would lead to incorrect measurements, the driver automatically sets up interrupts to handle such overflows in a similar way as described for the 32-bit Simple Counter mode above. This mechanism allows the measurement of signals in a very wide range of frequencies and duty-cycle with very fine resolution, but triggers interrupts in the background which might interfer with your own code in rare cases. No interrupt will be triggered as long as the signal is in the right frequency range; if you expect the signal to be disconnected, consider disabling the counter with TC::disable(counter); in order to avoid unnecessary interrupts.

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. However, keep in mind that this task is executed from an interrupt context and should be as short as possible.

The driver automatically handles counter overflows when the requested delay is greater than indicated in the table above; however, in order to minimize unnecessary interrupts in the background, consider selecting an appropriate input clock.

TC::execDelayed(TC::TC0_0, myAsynchronousTask, 500, TC::Unit::MILLISECONDS, true, TC::SourceClock::PBA_OVER_128); // Use TC0_0 to call myAsynchronousTask() every 500ms

API

Counter mode

void enableSimpleCounter(Counter counter, uint32_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.

void enableSimpleCounterFullInterrupt(Counter counter, void (*handler)(Counter)=nullptr)
Register an interrupt handler to be called when the counter reaches its maximum value. The handler can be ommited if it has been previously specified and was simply disabled, in order to enable it again.
void disableSimpleCounterFullInterrupt(Counter counter)
Disable the interrupt handler called when the counter has reached its maximum value.

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 enableMeasurement(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.
uint32_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.
uint32_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.

Interrupts

void enableCounterOverflowInterrupt(Counter counter, void (*handler)(Counter)=nullptr)
Register an interrupt handler to be called when the counter overflows. The handler can be ommited if it has been previously specified and was simply disabled, in order to enable it again.
void disableCounterOverflowInterrupt(Counter counter)
Disable the interrupt handler called when the counter overflows.

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. As explained in the Timing mode description above, select the input clock the most appropriate for the given delay to avoid unnecessary interrupts.

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 disable(Counter counter)
Disable the given counter. This should be called before changing the operating mode (PWM, measurement, ...) of the counter.
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, uint32_t maxValue=0xFFFF, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0, bool invertClock=false, bool upDown=false);
    void enableSimpleCounterFullInterrupt(Counter counter, void (*handler)(Counter)=nullptr);
    void disableSimpleCounterFullInterrupt(Counter counter);

    // 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 enableMeasurement(Counter counter, SourceClock sourceClock=SourceClock::PBA_OVER_8, unsigned long sourceClockFrequency=0);
    void measure(Counter counter, bool continuous=false);
    uint32_t measuredPeriodRaw(Counter counter);
    unsigned long measuredPeriod(Counter counter);
    uint32_t measuredHighTimeRaw(Counter counter);
    unsigned long measuredHighTime(Counter counter);
    unsigned int measuredDutyCycle(Counter counter);
    bool isMeasureOverflow(Counter counter);

    // Interrupts
    void enableCounterOverflowInterrupt(Counter counter, void (*handler)(Counter)=nullptr);
    void disableCounterOverflowInterrupt(Counter counter);

    // Low-level counter functions
    bool setRX(Channel channel, unsigned int rx);
    bool setRC(Counter counter, unsigned int rc);
    uint32_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 disable(Counter counter);
    void setPin(Channel channel, PinFunction function, GPIO::Pin pin);

}


#endif

Module

#include "tc.h"
#include "core.h"
#include "pm.h"
#include "scif.h"
#include <string.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_EXTERNAL_CLOCKS_PER_TC];

    // Keep track of pins already enabled in Peripheral mode
    bool _init = false;
    bool _pinsEnabled[MAX_N_TC][N_COUNTERS_PER_TC * N_CHANNELS_PER_COUNTER];
    bool _pinsCLKEnabled[MAX_N_TC][N_EXTERNAL_CLOCKS_PER_TC];

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

    // Interrupts
    void enableInterrupt(Counter counter);
    void interruptHandlerWrapper();
    void (*_counterOverflowHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    bool _counterOverflowHandlerEnabled[MAX_N_TC][N_COUNTERS_PER_TC];
    void (*_counterOverflowInternalHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    void (*_rbLoadingHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    bool _rbLoadingHandlerEnabled[MAX_N_TC][N_COUNTERS_PER_TC];
    void (*_rbLoadingInternalHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    void (*_rcCompareHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    bool _rcCompareHandlerEnabled[MAX_N_TC][N_COUNTERS_PER_TC];
    void (*_rcCompareInternalHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    volatile uint32_t _sr = 0;

    // Simple counter mode
    uint32_t _counterModeMaxValue[MAX_N_TC][N_COUNTERS_PER_TC];
    uint16_t _counterModeMSB[MAX_N_TC][N_COUNTERS_PER_TC];
    void (*_counterModeFullHandler[MAX_N_TC][N_COUNTERS_PER_TC])(Counter counter);
    bool _counterModeFullHandlerEnabled[MAX_N_TC][N_COUNTERS_PER_TC];
    void simpleCounterOverflowHandler(Counter counter);
    void simpleCounterRCCompareHandler(Counter counter);

    // Measurement mode
    uint16_t _periodMSB[MAX_N_TC][N_COUNTERS_PER_TC];
    uint16_t _highTimeMSB[MAX_N_TC][N_COUNTERS_PER_TC];
    uint32_t _periodMSBInternal[MAX_N_TC][N_COUNTERS_PER_TC];
    uint32_t _highTimeMSBInternal[MAX_N_TC][N_COUNTERS_PER_TC];
    void measurementRCCompareHandler(Counter counter);
    void measurementOverflowHandler(Counter counter);
    void measurementRBLoadingHandler(Counter counter);
    const uint16_t MEASUREMENT_RC_TRIGGER = 0xE000;

    // 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 init() {
        if (!_init) {
            memset(_pinsEnabled, 0, sizeof(_pinsEnabled));
            memset(_pinsCLKEnabled, 0, sizeof(_pinsCLKEnabled));
            memset(_countersConfig, 0, sizeof(_countersConfig));
            memset(_counterModeMSB, 0, sizeof(_counterModeMSB));
            memset(_counterOverflowHandler, 0, sizeof(_counterOverflowHandler));
            memset(_counterOverflowHandlerEnabled, 0, sizeof(_counterOverflowHandlerEnabled));
            memset(_counterOverflowInternalHandler, 0, sizeof(_counterOverflowInternalHandler));
            memset(_rbLoadingHandler, 0, sizeof(_rbLoadingHandler));
            memset(_rbLoadingHandlerEnabled, 0, sizeof(_rbLoadingHandlerEnabled));
            memset(_rbLoadingInternalHandler, 0, sizeof(_rbLoadingInternalHandler));
            memset(_rcCompareHandler, 0, sizeof(_rcCompareHandler));
            memset(_rcCompareHandlerEnabled, 0, sizeof(_rcCompareHandlerEnabled));
            memset(_rcCompareInternalHandler, 0, sizeof(_rcCompareInternalHandler));
            memset(_counterModeFullHandler, 0, sizeof(_counterModeFullHandler));
            memset(_counterModeFullHandlerEnabled, 0, sizeof(_counterModeFullHandlerEnabled));
            memset(_periodMSB, 0, sizeof(_periodMSB));
            memset(_highTimeMSB, 0, sizeof(_highTimeMSB));
            memset(_periodMSBInternal, 0, sizeof(_periodMSB));
            memset(_highTimeMSBInternal, 0, sizeof(_highTimeMSB));
            memset(_execDelayedData, 0, sizeof(_execDelayedData));
            _init = true;
        }
    }

    void initCounter(Counter counter, SourceClock sourceClock, unsigned long sourceClockFrequency) {
        init();

        // 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 && !_pinsCLKEnabled[counter.tc][0]) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][0]);
            _pinsCLKEnabled[counter.tc][0] = true;
        } else if (sourceClock == SourceClock::CLK1 && !_pinsCLKEnabled[counter.tc][1]) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][1]);
            _pinsCLKEnabled[counter.tc][1] = true;
        } else if (sourceClock == SourceClock::CLK2 && !_pinsCLKEnabled[counter.tc][2]) {
            GPIO::enablePeripheral(PINS_CLK[counter.tc][2]);
            _pinsCLKEnabled[counter.tc][2] = true;
        }
    }

    void disable(Counter counter) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Stop the counter
        Channel channel = {
            .counter = counter,
            .line = TIOA
        };
        setRX(channel, 0);
        channel.line = TIOB;
        setRX(channel, 0);
        setRC(counter, 0);
        stop(counter);

        // Disable the interrupts
        (*(volatile uint32_t*)(REG + OFFSET_IDR0)) = 0xFFFFFFFF;
        memset(_counterOverflowHandler, 0, sizeof(_counterOverflowHandler));
        memset(_counterOverflowHandlerEnabled, 0, sizeof(_counterOverflowHandlerEnabled));
        memset(_counterOverflowInternalHandler, 0, sizeof(_counterOverflowInternalHandler));
        memset(_rbLoadingHandler, 0, sizeof(_rbLoadingHandler));
        memset(_rbLoadingHandlerEnabled, 0, sizeof(_rbLoadingHandlerEnabled));
        memset(_rbLoadingInternalHandler, 0, sizeof(_rbLoadingInternalHandler));
        memset(_rcCompareHandler, 0, sizeof(_rcCompareHandler));
        memset(_rcCompareHandlerEnabled, 0, sizeof(_rcCompareHandlerEnabled));
        memset(_rcCompareInternalHandler, 0, sizeof(_rcCompareInternalHandler));
        memset(_counterModeFullHandler, 0, sizeof(_counterModeFullHandler));
        memset(_counterModeFullHandlerEnabled, 0, sizeof(_counterModeFullHandlerEnabled));

        // Disable the output pins
        for (int i = 0; i < N_CHANNELS_PER_COUNTER; i++) {
            if (_pinsEnabled[counter.tc][N_CHANNELS_PER_COUNTER * counter.n + i]) {
                GPIO::disablePeripheral(PINS[counter.tc][N_CHANNELS_PER_COUNTER * counter.n + i]);
                _pinsEnabled[counter.tc][N_CHANNELS_PER_COUNTER * counter.n + i] = false;
            }
        }

        // Disable the external input clock pin
        if (_pinsCLKEnabled[counter.tc][counter.n]) {
            GPIO::disablePeripheral(PINS_CLK[counter.tc][counter.n]);
            _pinsCLKEnabled[counter.tc][counter.n] = false;
        }

        // Disable the module clock
        PM::disablePeripheralClock(PM::CLK_TC0 + counter.tc);
    }


    // Simple counter mode

    void enableSimpleCounter(Counter counter, uint32_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;

        // When up-down mode is enabled the counter is limited to 16 bits
        if (upDown && maxValue > 0xFFFF) {
            maxValue = 0xFFFF;
        }

        // 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

        // Reset the MSB of the counter
        _counterModeMSB[counter.tc][counter.n] = 0;

        // Save the max value
        _counterModeMaxValue[counter.tc][counter.n] = maxValue;

        // Set the RC register with the low 16 bits of the max value
        (*(volatile uint32_t*)(REG + OFFSET_RC0)) = maxValue & 0xFFFF;

        // Automatically enable 32-bit mode when maxValue does not fit on 16-bit
        uint8_t wavesel = 0;
        if (maxValue > 0xFFFF) {
            // Enable the Counter Overflow interrupt
            _counterOverflowInternalHandler[counter.tc][counter.n] = simpleCounterOverflowHandler;
            enableInterrupt(counter);
            (*(volatile uint32_t*)(REG + OFFSET_IER0))
                = 1 << SR_COVFS;    // SR_COVFS : counter overflow status

            // Disable automatic trigger on RC compare
            wavesel = 0;

        } else {
            // Enable automatic trigger on RC compare
            wavesel = upDown ? 3 : 2;
        }

        // 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
            | wavesel << CMR_WAVSEL   // WAVSEL : UP or UP/DOWN mode with or without automatic trigger on RC compare
            | 1 << CMR_WAVE;          // WAVE : waveform generation mode

        // 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);
    }

    // Register an interrupt to be called when the max value of the counter has been reached
    void enableSimpleCounterFullInterrupt(Counter counter, void (*handler)(Counter)) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Save the handler and mark it as enabled
        if (handler != nullptr) {
            _counterModeFullHandler[counter.tc][counter.n] = handler;
        }
        _counterModeFullHandlerEnabled[counter.tc][counter.n] = true;

        // If maxValue > 0xFFFF, interrupts are already handled by the 32-bit counter mode and
        // the RC Compare interrupt will be enabled as needed by simpleCounterOverflowHandler()
        if (_counterModeMaxValue[counter.tc][counter.n] <= 0xFFFF) {
            // Enable the interrupts in this counter
            enableInterrupt(counter);

            // Enable the RC Compare interrupt
            _rcCompareInternalHandler[counter.tc][counter.n] = simpleCounterRCCompareHandler;
            (*(volatile uint32_t*)(REG + OFFSET_IER0))
                = 1 << SR_CPCS;    // SR_CPCS : RC compare status
        }
    }

    // Disable the Counter Full interrupt
    void disableSimpleCounterFullInterrupt(Counter counter) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Disable the handler
        _counterModeFullHandlerEnabled[counter.tc][counter.n] = false;

        // Make sure the interrupt is not used internally before disabling it
        if (_rcCompareInternalHandler[counter.tc][counter.n] == nullptr) {
            // IDR (Interrupt Disable Register) : disable the interrupt
            (*(volatile uint32_t*)(REG + OFFSET_IDR0))
                = 1 << SR_CPCS;    // SR_CPCS : RC compare status
        }
    }

    // Internal handler to handle 32-bit mode
    void simpleCounterOverflowHandler(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Increment the MSB of the counter
        _counterModeMSB[counter.tc][counter.n]++;

        // If the max value can be reached within the span of the next 16-bit counter, enable automatic trigger on RC compare
        if (_counterModeMSB[counter.tc][counter.n] == _counterModeMaxValue[counter.tc][counter.n] >> 16) {
            // 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) : enable automatic trigger on RC compare
            (*(volatile uint32_t*)(REG + OFFSET_CMR0)) |= 2 << CMR_WAVSEL;

            // Disable counter overflow interrupt
            _counterOverflowInternalHandler[counter.tc][counter.n] = nullptr;
            if (!_counterOverflowHandlerEnabled[counter.tc][counter.n]) {
                // IDR (Interrupt Disable Register) : disable the interrupt
                (*(volatile uint32_t*)(REG + OFFSET_IDR0))
                    = 1 << SR_COVFS;    // SR_COVFS : counter overflow status
            }

            // Enable the RC Compare interrupt
            _rcCompareInternalHandler[counter.tc][counter.n] = simpleCounterRCCompareHandler;
            (*(volatile uint32_t*)(REG + OFFSET_IER0))
                = 1 << SR_CPCS;    // SR_CPCS : RC compare status

            // 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
        }
    }

    // Internal handler to handle 32-bit mode
    void simpleCounterRCCompareHandler(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // 32-bit mode
        if (_counterModeMaxValue[counter.tc][counter.n] > 0xFFFF) {
            // Counter has reached its max value, reset the MSB of the counter
            _counterModeMSB[counter.tc][counter.n] = 0;

            // Disable RC compare interrupt
            _rcCompareInternalHandler[counter.tc][counter.n] = nullptr;
            if (!_rcCompareHandlerEnabled[counter.tc][counter.n]) {
                // IDR (Interrupt Disable Register) : disable the interrupt
                (*(volatile uint32_t*)(REG + OFFSET_IDR0))
                    = 1 << SR_CPCS;    // SR_CPCS : RC compare status
            }

            // Enable the Counter Overflow interrupt
            _counterOverflowInternalHandler[counter.tc][counter.n] = simpleCounterOverflowHandler;
            enableInterrupt(counter);
            (*(volatile uint32_t*)(REG + OFFSET_IER0))
                = 1 << SR_COVFS;    // SR_COVFS : counter overflow status

            // 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) : disable automatic trigger on RC compare
            (*(volatile uint32_t*)(REG + OFFSET_CMR0)) &= ~(uint32_t)(0b11 << CMR_WAVSEL);

            // 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
        }

        // If the Counter Full interrupt has been enabled by the user, call the registered handler
        if (_counterModeFullHandler[counter.tc][counter.n] != nullptr && _counterModeFullHandlerEnabled[counter.tc][counter.n]) {
            _counterModeFullHandler[counter.tc][counter.n](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
        uint32_t cmr = (*(volatile uint32_t*)(REG + OFFSET_CMR0)) & 0xFFFF0000; // Keep the current config of the A and B lines
        cmr = cmr
            | (static_cast<int>(sourceClock) & 0b111) << CMR_TCCLKS // TCCLKS : clock selection
            | 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
        if (channel.line == TIOA) {
            cmr &= 0xFF00FFFF;       // Erase current config for channel A
            cmr |= 2 << CMR_ACPA     // ACPA : RA/TIOA : clear
                |  1 << CMR_ACPC     // ACPC : RC/TIOA : set
                |  2 << CMR_ASWTRG;  // ASWTRG : SoftwareTrigger/TIOA : clear
        } else { // TIOB
            cmr &= 0x00FFFFFF;       // Erase current config for channel B
            cmr |= 2 << CMR_BCPB     // BCPA : RA/TIOB : clear
                |  1 << CMR_BCPC     // BCPC : RC/TIOB : set
                |  2 << CMR_BSWTRG;  // BSWTRG : SoftwareTrigger/TIOB : clear
        }
        (*(volatile uint32_t*)(REG + OFFSET_CMR0)) = cmr;

        // 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

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

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

        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);
        if (!_pinsEnabled[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line]) {
            GPIO::enablePeripheral(PINS[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line]);
            _pinsEnabled[channel.counter.tc][N_CHANNELS_PER_COUNTER * channel.counter.n + channel.line] = true;
        }
    }

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


    // Measure mode

    void enableMeasurement(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

        // Reset RA and RB
        (*(volatile uint32_t*)(REG + OFFSET_RA0)) = 0;
        (*(volatile uint32_t*)(REG + OFFSET_RB0)) = 0;

        // 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
        if (!_pinsEnabled[counter.tc][N_CHANNELS_PER_COUNTER * counter.n]) {
            GPIO::enablePeripheral(PINS[counter.tc][N_CHANNELS_PER_COUNTER * counter.n]);
            _pinsEnabled[counter.tc][N_CHANNELS_PER_COUNTER * counter.n] = true;
        }
    }

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

        // Enable the Counter Overflow interrupt
        _counterOverflowInternalHandler[counter.tc][counter.n] = measurementOverflowHandler;
        _rbLoadingInternalHandler[counter.tc][counter.n] = measurementRBLoadingHandler;
        enableInterrupt(counter);
        (*(volatile uint32_t*)(REG + OFFSET_IER0))
            = 1 << SR_COVFS;    // SR_COVFS : counter overflow status

        // Enable the RC Compare interrupt
        // RC is set to trigger an interrupt when the counter reaches about 90% of its
        // max value, which will enable the RB Loading interrupt. This is used to prevent
        // a race condition that can happen when the rising edge of the measured signal
        // happens very close to the Counter Overflow event, which could mask the rising
        // edge (RB Loading) event and produce erroneous values.
        // This is a good compromise instead of always enabling the RB Loading interrupt,
        // which would be uselessly CPU-intensive when measuring high-frequency signals.
        // For applications relying heavily on interrupts with priority higher than TC,
        // it might be a good idea to lower MEASUREMENT_RC_TRIGGER to make sure no rising
        // edge will be missed.
        // However, if low-frequency signals are expected, consider lowering the SourceClock
        // frequency in enableMeasurement() in order to avoid counter overflows altogether.
        (*(volatile uint32_t*)(REG + OFFSET_RC0)) = MEASUREMENT_RC_TRIGGER;
        _rcCompareInternalHandler[counter.tc][counter.n] = measurementRCCompareHandler;
        (*(volatile uint32_t*)(REG + OFFSET_IER0))
            = 1 << SR_CPCS;    // SR_CPCS : RC compare status

        // 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
    }

    void measurementRCCompareHandler(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Enable the RB Loading interrupt to catch the next rising edge
        (*(volatile uint32_t*)(REG + OFFSET_IER0))
            = 1 << SR_LDRBS;    // SR_LDRBS : RB loading status
    }

    void measurementOverflowHandler(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Enable the RB Loading interrupt to catch the next rising edge
        (*(volatile uint32_t*)(REG + OFFSET_IER0))
            = 1 << SR_LDRBS;    // SR_LDRBS : RB loading status

        // Increment the MSB of the period
        _periodMSBInternal[counter.tc][counter.n]++;

        // If the signal is high, increment the MSB of the high-time
        if (_sr & (1 << SR_MTIOA)) {
            _highTimeMSBInternal[counter.tc][counter.n]++;
        }
    }

    void measurementRBLoadingHandler(Counter counter) {
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Cache the internal MSB buffers
        _periodMSB[counter.tc][counter.n] = _periodMSBInternal[counter.tc][counter.n];
        _highTimeMSB[counter.tc][counter.n] = _highTimeMSBInternal[counter.tc][counter.n];

        // Reset the internal MSB buffers
        _periodMSBInternal[counter.tc][counter.n] = 0;
        _highTimeMSBInternal[counter.tc][counter.n] = 0;

        // Disable the RB Loading interrupt
        (*(volatile uint32_t*)(REG + OFFSET_IDR0))
            = 1 << SR_LDRBS;    // SR_LDRBS : RB loading status
    }

    uint32_t measuredPeriodRaw(Counter counter) {
        return _periodMSB[counter.tc][counter.n] << 16 | rbValue(counter);
    }

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

    uint32_t measuredHighTimeRaw(Counter counter) {
        return _highTimeMSB[counter.tc][counter.n] << 16 | 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) {
        // TODO
        //uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;
        //return ((*(volatile uint32_t*)(REG + OFFSET_SR0)) >> SR_COVFS) & 1;
        return false;
    }


    // Interrupts

    // Enable the interrupts for the given counter at the core level
    void enableInterrupt(Counter counter) {
        checkTC(counter);

        Core::Interrupt interrupt = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::TC00) + counter.tc * N_COUNTERS_PER_TC + counter.n);
        Core::setInterruptHandler(interrupt, &interruptHandlerWrapper);
        Core::enableInterrupt(interrupt, INTERRUPT_PRIORITY);
    }

    // Enable the Counter Overflow interrupt on the given counter
    void enableCounterOverflowInterrupt(Counter counter, void (*handler)(Counter)) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Save the user handler and mark it as enabled
        if (handler != nullptr) {
            _counterOverflowHandler[counter.tc][counter.n] = handler;
        }
        _counterOverflowHandlerEnabled[counter.tc][counter.n] = true;

        // Enable the interrupts in this counter
        enableInterrupt(counter);

        // IER (Interrupt Enable Register) : enable the interrupt
        (*(volatile uint32_t*)(REG + OFFSET_IER0))
            = 1 << SR_COVFS;    // SR_COVFS : counter overflow status
    }

    // Disable the Counter Overflow interrupt on the given counter
    void disableCounterOverflowInterrupt(Counter counter) {
        checkTC(counter);
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Mark the handler as disabled
        _counterOverflowHandlerEnabled[counter.tc][counter.n] = false;

        // Make sure the interrupt is not used internally before disabling it
        if (_counterOverflowInternalHandler[counter.tc][counter.n] == nullptr) {
            // IDR (Interrupt Disable Register) : disable the interrupt
            (*(volatile uint32_t*)(REG + OFFSET_IDR0))
                = 1 << SR_COVFS;    // SR_COVFS : counter overflow status
        }
    }

    // Internal interrupt handler wrapper
    void interruptHandlerWrapper() {
        // Get the channel which generated the interrupt
        int interrupt = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::TC00);
        Counter counter = {
            .tc = static_cast<uint8_t>(interrupt / N_COUNTERS_PER_TC),
            .n = static_cast<uint8_t>(interrupt % N_COUNTERS_PER_TC)
        };
        uint32_t REG = TC_BASE + counter.tc * TC_SIZE + counter.n * OFFSET_COUNTER_SIZE;

        // Save the SR (Status Register) in order to read it only once, because each read clears
        // most of the interrupt bits
        _sr = (*(volatile uint32_t*)(REG + OFFSET_SR0));

        // Get the triggered interrupts from SR and IMR (Interrupt Mask Register)
        uint32_t interrupts = _sr & (*(volatile uint32_t*)(REG + OFFSET_IMR0));

        // RC Compare
        if (interrupts & (1 << SR_CPCS)) {
            // Call the internal handler if one has been registered
            if (_rcCompareInternalHandler[counter.tc][counter.n] != nullptr) {
                _rcCompareInternalHandler[counter.tc][counter.n](counter);
            }

            // Call the user handler if one has been registered and enabled
            if (_rcCompareHandler[counter.tc][counter.n] != nullptr && _rcCompareHandlerEnabled[counter.tc][counter.n]) {
                _rcCompareHandler[counter.tc][counter.n](counter);
            }
        }

        // Counter Overflow
        if (interrupts & (1 << SR_COVFS)) {
            // Call the internal handler if one has been registered
            if (_counterOverflowInternalHandler[counter.tc][counter.n] != nullptr) {
                _counterOverflowInternalHandler[counter.tc][counter.n](counter);
            }

            // Call the user handler if one has been registered and enabled
            if (_counterOverflowHandler[counter.tc][counter.n] != nullptr && _counterOverflowHandlerEnabled[counter.tc][counter.n]) {
                _counterOverflowHandler[counter.tc][counter.n](counter);
            }
        }

        // RB Loading
        if (interrupts & (1 << SR_LDRBS)) {
            // Call the internal handler if one has been registered
            if (_rbLoadingInternalHandler[counter.tc][counter.n] != nullptr) {
                _rbLoadingInternalHandler[counter.tc][counter.n](counter);
            }

            // Call the user handler if one has been registered and enabled
            if (_rbLoadingHandler[counter.tc][counter.n] != nullptr && _rbLoadingHandlerEnabled[counter.tc][counter.n]) {
                _rbLoadingHandler[counter.tc][counter.n](counter);
            }
        }
    }


    // 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
    uint32_t counterValue(Counter counter) {
        checkTC(counter);
        return ((uint32_t)_counterModeMSB[counter.tc][counter.n] << 16)
             | (*(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;

        // Reset the MSB of the counter
        _counterModeMSB[counter.tc][counter.n] = 0;

        // 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][channel.counter.n] = pin;
                break;
        }
    }


}