Go Down

Topic: Changing Arduino Zero PWM Frequency (Read 25974 times) previous topic - next topic

MartinL

#135
May 08, 2017, 09:39 am Last Edit: May 09, 2017, 10:56 am by MartinL
Further to my last post, it's also possible to mix and match the pins on the low (D2, D3, D1 and D0) and high side (D4, D5, D6 and D7) using the output matrix.

For example, you can set the output matrix to only output from timer TCC0 on channel CC0, by setting the TCC_WEXCTRL_OTMX(value) bitfield to 2:

CC0: D2, D3, D1, D0 - D4, D5, D6, D7

It's now possible to output on any low side pin, on say D3 and also on any high side, for instance D7 and still retain the dead time insertion functionality. However, it's now necessary to set not only the TCC_WEXCTRL_DTIEN3 bit for D7 (normally on CC3), but also the TCC_WEXCRL_DTIEN1 bit for D3 (normally on CC1):

Code: [Select]
// 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) |
  TCC_WEXCTRL_DTIEN3 | TCC_WEXCTRL_DTIEN1 | TCC_WEXCTRL_OTMX(0x2);

Here's the example code:

Code: [Select]
// Output 150kHz with non-inverting and inverting PWM on timer TCC0 with dead time insertion (8-bit resolution)
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
                     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 D3 and D7
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].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[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg = PORT_PMUX_PMUXO_E;// | PORT_PMUX_PMUXE_F;
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].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
                     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
  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) |
    TCC_WEXCTRL_DTIEN3 | TCC_WEXCTRL_DTIEN1 | TCC_WEXCTRL_OTMX(0x2);
 
  // Invert the driver on TCC0/WO[7], which by coincidence happens to be Arduino digtal pin D7
  //REG_TCC0_DRVCTRL |= TCC_DRVCTRL_INVEN7;

  // 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
  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
  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(){}


techguystuff

MartinL,

I am in awe of your datasheet reading skills. Truly. You have helped me quite a bit, thank you!

After playing with this a bit, I noticed that tone() stopped working since it too uses TCC0. So to recover the use of tone() (I've peppered the UI with calls to tone() already) would it be possible to use TCC1 or TCC2 for DTI since(from one of your earlier posts):

TCC1 has 2 channels:

WO[0] = D8
WO[1] = D9

TCC2 also has 2 channels:

WO[0] = D11
WO[1] = D13

What would the configuration be to use either of these pairs of pins?

MartinL

#137
May 08, 2017, 05:12 pm Last Edit: May 08, 2017, 05:15 pm by MartinL
Hi techguystuff,

Quote
I noticed that tone() stopped working since it too uses TCC0.
I thought the tone() function only uses timer TC5 on the Arduino Zero?

Quote
would it be possible to use TCC1 or TCC2 for DTI
Unfortunately it's only possible to use timer TCC0, as TCC1 and TCC2 don't support either dead time insertion or the output matrix.

techguystuff

MartinL,

Right! So it is. Turns out I have been moving the PWM pins around quite a bit trying to find the best combination, and my last stop just happened to be on the same pin I had configured tone() to use. I've now moved it up to D10, and all is good now. Thanks for all your help!

BTW, not sure why, but I had to disable the inversion of D7 in order to get an inverted waveform (compared to D3) on D7:

// REG_TCC0_DRVCTRL |= TCC_DRVCTRL_INVEN7;

My target frequency is variable, from 10K to 25K (nominally 15K), PWM less than 30% (nominally 10%). So I use these lines:

  // 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 = 48e6/15e3;                  // Set the frequency of the PWM on TCC0 to 15kHz
  while(TCC0->SYNCBUSY.bit.PER);            // Wait for synchronization
 
  // Set the PWM signal to output 10% duty cycle on D3, 90% on D7
  REG_TCC0_CCB0 = (48e6/15e3)*0.10;      // TCC0 CCB0 - on output on D3 and inverted output on D7
  while(TCC0->SYNCBUSY.bit.CCB0);          // Wait for synchronization

Deadtime only needs to be about 200ns, so I'm only using a setting of 10 cycles (at 48MHz):

  REG_TCC0_WEXCTRL =  TCC_WEXCTRL_DTHS(10) | TCC_WEXCTRL_DTLS(10) |
    TCC_WEXCTRL_DTIEN3 | TCC_WEXCTRL_DTIEN1 | TCC_WEXCTRL_OTMX(0x2);

This gives absolutely perfect waveforms!

Again, thank you very much for your help.

Sincerely,
Carlos

MartinL

#139
May 09, 2017, 09:07 am Last Edit: May 09, 2017, 09:08 am by MartinL
Hi Carlos,

Glad you got it working.

Quote
BTW, not sure why, but I had to disable the inversion of D7 in order to get an inverted waveform (compared to D3) on D7:
Then I guess provided you choose a pin from the low and high side, the SAMD21 does the inversion for you and activating the inverter isn't necessary. As I haven't got an oscilloscope, it's difficult for me to see if the signals are inverted or non-inverted. Thanks for the information.

Go Up