8 Getting Started with TCA
8.1 Introduction
Author: Catalin Visan, Microchip Technology Inc. |
The AVR® microcontrollers are equipped with powerful timers designed to cover a wide area of applications, from signals measurement to events synchronization and waveforms generation.
The Timer/Counter type A (TCA) is a 16-bit timer that is present in the tinyAVR® 0-series, tinyAVR® 1-series, megaAVR® 0-series, and AVR® DA devices. The main idea behind the TCA is that a very flexible timer is needed to perform convoluted actions as well as the very basic functions of a simple timer. The flexibility comes from the multitude of features provided, such as the possibility of splitting the 16-bit timer into two completely independent 8-bit timers or the built-in Wave Generation modes. Another important characteristic is that the TCA was devised to overcome common problems when using timers, such as the unpredictable behavior of the PWM signal when the duty cycle is changed while the timer is running. The TCA has double-buffered registers that synchronize the updates of different registers, making the waveforms generated predictable in every single situation.
The purpose of this technical brief is to familiarize the reader with some of the operating modes of the TCA, emphasizing this timer’s particularities and to provide initialization code snippets. For a deeper understanding of the functionality, please consult the data sheet. The structure of the document covers three specific use cases:
- Using
Periodic Interrupt Mode:
Initialize the timer to trigger an interrupt every 250 ms, toggling an example GPIO in the Interrupt Service Routine
- Generating a
Dual-Slope PWM Signal:
Initialize the timer to generate a dual-slope 16-bit PWM signal with 1 kHz frequency and 50% duty cycle on a GPIO pin
- Generating
Two PWM Signals in Split Mode:
Initialize the timer in Split mode to generate two single-slope 8-bit PWM signals on two GPIO pins, with independent duty cycle and frequency
8.2 Overview
The flexible 16-bit PWM Timer/Counter type A (TCA) provides accurate program execution timing, frequency and waveform generation, and command execution.
A TCA instance consists of a base counter and three compare channels. The user can set the base counter to count upwards or downwards based on clock ticks (timer) or different events (counter). The Event System can also be used for direction control or to synchronize operations. The period can be adjusted from a specific register as well as the compare thresholds that can be used to generate different waveforms or to trigger events. It is worth mentioning that a prescaler can be used to divide the clock source and also that TCA can operate in Idle sleep mode.
The counter value is compared continuously to zero and the period value. If one of the conditions is met, the control logic block acts according to the configured operation mode, updating the counter and/or generating an interrupt request. The counter is also compared to the Compare registers. Use these comparisons to generate interrupt requests and set the waveform period or the pulse width if a Waveform Generator mode is selected. The Counter, Period and Compare registers and all their buffers are 16 bits wide. The buffers are part of a scheme that ensures the respective registers are updated only when the Counter register is updated. Each buffer has a Buffer Valid (BV) bit used by the logic block to determine if the respective register needs to be updated.
The TCA can be configured to use the Event System and can be utilized to count rising and/or falling edges of the event signal or use it to enable clock ticks counting. Also, the polarity of the event signal can be used to control the direction, low signal for up-counting, and high signal for down-counting. Moreover, the TCA can generate one-cycle strobes on the event channel outputs. The trigger for generating one-cycle strobes on the event channel can be the overflow of the timer, a compare channel match, or an underflow in Split mode.
8.3 Using Periodic Interrupt Mode
A basic use case of the timer is to set it to trigger an interrupt every time it is updated. This mode is useful if a piece of code must be executed repeatedly every few milliseconds. The user must enable the interrupts and set an Interrupt Service Routine (ISR), which will contain the appropriate code. A basic example containing the initialization and an ISR is provided below. The program will toggle a pin every 250 ms using TCA's periodic interrupts. A pin must be configured as an output by setting the corresponding bit of the Direction register before the initialization of the timer as described below. In this case, Port A pin 0 (PA0) was chosen.
- Setting the corresponding bit in the
Interrupt Control register enables the overflow interrupt of
TCA.
TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
- In this mode, no waveform must be
generated, so the Waveform Generation bit field in the CTRLB register must be
configured
accordingly.
TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc;
- Since the timer may count clock
ticks, not events, the CNTEI bit of the EVCTRL register must be set to
‘
0
’. It is worth mentioning that this is the default value of the CNTEI bit.TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm);
- The value written in the Period
register represents the number of clock ticks between the moment when the timer
starts and the moment when the first interrupt is triggered, and also the number of
clock ticks between two consecutive interrupts. It can be deduced from the following
equation. Note: The value written to the Period register will be one less than the desired count because the counting starts from ‘
0
’.Note: The Period register is 16 bits wide. Thus the longest achievable interrupt period with no TCA prescaler is listed below.TCA0.SINGLE.PER = 0x0CB6;
- From the CTRLA register, the
prescaler is set to ‘256’. To start the counter, the user must set the Enable bit in
the same
register.
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc | TCA_SINGLE_ENABLE_bm;
- After the timer is fully configured,
the
sei();
macro enables the global interrupts. Always configure the peripherals when the global interrupts are disabled to avoid problems.In the ISR, the output pin is toggled by setting the corresponding bit in the Input register of the port. Also, the Overflow Interrupt flag is cleared.Tip: Interrupt flags have to be cleared in software by writing ‘1
’ at the respective bit location.ISR(TCA0_OVF_vect) { PORTA.OUTTGL = PIN0_bm; TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; }
An interrupt request is generated when the corresponding interrupt source is enabled, and the Interrupt flag is set. As soon as the flag is set, the microcontroller will start executing the code from the ISR written by the user. The interrupt request remains active until the Interrupt flag is cleared. The parameter of the ISR is the interrupt vector. Therefore, the user can specify what interrupt source the ISR corresponds to. A list of the TCA interrupt vectors is provided below. When programming, it is useful to use the autocomplete function of the IDE to identify the desired interrupt vector. - Port A pin 0 (PA0) is set as output
by writing a ‘
1
’ to the corresponding bit in the Direction register of the port. This GPIO is configured only to obtain a visible output, but it has nothing to do with the TCA instance itself in this mode.PORTA.DIR |= PIN0_bm;
An MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:
8.4 Generating a Dual-Slope PWM Signal
One of the most important characteristics of the TCA when compared to other timers, such as TCB, is the versatility and precision of the PWM generation. The user can choose from various configurations according to the complexity of the application. The TCA can be configured in both Single-Slope and Dual-Slope PWM Generation modes, which permits the trade-off between a constant phase (Correct Phase PWM) and a higher maximum operation frequency (Fast PWM). Also, the TCA has a buffering scheme that ensures a glitch-free PWM.
Both the TCA and TCB can be used to generate a PWM signal with a high maximum operating frequency. Only the TCA can be used in critical applications due to its dual-slope PWM capabilities given by its selectable direction. Dual-slope PWM does not modify the pulse center position when the duty cycle is changed. Thus, the phase is always constant.
The buffering scheme contains a buffer for each Compare register as well as for the Period register. The use of these buffers is essential in critical applications where an unexpected long pulse can lead to a short circuit. Moreover, the presence of these buffers can prevent the loss of synchronization between two peripherals that use the same timer but different compare channels. However, given the fact that the Period and Compare registers can be updated directly, the buffering scheme can be avoided by the user. The following waveforms illustrate the difference between buffered and unbuffered operations.
If the user changes the Period register directly, it is possible that the timer has already passed the new threshold, so it will continue counting to the maximum value. That will cause an unusually long pulse that can cause further problems. Also, if two or more compare channels are used and one of them is updated, the sync between the triggers may be lost. To prevent all these possible problems, the use of the buffering scheme is required. The buffers hold the new value and transfer it to the Compare or the Period register accordingly when the timer is updated. With all values changed at the same time, the problems mentioned disappear.
Below is an example of how to set a TCA instance to generate a 1 kHz PWM signal with a 50% duty cycle using the buffering scheme described above.
- The TCA corresponding register in
Port Multiplexer can be set to route the module outputs to different ports. In this
case, Port A is chosen, which is also the default
port.
PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc;
- The CTRLB register contains the
Enable bits of the compare channels and the bit field that determines the Waveform
Generation mode. In this example, channel 0 is used together with a Dual-Slope PWM
mode.
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_DSBOTTOM_gc;
- Set the CNTEI bit of the EVCTRL
register to ‘
0
’ to set the timer to count clock ticks instead of events. It is worth mentioning that this is the default value of the CNTEI bit.TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm);
- PERBUF is the buffer of the Period
register. It is used to set the frequency of the PWM signal using the following
formula.
TCA0.SINGLE.PERBUF = 0x01A0;
- Also, the Compare register is updated
using its buffer to set the duty cycle. The value in the Compare register is half of
the one in the Period register because a 50% duty cycle is
desired.
TCA0.SINGLE.CMP0BUF = 0x00D0;
- Set the prescaler to 4 by changing
the CLKSEL bit field in the CTRLA register. To start the counter, the user has to
set the Enable bit in the same
register.
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV4_gc | TCA_SINGLE_ENABLE_bm;
- Then, the Port A Pin 0 (PA0) is set
as output by writing a ‘
1
’ to the corresponding bit in the Direction register of the port.PORTA.DIR |= PIN0_bm;
An MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:
8.5 Generating Two PWM Signals in Split Mode
A TCA instance can be split into two completely independent 8-bit timers. This feature provides a high degree of flexibility, being extremely helpful in waveform generation applications. Except for the cases where high accuracy signals are required, most of the applications can be designed using 8-bit signal generators, and the possibility of adding one more generator to the design can be a huge advantage. Though, there are more limitations in the Split mode than the Counter registers dimension. The most important one is that both 8-bit timers have only the down-count option, so the Dual-Slope PWM mode becomes unavailable. Also, the buffering scheme cannot be used, and the timers can no longer count events, only clock ticks. Moreover, there are no interrupts or flags for high-byte Compare registers. Regardless of these limitations, the Split mode can be attractive when there is a need for a high number of timers. The block diagram of the TCA in Split mode is provided below.
This mode will be put forward by generating two PWM signals with different frequencies and different duty cycles.
- The TCA corresponding register in
Port Multiplexer can be set to rout the module outputs to different ports. In this
case, Port A is chosen, which is also the default
port.
PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc;
- Enable the Split mode by setting the
corresponding bit in the CTRLD
register.
TCA0.SPLIT.CTRLD = TCA_SPLIT_SPLITM_bm;
- The CTRLB register contains the
Enable bits of the compare channels. In this example, channel 0 of the lower byte of
the timer and channel 0 of the higher byte of the timer are
used.
TCA0.SPLIT.CTRLB = TCA_SPLIT_HCMP0EN_bm | TCA_SPLIT_LCMP0EN_bm;
- In this mode, the Period register and
the Compare registers are split in half. Each half of the Period register determines
the frequency of the respective PWM signal. Using the desired frequency value, the
Period register value can be deduced from the following formula:
TCA0.SPLIT.LPER = 0xCF; TCA0.SPLIT.HPER = 0x44;
- Each half of the Compare registers
determines the duty cycle of the respective PWM
signal.
TCA0.SPLIT.LCMP0 = 0x68; TCA0.SPLIT.HCMP0 = 0x11;
- From the CTRLA register, the
prescaler is set to 16. To start the counter, the user must set the Enable bit in
the same
register.
TCA0.SPLIT.CTRLA = TCA_SPLIT_CLKSEL_DIV16_gc | TCA_SPLIT_ENABLE_bm;
- The initialization code provided
illustrates a simple way of configuring the TCA in Split mode, but some mentions
must be made. The Single Slope PWM mode is the only Waveform Generation mode
available. Also, it is recommended to stop the timer and to do a hard Reset before
switching from Normal mode to Split mode. An example is provided below. Clear the
Enable bit in the CTRLA register to stop the counter. Then, in the Command bit field
of the CTRLESET register, the user will write the code of the hard Reset
command.
void TCA0_hardReset(void) { TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm); TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc; }
- Then, pins 0 and 3 of Port A (PA0 and
PA3) are set as outputs by writing a ‘
1
’ to each corresponding bit in the Direction register of the port.PORTA.DIR |= PIN0_bm | PIN3_bm;
An MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:
8.6 References
- AVR16EB32 product page: add refference here
- AVR® EB - series Manual
- AVR16EB32 – 32-Pin Data Sheet – AVR® EB - series
- AVR EB Xplained Pro web page: https://www.microchip.com/developmenttools/ProductDetails/atmega4809-xpro
- AVR16EB32 Product Page: www.microchip.com/wwwproducts/en/AVR128DA48
- AVR16EB32 Curiosity Nano Evaluation Kit web page: www.microchip.com/Developmenttools/ProductDetails/DM164151
- AVR16EB16/20/28/32 AVR® EB Family Data Sheet (DS40002183)
- Getting Started with the AVR® EB Family (DS00003429)
8.7 Appendix
Using Periodic Interrupt Mode Full Code Example
#define PERIOD_EXAMPLE_VALUE (0x0CB6) #include <avr/io.h> #include <avr/interrupt.h> /*Using default clock 3.33MHz */ void TCA0_init(void); void PORT_init(void); void TCA0_init(void) { /* enable overflow interrupt */ TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; /* set Normal mode */ TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; /* disable event counting */ TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm); /* set the period */ TCA0.SINGLE.PER = PERIOD_EXAMPLE_VALUE; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc /* set clock source (sys_clk/256) */ | TCA_SINGLE_ENABLE_bm; /* start timer */ } void PORT_init(void) { /* set pin 0 of PORT A as output */ PORTA.DIR |= PIN0_bm; } ISR(TCA0_OVF_vect) { /* Toggle PIN 0 of PORT A */ PORTA.OUTTGL = PIN0_bm; /* The interrupt flag has to be cleared manually */ TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; } int main(void) { PORT_init(); TCA0_init(); /* enable global interrupts */ sei(); while (1) { ; } }
Generating a Dual-Slope PWM Signal Full Code Example
#define PERIOD_EXAMPLE_VALUE (0x01A0) #define DUTY_CYCLE_EXAMPLE_VALUE (0x00D0) #include <avr/io.h> /*Using default clock 3.33MHz */ void TCA0_init(void); void PORT_init(void); void TCA0_init(void) { /* set waveform output on PORT A */ PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc; TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm /* enable compare channel 0 */ | TCA_SINGLE_WGMODE_DSBOTTOM_gc; /* set dual-slope PWM mode */ /* disable event counting */ TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm); /* set PWM frequency and duty cycle (50%) */ TCA0.SINGLE.PERBUF = PERIOD_EXAMPLE_VALUE; TCA0.SINGLE.CMP0BUF = DUTY_CYCLE_EXAMPLE_VALUE; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV4_gc /* set clock source (sys_clk/4) */ | TCA_SINGLE_ENABLE_bm; /* start timer */ } void PORT_init(void) { /* set pin 0 of PORT A as output */ PORTA.DIR |= PIN0_bm; } int main(void) { PORT_init(); TCA0_init(); /* Replace with your application code */ while (1) { ; } }
Generating Two PWM Signals in Split Mode Full Code Example
#define PERIOD_EXAMPLE_VALUE_L (0xCF) #define PERIOD_EXAMPLE_VALUE_H (0x44) #define DUTY_CYCLE_EXAMPLE_VALUE_L (0x68) #define DUTY_CYCLE_EXAMPLE_VALUE_H (0x11) #include <avr/io.h> /*Using default clock 3.33MHz */ void TCA0_init(void); void PIN_init(void); void TCA0_hardReset(void); void TCA0_init(void) { /* set waveform output on PORT A */ PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc; /* enable split mode */ TCA0.SPLIT.CTRLD = TCA_SPLIT_SPLITM_bm; TCA0.SPLIT.CTRLB = TCA_SPLIT_HCMP0EN_bm /* enable compare channel 0 for the higher byte */ | TCA_SPLIT_LCMP0EN_bm; /* enable compare channel 0 for the lower byte */ /* set the PWM frequencies and duty cycles */ TCA0.SPLIT.LPER = PERIOD_EXAMPLE_VALUE_L; TCA0.SPLIT.LCMP0 = DUTY_CYCLE_EXAMPLE_VALUE_L; TCA0.SPLIT.HPER = PERIOD_EXAMPLE_VALUE_H; TCA0.SPLIT.HCMP0 = DUTY_CYCLE_EXAMPLE_VALUE_H; TCA0.SPLIT.CTRLA = TCA_SPLIT_CLKSEL_DIV16_gc /* set clock source (sys_clk/16) */ | TCA_SPLIT_ENABLE_bm; /* start timer */ } void PIN_init(void) { PORTA.DIR |= PIN0_bm /* set pin 0 of PORT A as output */ | PIN3_bm; /* set pin 3 of PORT A as output */ } /* must be used when switching from single mode to split mode */ void TCA0_hardReset(void) { /* stop timer */ TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm); /* force a hard reset */ TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc; } int main(void) { PIN_init(); TCA0_init(); while (1) { ; } }