TCC0 complementary PWM in oneshot mode

When trying to configure timer with 20KHz PWM signal on WO/4 and WO/5, setting POL1 it works if running continuously, but when I set the timer as oneshot, the falling edge of the inverted channel is very slow. What can be wrong here?

It's similar to this topic, but I would like to use oneshot mode: link

Basically I'm trying to control half bridge, therefore I need signal for upper transistor as well lower transistor.

Hi @LeCrAm

To set up non-inverting and inverting complemtary PWM outputs, it's necessary to enable dead time insertion on the selected TCC0's CC channel, (be it CC0, CC1, CC2 or CC3.

Furthermore, when using complentary timer outputs on the SAMD21, by default each TCC0 CCx channel: CC0, CC1, CC2 and CC3, maps on to both it's respective non-inverting timer outputs: WO[0], WO[1], WO[2] and WO[3] and inverting WO[4], WO[5], WO[6] and WO[7]. For example, TCC0 channel CC3 maps on to non-inverting output WO[3] and inverting WO[7].

I mention "by default" because it's possible to change the CCx to WO[x] mapping in the TCC0 WEXCTRL register's OTMX bitfield. The SAMD21 datasheet specifies the applications for which the various OTMX settings are intended, but it can be used to simply add flexibility over which timer outputs to select.

The code below sets up 20kHz complementary PWM, configured for oneshot operation on channel CC3 with the non-inverting output WO[3] on port pin PA19 (Arduino Zero D12) and inverting WO[7] on PA21 (Arduino Zero D7). The oneshot operation is triggered every second in the loop().

Another point to note is that with oneshot operation, it's necessary to set the non-recoverable fault output state (non-inverting = 0, inverting = 1), otherwise the timer outputs will float once the triggered oneshot timer period has elapsed.

It's also possible offset the complementary pulse edges by inserting addition "dead-time" GCLK cycles for both low and high sided outputs, (I've included this feature in the code, but have commented these lines out).

Anyway, here's the example program:

// Output 20kHz with non-inverting and inverting complementary PWM on timer TCC0 with dead-time insertion and oneshot operation
// Digital pins D12 (PA19) non-inverting and D7 (PA21) inverting 
void setup()
{
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0 
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0 
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Use 48MHz GCLK0 as the clock source for timers TCC0 and TCC1

  // Enable the port multiplexer for the digital pins D12 (PA19) and D7 (PA21)
  PORT->Group[PORTA].PINCFG[19].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to the port output D12 (PA19) and D7 (PA21) - port pins are paired odd PMUXO and even PMUXE
  // D12 (PA19) -> TCC0/WO[3] and its complementary inverting pair: D7 (PA21) -> TCC0/WO[7]
  PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_F;// | PORT_PMUX_PMUXE_F;
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= PORT_PMUX_PMUXO_F;// | PORT_PMUX_PMUXE_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;          // Setup single slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC0->WEXCTRL.reg = //TCC_WEXCTRL_DTHS(100) |      // Add high side dead-time delay of 100 GLCK pulses
                      //TCC_WEXCTRL_DTLS(100) |      // Add low side dead-time delay of 100 GCLK pulses 
                      TCC_WEXCTRL_DTIEN3 |         // Enable dead-time insertion on channel CC3                     
                      TCC_WEXCTRL_OTMX(0);         // Set non-inverting D12 WO[3] and D7 WO[7] to channel CC3
  
  TCC0->DRVCTRL.reg |= TCC_DRVCTRL_NRV7 |          // Enable the non-recoverable timer output state after oneshot pulse
                       TCC_DRVCTRL_NRE7 |          // WO[3] output 0 (0V), WO[7] output 1 (3.3V) 
                       TCC_DRVCTRL_NRE3;           // (by coincidence WO[7] happens to be on D7)

  TCC0->PER.reg = 2399;                            // Set the frequency of the PWM on TCC0 to 20kHz
  while(TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization
 
  TCC0->CC[3].reg = 1200;                          // TCC0 CC3 - 50% duty-cucle on non-inverted output D12 and inverted output D7
  while(TCC0->SYNCBUSY.bit.CC0);                   // Wait for synchronization
  
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;       // Enable oneshot operation
  while(TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
  
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop()
{
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger ONESHOT operation on TCC0
  while (TCC0->SYNCBUSY.bit.CTRLB);                // Wait for synchronization
  delay(1000);                                     // One second delay
}
1 Like

Thanks Martin. I checked the datasheet since I’m using WO[4] and WO[5] which are both part of the inverting pins…. Do you know how I need to configure this OTMX register? I dont understand what is meant in table 31-4 of the datasheet…

edit: ChatGPT says it has to be set to 3, seems to work :slight_smile:

One additional question, if I would like to toggle between OneShot mode and Normal mode, lets say each 2 seconds, how can I achieve this? I tried stopping the timer, then clear the oneshot like this:

 TCC0->CTRLBCLR.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger ONESHOT operation on TCC0
  while (TCC0->SYNCBUSY.bit.CTRLB); 

Then retrigger, but this does not seem to work.

Hi @LeCrAm

To return to normal mode just clear oneshot mode and retrigger:

TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_ONESHOT;       // Disble oneshot operation
while(TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger normal operation on TCC0
while (TCC0->SYNCBUSY.bit.CTRLB);                // Wait for synchronization

To return to onshot mode just enable and retrigger once more:

TCC0->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;       // Enable oneshot operation
while(TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger ONESHOT operation on TCC0
while (TCC0->SYNCBUSY.bit.CTRLB);                // Wait for synchronization

Regarding the OTMX bitfield in TCC0's WEXCTRL register, to drive outputs WO[4] and WO[5] from the same CCx channel, set its value to 2 for CC0 or 3 for CC1.

The TCCO outputs didn't behave as expected setting OTMX to a value of 2. In this instance and according to the datasheet the WO[4] and WO[4] outputs should be the same, but they're not, (one of the channel's polarity is reversed). However, changing the CC0 polarity and non-recoverable fault outputs it's still possible to get complementary outputs. I chose port pins PA14 for WO[4] and PA15 for WO[5]:

// Output 20kHz with non-inverting and inverting complementary PWM on timer TCC0 with dead-time insertion and oneshot operation
// Digital pins D2 (PA14) non-inverting and D5 (PA15) inverting 
void setup()
{
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0 
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0 
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Use 48MHz GCLK0 as the clock source for timers TCC0 and TCC1

  // Enable the port multiplexer for the digital pins D2 (PA14) and D5 (PA15)
  PORT->Group[PORTA].PINCFG[14].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[15].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to the port output D2 (PA14) and D5 (PA15) - port pins are paired odd PMUXO and even PMUXE
  // D2 (PA14) -> TCC0/WO[4] and its complementary inverting pair: D5 (PA14) -> TCC0/WO[5]
  PORT->Group[PORTA].PMUX[14 >> 1].reg |= PORT_PMUX_PMUXE_F;// | PORT_PMUX_PMUXO_F;
  PORT->Group[PORTA].PMUX[15 >> 1].reg |= PORT_PMUX_PMUXO_F;// | PORT_PMUX_PMUXE_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM |         // Setup single slope PWM on TCC0
                   TCC_WAVE_POL0;                  // Reverse the polarity of the CC0 channel     
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC0->WEXCTRL.reg = //TCC_WEXCTRL_DTHS(100) |      // Add high side dead-time delay of 100 GLCK pulses
                      //TCC_WEXCTRL_DTLS(100) |      // Add low side dead-time delay of 100 GCLK pulses 
                      TCC_WEXCTRL_DTIEN0 |         // Enable dead-time insertion on channel CC0                     
                      TCC_WEXCTRL_OTMX(2);         // Set non-inverting D2 WO[4] and D5 WO[5] to channel CC0
  
  TCC0->DRVCTRL.reg = TCC_DRVCTRL_NRV5 |           // Set the WO[5] non-recoverable output state to output to 1 on on inverting channel
                      TCC_DRVCTRL_NRE5 |           // Enable the WO[4] and WO[5] output states: WO[4] = 0, WO[5] = 1
                      TCC_DRVCTRL_NRE4;           

  TCC0->PER.reg = 2399;                            // Set the frequency of the PWM on TCC0 to 20kHz
  while(TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization

  TCC0->CC[0].reg = 1200;                           // TCC0 CC0 - 50% duty-cycle on non-inverted output D2 and inverted output D5
  while(TCC0->SYNCBUSY.bit.CC0);                   // Wait for synchronization
  
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;       // Enable oneshot operation
  while(TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
  
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop()
{
  TCC0->CCB[0].reg = 600;                           // TCC0 CC0 - 25% duty-cycle on non-inverted output D2 and inverted output D5
  while(TCC0->SYNCBUSY.bit.CCB0);                   // Wait for synchronizatio
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;  // Retrigger ONESHOT operation on TCC0
  while (TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
  delay(1000);                                      // One second delay
  TCC0->CCB[0].reg = 1200;                          // TCC0 CC0 - 50% duty-cycle on non-inverted output D2 and inverted output D5
  while(TCC0->SYNCBUSY.bit.CCB0);                   // Wait for synchronizatio
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;  // Retrigger ONESHOT operation on TCC0
  while (TCC0->SYNCBUSY.bit.CTRLB);                 // Wait for synchronization
  delay(1000);                                      // One second delay
}
1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.