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:

Note: For each of the use cases described in this document, there are two code examples: One bare metal developed on ATmega4809 and one generated with MPLAB® Code Configurator (MCC) developed on AVR128DA48.

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.

Figure 8-86. Timer/Counter Block Diagram

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.

  1. Setting the corresponding bit in the Interrupt Control register enables the overflow interrupt of TCA.
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
    Figure 8-87. Interrupt Control Register
  2. 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;
    Figure 8-88. CTRLB Register
  3. 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);
    Figure 8-89. EVCTRL Register
  4. 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’.
    t i m e T C A I R Q ( s ) = T C A p e r i o d + 1 T C A c l o c k ( H z )
    where the clock of the TCA instance is defined by: T C A c l o c k ( H z ) = f C L K ( H z ) T C A p r e s c a l e r
    and the peripheral clock  f C L K = C L K _ M A I N M a i n c l o c k p r e s c a l e r
    Combining these equations, the following result is obtained:  
    t i m e T C A I R Q ( s ) = ( T C A p e r i o d + 1 ) × T C A p r e s c a l e r f C L K ( H z )
    Note: The Period register is 16 bits wide. Thus the longest achievable interrupt period with no TCA prescaler is listed below.
    t i m e T C A I R Q ( s ) = ( T C A p e r i o d + 1 ) × T C A p r e s c l a e r f C L K ( H z ) = ( 0 x F F F F + 1 ) × 1 3333333 ( H z ) = 19 , 66 × 10 3 s
    Considering the targeted values for this example,
    T C A p e r i o d = t i m e T C A I R Q ( s ) × f C L K ( H z ) T C A p r e s c a l e r 1 = 250 × 10 3 ( s ) × 3333333 ( H z ) 256 1 3254 = 0 x C B 6
    TCA0.SINGLE.PER = 0x0CB6;
  5. 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;
    Figure 8-90. Control A Register
  6. 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;
    }
    Figure 8-91. INTCTRL Register
    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.
    Figure 8-92. Available Interrupt Vectors and Sources in Normal Mode
  7. 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;
Tip: The full code example is also available in the Appendix section.

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.

Figure 8-93. Unbuffered Dual-Slope Operation
Figure 8-94. Changing the Period Using Buffering

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.

  1. 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;
    Figure 8-95. PORTMUX Control for TCA
  2. 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;
    Figure 8-96. CTRLB Register
  3. 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);
    Figure 8-97. EVCTRL Register
  4. PERBUF is the buffer of the Period register. It is used to set the frequency of the PWM signal using the following formula.
    f D S P W M ( H z ) = f C L K ( H z ) 2 × T C A p r e s c a l e r × T C A p e r i o d
    Considering the targeted values for this example,
    T C A p e r i o d = f C L K ( H z ) 2 × T C A p r e s c a l e r × f D S P W M ( H z ) = 3333333 2 × 4 × 1000 416 = 0 x 1 A 0
    TCA0.SINGLE.PERBUF  = 0x01A0;
  5. 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;
  6. 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;
    Figure 8-98. CTRLA Register
  7. 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;
Tip: The full code example is also available in the Appendix section.

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.

Figure 8-99. Timer/Counter Block Diagram Spit Mode

This mode will be put forward by generating two PWM signals with different frequencies and different duty cycles.

  1. 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;
    Figure 8-100. TCAROUTEA Register
  2. Enable the Split mode by setting the corresponding bit in the CTRLD register.
    TCA0.SPLIT.CTRLD = TCA_SPLIT_SPLITM_bm;
    Figure 8-101. CTRLD Register
  3. 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;
    Figure 8-102. CTRLB Register - Split Mode
  4. 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:
    f s s P W M = f C L K ( H z ) T C A p r e s c a l e r × ( T C A p e r i o d + 1 )
    Considering the targeted values for this example,
    T C A p e r i o d 1 = f C L K ( H z ) T C A p r e s c a l e r × f s s P W M 1 ( H z ) 1 = 3333333 16 × 1000 1 207 = 0 x C F
    T C A p e r i o d 2 = f C L K ( H z ) T C A p r e s c a l e r × f s s P W M 2 ( H z ) 1 = 3333333 16 × 3000 1 68 = 0 x 44
    This translates to the following lines of code:
    TCA0.SPLIT.LPER = 0xCF;
    
    TCA0.SPLIT.HPER = 0x44;
  5. Each half of the Compare registers determines the duty cycle of the respective PWM signal.
    TCA0.SPLIT.LCMP0 = 0x68;
    
    TCA0.SPLIT.HCMP0 = 0x11;
  6. 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;
  7. 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; 
    }
  8. 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;  
Tip: The full code example is also available in the Appendix section.

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

Use the following links to find more information about the TCA operation modes: These links are not correct, they must be updated once the web pages will actually exist.
  1. AVR16EB32 product page: add refference here
  2. AVR® EB - series Manual
  3. AVR16EB32 – 32-Pin Data Sheet – AVR® EB - series
  4. AVR EB Xplained Pro web page: https://www.microchip.com/developmenttools/ProductDetails/atmega4809-xpro
  5. AVR16EB32 Product Page: www.microchip.com/wwwproducts/en/AVR128DA48
  6. AVR16EB32 Curiosity Nano Evaluation Kit web page: www.microchip.com/Developmenttools/ProductDetails/DM164151
  7. AVR16EB16/20/28/32 AVR® EB Family Data Sheet (DS40002183)
  8. 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) 
    {
        ;
    }
}