Go Down

Topic: Changing Arduino Zero PWM Frequency (Read 27288 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.

b0rn4

#140
Aug 10, 2017, 06:33 am Last Edit: Aug 11, 2017, 01:39 am by b0rn4
Hello Friends! I'm new in arduino world and trying to make a code for pwm 2 outputs(pin 5 and pin 6), 20hz pwm frec.
Reading atmel SAMD21G datasheet found RAMP2 operation could be a solution for my problem, but is a hard work a lots configuration of matches, modes, etc.
Upload picture show my idea how output should be work and that 90° off phases needed between W[5] and W[6] output.
I have problem to implement output idea due can't make a correct register configuration.
Someone can tell me if i'm in a right way, or suggest other solution, code examples, edit my code, etc? Any help is welcome.

Thanks!  


Code: [Select]

#include <Adafruit_NeoPixel.h>

#define Pin13LED 13
#define NEOPIXEL 8  // Neopixel

int PERx = 32000; // PER=TOP value just for example

volatile int cont =0;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(1, NEOPIXEL);   // Create an strip object

void setup()
{
  strip.begin();
  strip.show(); // neopixel off
  
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Generic clock divisor D = 1
    GCLK_GENDIV_ID(4);          // Selecciono el clock gen 4
  while (GCLK->STATUS.bit.SYNCBUSY);    // Espero sincronizacion

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Duty cicle 50%
    GCLK_GENCTRL_GENEN |        // habilito gclk4
    GCLK_GENCTRL_SRC_XOSC32K |    // selecciono clock de 32Khz
    GCLK_GENCTRL_ID(4);         // Selecciono el clock gen 4
  while (GCLK->STATUS.bit.SYNCBUSY);    // Espero sincronizacion

  // Habilito el multiplexor para 2 canales de PWM con Timer TCC0
  const uint8_t CHANNELS = 2;
  const uint8_t Pin_5 = 5;  // TCC0;CCB1;Odd
  const uint8_t Pin_6 = 6;  // TCC0;CCB2;Even
  PORT->Group[g_APinDescription[Pin_5].ulPort].PINCFG[g_APinDescription[Pin_5].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[Pin_6].ulPort].PINCFG[g_APinDescription[Pin_6].ulPin].bit.PMUXEN = 1;

  // Conecto el timer TCC0 con los puertos de salida
  PORT->Group[g_APinDescription[5].ulPort].PMUX[g_APinDescription[5].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | 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;

  // Conecto el GCLK con TCC0
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |
     GCLK_CLKCTRL_GEN_GCLK4 |
     GCLK_CLKCTRL_ID_TCC0_TCC1;
  while (GCLK->STATUS.bit.SYNCBUSY);    // Espero sincronizacion

  
  // Single slope PWM: timer TCC0
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM |
    //TCC_WAVE_CIPEREN |  // Diferentes PER en ciclo A y B
    TCC_WAVE_POL0 | // Change polarity output
    TCC_WAVE_POL1;
    TCC_WAVE_RAMP_RAMP2;  // RAMP2 operation
  while (TCC0->SYNCBUSY.bit.WAVE);    // Espero sincronizacion

  // Configuro valor PER
  REG_TCC0_PER = PERx;
  while(TCC0->SYNCBUSY.bit.PER)   // Espero sincronizacion

  delay(50);

  // Configuro valor CCBx
  REG_TCC0_CC1 = (PERx/2);       // TCC0 CCB1 - 50% PWM on PIN 5
  while(TCC0->SYNCBUSY.bit.CC1);
  REG_TCC0_CC2 = (PERx/2);    // TCC0 CCB2 - 50% PWM on PIN 6
  while(TCC0->SYNCBUSY.bit.CC2);

  
  // Configuro preescaler y habilito las salidasTCC_CTRLBSET_IDXCMD_DISABLE |
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |
    TCC_CTRLA_CPTEN0 |  // capture channel 0 enable
    //TCC_CTRLBSET_IDXCMD_DISABLE |
    TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);  // Espero por sincronizacion

  delay(50);
  
}

void loop()
{
  
  REG_TCC0_CCB1 = (PERx/2);                       // OUTPUT pin 5
  while(TCC0->SYNCBUSY.bit.CCB1);
  REG_TCC0_CCB2 = (PERx/2);                     //OUTPUT pin 6  
  while(TCC0->SYNCBUSY.bit.CCB2);

}

glovisol

Go here for a simplified tutorial on Arduino Zero PWM frequency change.

https://www.picotech.com/support/topic24051.html

Cheers,

Glovisol

b0rn4

Thanks glovisol! I based my current code in that tutorial.
Unfortunately can't found correct reg configuration for 90° unphased output.

dlabun

@b0rn4 You should post your question on the Atmel forum as well, they are pretty helpful with features and questions that involve the SAMD registers.

b0rn4


glovisol

Hi diabun,

Please better explain what you wish to do: from your description it seems you only need to obtain two 20 Hz squarewaves shifted by 90°. Is this all, or do you also need the waveforms to change in duration driven by another signal?

If you only need the two 90° shifted squarewaves with FIXED duty cycle you only have to add adequate delay between the two.....


b0rn4

#146
Aug 15, 2017, 02:00 am Last Edit: Aug 15, 2017, 02:57 am by b0rn4
Hi @glovisol! I'm gona try to better explain. Sorry for my english, not is the best  :smiley-sweat:

Like picture show, i need a two output 90° shifted squarewaves.
Respect PWM drive,i need start in 0% and increment, 1 step per sec, for 1 min, from 0us to 107us (period max). In other words, need 60 steps of 1,78us active period increase.
That's especification not are most relevant for my problem. I have a hard time triying to make the two output 90° shifted with 49875us(aprox..) of dead time each.
My duty cicle is veery small and can't make dead time insertion.

Hope clarify my first post. I'm at your disposal for any other question.

Cheers

glovisol

Hi Born4,

The easiest & simplest way to obtain your result is to use a Standard Digital Phase Shiìfter using two type "D" flip-flops and one inverter. This can be implemented with just two digital packages. If you are willing to add this simpe hardware I can sedn you the schematic diagram.

b0rn4

Hi! @glovisol

I appreciate your idea, but this signal generation is just a part for a biggest program to run a hardware.
Unfortunately already design PCB and buy SMD components. My only option is finish the job by software.

Go Up