MartinL--Please Help---SAMD21 PWM DTI Questions/Help

Hi,

I have spent the last week reading through everything on this thread: https://forum.arduino.cc/index.php?topic=346731.105 I am hoping someone can help me.

I am using a Seeedstudio XIAO microcontroller which uses the SAMD21 chip (ATSAMD21G18-QFN48). Schematic is attached.

I am trying to have 2 PWM signals with dead time so that neither signal is HIGH at the same time. I want to be able to adjust the frequency and duty cycle. Same frequency on both though, I don't need 2 different frequencies at the same time. Essentially I want to have one PWM turn on and and then cascade off while the other cascades on without them ever both being on/high at the same time. I was hoping the DTI function could do this and tried modifying examples I found but haven't had any luck.

Is this possible with the DTI function or should I look at something else?

Just for now I would be happy just getting 2 PWM signals where one is like 50% duty cycle and the other is 45% duty cycle and the 2nd one is inverted and about 5% dead time between them. Something like in the attached picture. I can pretty much use any of the pins on the XIAO microcontroller but cant figure out how to get this working. I got this working on the Arduino Mega2560 but its a different chip essentially I did it with a lot of while loops and manipulating the OCR1A and OCR1B registers. I was hoping there is a better/cleaner way.

When I run the below code I get the attached image on the scope (current code scope results.png)
Sample code I tried to change that MartinL wrote on a different thread:

/*
  Currently trying to use pins D2 and D3 which according to the schematic is PA10 and PA11.
  XIAO D2 is PA10 (even port so PMUXE)
  XIAO D3 is PA11 (odd port so  PMUXO)
 
*/  


void setup()
{
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW .....Duty Cycle
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the digital pins desired (this has to be done when using the multiplexer and before the next PORT->Group below)
  PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1; 
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;

 
  // Connect the TCC0 timer to the port output D3 and D7 - port pins are paired odd PMUXO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXE_F;// | PORT_PMUX_PMUXE_F; 
  PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg = PORT_PMUX_PMUXO_F;// | PORT_PMUX_PMUXE_F;

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1 //Enabling the clock and syncronizing it
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single slope PWM on TCC0....Normal Pulse-Width Modulation (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization
  
  // Set the output matrix so that D3 and D7 are set to output CC0 and enable low and high dead time insertion
  REG_TCC0_WEXCTRL |=  TCC_WEXCTRL_DTHS(100) | TCC_WEXCTRL_DTLS(100) |     //Was 100 and 100
    TCC_WEXCTRL_DTIEN0 | TCC_WEXCTRL_DTIEN1 | TCC_WEXCTRL_OTMX(0x2);   /// OTMX is Output Matrix ....0x2 is CC0 from page 644...DTIEN 3 and 1
 
  // Invert the driver on TCC0/WO[7], which by coincidence happens to be Arduino digtal pin D7
  //REG_TCC0_DRVCTRL |= TCC_DRVCTRL_INVEN3;

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  REG_TCC0_PER = 319;                             // Set the frequency of the PWM on TCC0 to 150kHz         ////was 319 for 150kHz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle on D3, 50% on D7
  REG_TCC0_CCB0 = 159;                            // TCC0 CCB0 - on output on D3 and inverted output on D7 ////was 159
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
 
  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop(){}

XIAO Pinouts.PNG

Seeeduino XIAO_v1.0_SCH_191112.pdf (41.6 KB)

XIAO Pinouts.PNG

Hi zebrawolf,

Thanks I got your message.

The SAMD21 datasheet isn't very clear when it comes to explaining how to generate complementary PWM signals with dead-time insertion.

Essentially, generating dead-time insertion requires the TCC0 to be configured for a given channel output and its complement.

The channel outputs and their complements are as follows:

Channel CC0: output WO[0] and its complement WO[4]
Channel CC1: output WO[1] and its complement WO[5]
Channel CC2: output WO[2] and its complement WO[6]
Channel CC3: output WO[3] and its complement WO[7]

Unfortunately however, in order to provide analog inputs on every pin, Seeed Studio have sacrificed the XIAO's ability to perform dead-time insertion, as the TCC0's complementary outputs (WO[4] through to WO[7]) haven't been broken out.

Hi zebrawolf,

A workaround is to use dual slope PWM on both channels 2 (PA10) and 3 (PA11), but reverse the polarity on channel 2 and set their duty-cycles appropriately:

// Output 300kHz dual slope PWM on TCC0 with complementary outputs and dead time insertion 
void setup()
{
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable GCLK0
                      GCLK_CLKCTRL_GEN_GCLK0 |    // Select GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Route GCLK0 to TCC0 and TCC1

  PORT->Group[PORTA].PINCFG[10].bit.PMUXEN = 1;   // Enable the port multiplexer for port pins PA10 and PA11
  PORT->Group[PORTA].PINCFG[11].bit.PMUXEN = 1;
 
  // Select the port pin multiplexer switch to option F for TCC0/WO[2] and TCC0/WO[3] on port pins PA10 and PA11 respectively
  PORT->Group[PORTA].PMUX[10 >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  
  TCC0->WAVE.reg = TCC_WAVE_POL2 |                // Reverse the signal polarity on channel 2
                   TCC_WAVE_WAVEGEN_DSBOTTOM;     // Dual slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization
  
  TCC0->PER.reg = 79;                             // Set the frequency of the PWM on TCC0 to 300kHz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  TCC0->CC[2].reg = 40;                           // Output a 50% duty-cycle
  while(TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization

  TCC0->CC[3].reg = 35;                           // Output a 43% duty-cycle
  while(TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization
  
  TCC0->CTRLA.bit.ENABLE = 1;                     // Enable TCC0 
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop(){}
1 Like

Furthermore, to change the duty-cycle during operation:

void loop()
{
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;         // Set the Lock Update (LUPD) bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchroniztion
  TCC0->CCB[2].reg = 40;                          // Output a 50% duty-cycle
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  TCC0->CCB[3].reg = 35;                          // Output a 43% duty-cycle
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization  
  TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;         // Clear the Lock Update (LUPD) bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchroniztion
  delay(1000);                                    // Wait for 1 second

  TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;         // Set the Lock Update (LUPD) bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchroniztion
  TCC0->CCB[2].reg = 20;                          // Output a 25% duty-cycle
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  TCC0->CCB[3].reg = 15;                          // Output a 18% duty-cycle
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization  
  TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;         // Clear the Lock Update (LUPD) bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchroniztion
  delay(1000);                                    // Wait for 1 second
}
1 Like