10 Getting Started with TCD

10.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 signal measurement to events synchronization and waveform generation.

The Timer/Counter type D (TCD) is a 12-bit timer available in tinyAVR® 0- and 1-series, megaAVR® 0-series and AVR® Dx and AVR DA devices. The TCD is designed to cover the need for a fast timer with multiple waveform generation capabilities in common embedded applications, such as motor control or Switch mode power supplies. One of the key features of the TCD is that in addition to running from the main clock, it can be set up to run directly from the internal 20 MHz oscillator (OSC20M) or used with an external clock source. Moreover, it has multiple configurable Waveform Generation modes.

This document describes some of the TCD operating modes, emphasizing this timer’s particularities and providing initialization code snippets. For a deeper understanding of the functionality, refer to the tinyAVR® 0- and 1-series, megaAVR® 0-series and AVR® Dx and AVR DA data sheet. The structure of the document comprises two specific use cases:
Note: For each of the use cases described in this document, there are two code examples: One bare metal developed on ATtiny817, and one generated with MPLAB® Code Configurator (MCC) developed on AVR128DA48.

10.2 Overview

The TCD is a high-performance waveform controller that consists of an asynchronous counter, a prescaler, compare logic, capture logic, and control logic. The TCD contains a counter that can run on a clock that is asynchronous to the system clock. It contains compare logic that can generate two independent outputs with optional dead-time. It is connected to the event system for capture and deterministic fault control. The timer/counter can generate interrupts and events on compare match and overflow.
Figure 10-118. TCD Block Diagram

The TCD core is asynchronous to the system clock. The timer/counter consist of two compare/capture units, each with a separate waveform output. In addition, there are two extra waveform outputs that can be equal to the output from one of the units. The compare registers CMPxSET and CMPxCLR are stored in the respective registers (TCD.CMPxSET, TCD.CMPxCLR), which consist of both a low and a high byte. The registers are synchronized to the TCD domain after writing to the registers. During normal operation, the counter value is continuously compared to the compare registers. This is used to generate both interrupts and events.

The TCD can use the input events in ten different input modes, selected separately for the two input events (see Figure 10-129). The input mode defines how the input event will affect the outputs.

10.3 Generating Complementary Driving Signals

The 8-bit microcontrollers are commonly used in Switch mode power supplies. This use case shows how the TCD can be configured to generate complementary waveforms for the power MOSFET transistors. In this example, the TCD instance is configured to generate two complementary signals with 50 kHz frequency and 100 ns dead-time.
Figure 10-119. Dual Slope Mode
  • The maximum counter value is stored in the CMPBLCR register (see Figure 10-119)
  • The WOA output is set when the TCD counter counts down and matches the CMPASET value
  • WOA is cleared when the TCD counter counts up and matches the CMPASET value
  • The WOB output is set when the TCD counter counts up and matches the CMPBSET value
  • WOB is cleared when the TCD counter counts down and matches the CMPBSET value
  1. The Waveform Generation mode will be set to dual slope using the CTRLB register.
    TCD0.CTRLB = TCD_WGMODE_DS_gc;
    Figure 10-120. CTRLB Register
  2. The signal’s period must be deduced from the following formula and written to the CMPBCLR register.
    f s i g n a l = f C L K P r e s c a l e r C N T × 2 × ( C M P B C L R + 1 )
    The peripheral clock will be set as the 20 MHz internal oscillator and the counter prescaler will be set to ‘1’.
    C M P B C L R = f C L K P r e s c a l e r C N T × 2 × f s i g n a l 1 = 20 × 10 6 1 × 2 × 50 × 10 3 1 200 = 0 x C 8
    TCD0.CMPBCLR = 0xC8;
  3. To set the dead-time, the counting period must be determined, as well as the number of counting periods that match the dead-time.
    C N T p e r i o d = 1 f C N T = Pr e s c a l e r C N T f C L K = 1 20 × 10 6 = 50 n s
    D e a d t i m e = 2 × C N T p e r i o d = 100 n s
    Assuming the user wants both signals to have the same duty cycle.
    C M P B S E T = C M P B C L R 2 + D e a d t i m e 2 = 0 x 65
    C M P A S E T = C M P B C L R 2 D e a d t i m e 2 = 0 x 63
    TCD0.CMPBSET = 0x65;
    
    TCD0.CMPASET = 0x63;
    
  4. Before enabling the timer, the ENRDY bit of the STATUS register must be verified to have the value ‘1’.
    The hardware sets this bit when it is safe to start the timer. This mechanism prevents synchronization issues between the TCD time domain and the system time domain.
    while(!(TCD0.STATUS & TCD_ENRDY_bm))
    {
        ;
    }
    Figure 10-121. STATUS Register
  5. After the ENRDY bit is set, the timer can be enabled from the CTRLA register.
    From the same register, the clock source and the prescaler can also be set. All the bits from CTRLA except the ENABLE bit are enable-protected, they can only be written when ENABLE is set to ‘0’ before the writing operation.
    TCD0.CTRLA = TCD_CLKSEL_20MHZ_gc	
               | TCD_CNTPRES_DIV1_gc
               | TCD_ENABLE_bm;
    
    Figure 10-122. CTRLA Register
  6. To enable the output channels, certain bits in the FAULTCTRL register may be set.
    This register is under Configuration Change Protection (CCP). Thus, a key must be written in the CCP register of the CPU before writing to the FAULTCTRL register. In this case, only channels A and B will be enabled.
    void TCD0_enableOutputChannels(void)
    {
            CPU_CCP = CCP_IOREG_gc;
    
            TCD0.FAULTCTRL = TCD_CMPAEN_bm 
                           | TCD_CMPBEN_bm;
    }
    Figure 10-123. FAULTCTRL Register
    Info: After a Reset, the FAULTCTRL register loads its value from the fuses.
  7. In the targeted microcontroller, the TCD output channels A and B are linked to PA4 and PA5. These pins must be configured as outputs:
    PORTA.DIR |= PIN4_bm       
              | PIN5_bm; 
This application will configure the TCD instance to generate two complementary signals with 50 kHz frequency and 100 ns dead-time.
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:

10.4 Controlling Synchronous Signals Using Input Events

In motor control applications, it is often necessary to use perfectly synchronized signals. Also, there is a narrow class of applications that, for driving purposes, requires just a little bit more current than a pin can provide. In these cases, the possibility to map the same signal on multiple pins can reduce the bill of materials.

In most embedded systems there are different feedback loops and fault control components that ensure everything works properly. For reliability reasons, it is better to design these feedback mechanisms without the use of the MCU core. TCD has two event inputs that can be used to monitor the activity of other peripherals and components, and alter the output accordingly.

In this example, the TCD instance will be configured to generate four PWM signals with 10 kHz frequency and approximately 50% duty cycle, synchronized in pairs. In case of a fault signal on an input channel, the timer will stop and wait until the signal changes to the Safe state (no fault detected).

  1. The timer will be configured from the CTRLB register (as in the previous use case), but this time in the Four Ramp mode:
    TCD0.CTRLB = TCD_WGMODE_FOURRAMP_gc;
    Figure 10-124. CTRLB Register
    Figure 10-125. Four Ramp Mode
  2. By default, channels C and D are linked to channel A. For the signals to be synchronized in pairs, the CMPDSEL bit from the CTRLC register must be set to link channel D to channel B (see Figure 10-127):
    TCD0.CTRLC = TCD_CMPDSEL_bm;
    Figure 10-126. CTRLC Register
    Figure 10-127. Full-Bridge Output
  3. The frequency can be computed using the following formula:
    f P W M = f C L K P r e s c a l e r C N T × ( C M P A S E T + C M P A C L R + C M P B S E T + C M P B C L R + 4 )
    C M P A S E T + C M P A C L R + C M P B S E T + C M P B C L R = f C L K f Pr e s c a l e r × f P W M 4 =
    = 20000000 4 × 10000 4 = 496 = 0 x 1 F 0
    The targeted duty cycle is approximately 50%, so CMPACLR = CMPBCLR. For most applications, there is a settling time. Thus, the user must consider that and manually configure the peripheral to include a little dead-time. So CMPASET = CMPBSET = 2 for 0.4 µs settling time:
    C M P A C L R = C M P B C L R = 0 x 1 F 0 C M P A S E T C M P B S E T 2 = 0 x 1 F 0 2 2 2 = 0 x F 6
    TCD0.CMPASET = 0x02;				
    
    TCD0.CMPACLR = 0xF6; 
    
    TCD0.CMPBSET = 0x02;				
    
    TCD0.CMPBCLR = 0xF6;
  4. For fault detection purposes, input channel A of the timer will be configured to be active-low and the digital filter of the channel will be enabled to filter the potential spikes (see Figure 10-128).
    For this use case, the fault signal will be externally triggered by the press of a button, connected to pin PC5:
    TCD0.EVCTRLA = TCD_CFG_FILTER_gc	
                 | TCD_EDGE_FALL_LOW_gc
                 | TCD_TRIGEI_bm;
    Figure 10-128. EVCTRL Register
  5. The timer can be configured to respond to the input signal in various ways, using the INPUTCTRLA register.
    In this case, when a falling edge is detected on channel A, the timer is stopped and reset. The counter will restart at the next rising edge of the input signal.
    Figure 10-129. INPUTCTRLA Register
  6. The ENRDY bit of the STATUS register must be ‘1’ before starting the timer from the CTRLA register.
    Also, the 20 MHz clock is selected from CTRLA together with a prescaler of 4.
    while(!(TCD0.STATUS & TCD_ENRDY_bm))
    {
        ;
    }
    
    TCD0.CTRLA = TCD_CLKSEL_20MHZ_gc | TCD_CNTPRES_DIV4_gc | TCD_ENABLE_bm;
    In this example, all four output channels are used. The procedure is detailed within the previous use case.
    void TCD0_enableOutputChannels(void)
    {
            CPU_CCP = CCP_IOREG_gc;		
    
            TCD0.FAULTCTRL = TCD_CMPAEN_bm | TCD_CMPBEN_bm | TCD_CMPCEN_bm | TCD_CMPDEN_bm;
    }
  7. The event system needs to be configured to link the event generator to the TCD instance.
    In this case, the fault signal is simulated by pressing a button that is connected to PC5. The initialization code is provided below, but the event system configuration details are beyond the scope of this document.
    void EVENT_SYSTEM_init(void)
    {
            EVSYS.ASYNCCH2 = EVSYS_ASYNCCH2_PORTC_PIN5_gc;
    
            EVSYS.ASYNCUSER6 = EVSYS_ASYNCUSER6_ASYNCCH2_gc;
    }
  8. Channels A and B are linked with PA4 and PA5 pins, and channels C and D are linked with PC0 and PC1 pins. These pins must be configured as outputs.

    PC5, which is configured as input, is connected to the ATtiny817 Xplained Mini user button and has an internal pull-up resistor enabled.

    A pull-up resistor provides a default state of ‘1’ to the pin. Thus, the button will be used to drive the pin low (logical ‘0’) when it is pressed.
    PORTA.DIR |= PIN4_bm      
              | PIN5_bm;    
    
    PORTC.DIR |= PIN0_bm      
              | PIN1_bm;        
    
    PORTC.DIR &= ~PIN5_bm;      
    
    PORTC.PIN5CTRL = PORT_PULLUPEN_bm;  

This application will configure the TCD instance to generate four PWM signals with 10 kHz frequency at an approximately 50% duty cycle, synchronized in pairs.

Moreover, when a fault signal appears on an input channel, the timer will stop and wait until the signal changes to the Safe state.
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:

10.5 References

10.6 Appendix

Generating Complementary Driving Samples Source Code

#define SIGNAL_PERIOD_EXAMPLE_VALUE            (0xC8)
#define SIGNAL_DUTY_CYCLE_EXAMPLE_VALUE        (0x64)

#include <avr/io.h>
/*Using default clock 3.3 MHz */

void TCD0_init(void);
void TCD0_enableOutputChannels(void);
void PORT_init(void);

void TCD0_init(void)
{    
    /* set the waveform mode */
    TCD0.CTRLB = TCD_WGMODE_DS_gc;
    
    /* set the signal period */
    TCD0.CMPBCLR = SIGNAL_PERIOD_EXAMPLE_VALUE;    
    
    /* the signals are alternatively active and a small symmetric dead time is needed */
    TCD0.CMPBSET = SIGNAL_DUTY_CYCLE_EXAMPLE_VALUE + 1;   
    TCD0.CMPASET = SIGNAL_DUTY_CYCLE_EXAMPLE_VALUE - 1;
    
    /* ensure the ENRDY bit is set */
    while(!(TCD0.STATUS & TCD_ENRDY_bm))
    {
        ;
    }
    
    TCD0.CTRLA = TCD_CLKSEL_20MHZ_gc        /* choose the timer’s clock */
               | TCD_CNTPRES_DIV1_gc        /* choose the prescaler */
               | TCD_ENABLE_bm;             /* enable the timer */
}

void TCD0_enableOutputChannels(void)
{
    /* enable write-protected register */
    CPU_CCP = CCP_IOREG_gc;    
    
    TCD0.FAULTCTRL = TCD_CMPAEN_bm          /* enable channel A */
                   | TCD_CMPBEN_bm;         /* enable channel B */
}

void PORT_init(void)
{
    PORTA.DIR |= PIN4_bm                    /* set pin 4 as output */
              | PIN5_bm;                    /* set pin 5 as output */
}

int main(void)
{
    PORT_init();
    
    TCD0_enableOutputChannels();
    
    TCD0_init();
    
    /* Replace with your application code */
    while (1) 
    {
        ;
    }
}

Controlling Synchronous Signals Using Input Events Source Code

#define SETTLING_TIME_EXAMPLE_VALUE        (0x02)
#define DUTY_CYCLE_EXAMPLE_VALUE           (0xF6)

#include <avr/io.h>
/*Using default clock 3.3 MHz */

void TCD0_init(void);
void TCD0_enableOutputChannels(void);
void EVENT_SYSTEM_init(void);
void PORT_init(void);

void TCD0_init(void)
{
    /* set the waveform mode */
    TCD0.CTRLB = TCD_WGMODE_FOURRAMP_gc;
    
    /* set channel D to match channel B */
    TCD0.CTRLC = TCD_CMPDSEL_bm;        
    
    /* set the settling time and duty cycle for the signals*/    
    TCD0.CMPASET = SETTLING_TIME_EXAMPLE_VALUE;    
    TCD0.CMPACLR = DUTY_CYCLE_EXAMPLE_VALUE;                    
    TCD0.CMPBSET = SETTLING_TIME_EXAMPLE_VALUE;                    
    TCD0.CMPBCLR = DUTY_CYCLE_EXAMPLE_VALUE;                
    
    TCD0.EVCTRLA = TCD_CFG_FILTER_gc            /* set the anti-spike filter */
                 | TCD_EDGE_FALL_LOW_gc         /* set the ‘fault’ state */
                 | TCD_TRIGEI_bm;               /* enable input channel A */
    
    /* set the input mode */             
    TCD0.INPUTCTRLA = TCD_INPUTMODE_WAIT_gc;
    
    /* ensure the ENRDY bit is set */
    while(!(TCD0.STATUS & TCD_ENRDY_bm))
    {
        ;
    }
    
    TCD0.CTRLA = TCD_CLKSEL_20MHZ_gc            /* choose the timer’s clock */
               | TCD_CNTPRES_DIV4_gc            /* choose the prescaler */
               | TCD_ENABLE_bm;                 /* enable the timer */
}

void TCD0_enableOutputChannels(void)
{
    /* enable write-protected register */
    CPU_CCP = CCP_IOREG_gc;
    
    TCD0.FAULTCTRL = TCD_CMPAEN_bm              /* enable channel A */
                   | TCD_CMPBEN_bm              /* enable channel B */
                   | TCD_CMPCEN_bm              /* enable channel C */
                   | TCD_CMPDEN_bm;             /* enable channel D */
} 
    
void EVENT_SYSTEM_init(void)
{
    EVSYS.ASYNCCH2 = EVSYS_ASYNCCH2_PORTC_PIN5_gc;
    
    EVSYS.ASYNCUSER6 = EVSYS_ASYNCUSER6_ASYNCCH2_gc;
}

void PORT_init(void)
{
    /* set pin 4 and pin 5 of port A as output */
    PORTA.DIR |= PIN4_bm                        
              | PIN5_bm;                      
                
    /* set pin 0 and pin 1 of port C as output */
    PORTC.DIR |= PIN0_bm                        
              | PIN1_bm;
                
    /* set pin 5 of port C as input */
    PORTC.DIR &= ~PIN5_bm;                      
    
    /* enable pull-up resistor for pin 5 of port C */    
    PORTC.PIN5CTRL = PORT_PULLUPEN_bm;        
}

int main(void)
{    
    PORT_init();    
    
    EVENT_SYSTEM_init();
    
    TCD0_enableOutputChannels();
    
    TCD0_init();
    
    /* Replace with your application code */
    while (1) 
    {
        ;
    }
}