TC
module reference#include <tc.h>Makefile :
MODULES+=tc
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 :
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.
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 :
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 defaultSourceClock::PBA_OVER_32
SourceClock::PBA_OVER_128
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
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 |
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);
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);
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 :
TC::enablePWM(TC::TC0_0A, 10000, 1500); // 10ms period, 1.5ms high-timeand control the angle with
setHighTime()
between 1000 and 2000TC::enablePWM(TC::TC0_0A, 1000, 0); // 1ms period, 0ms high-time (LED turned off)and control the luminosity with
setDutyCycle()
between 0 and 100For even easier servomotor control, take a look at the Servo
util module.
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.
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
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.
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.
setHighTime()
with the correct value based on the current period.channel
.counter
to measure a signal wired on the channel A input.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.rx
is greater than the maximum value 0xFFFF
, it will be truncated.rc
is greater than the maximum value 0xFFFF
, it will be truncated.counter
. In most cases, this has no advantage over Core::sleep()
and the latter should be used instead.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.Set the GPIO pin used for this port. PinFunction
can be OUT
or CLK
.
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.
#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 setPeriod(Channel channel, 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
#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; _counterOverflowHandler[counter.tc][counter.n] = nullptr; _counterOverflowHandlerEnabled[counter.tc][counter.n] = false; _counterOverflowInternalHandler[counter.tc][counter.n] = nullptr; _rbLoadingHandler[counter.tc][counter.n] = nullptr; _rbLoadingHandlerEnabled[counter.tc][counter.n] = false; _rbLoadingInternalHandler[counter.tc][counter.n] = nullptr; _rcCompareHandler[counter.tc][counter.n] = nullptr; _rcCompareHandlerEnabled[counter.tc][counter.n] = false; _rcCompareInternalHandler[counter.tc][counter.n] = nullptr; _counterModeFullHandler[counter.tc][counter.n] = nullptr; _counterModeFullHandlerEnabled[counter.tc][counter.n] = false; // 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; } } // 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 period in microseconds for both TIOA and TIOB of the specified channel bool setPeriod(Channel channel, float period) { return setPeriod(channel.counter, period); } // 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; } } }