Go Down

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

b0rn4

Hi darkhawk,

I'm having some similar insue.
I recommend read this atmel application note (http://www.atmel.com/Images/Atmel-42357-Using-the-Timer-Counter-for-Control-Applications_ApplicationNote_AT07690.pdf) have a usefull information.
I guess ramp2 or ramp2A can resolve your problem.
I try to make some code but still don't work totally as i want. However maybe can help you.
Code: [Select]


void setup() {
 
  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;CC1;W0;Odd
  const uint8_t Pin_6 = 6;  // TCC0;CC2;W1;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;

  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
  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_POL0 |
      TCC_WAVE_RAMP_RAMP2A; // RAMP2A operation
  while (TCC0->SYNCBUSY.bit.WAVE);    // Espero sincronizacion


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

  delay(50);

  // Configuro valor CCx
  REG_TCC0_CC0 = 16000;       // TCC0 CCB1 - 50% PWM on PIN 5
  while(TCC0->SYNCBUSY.bit.CC0);
  delay(50);


  // Configuro preescaler y habilito las salidas
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |
    TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);  // Espero por sincronizacion

  delay(50);

}

void loop() {

}



darkhawk

My biggest issue is defining which pins to use for the output, and then getting that output there.
Unfortunately, no where can I find a good, easy to understand idea of what goes where, how it gets there, or what to do. I do have a table for the Zero (which matches the M0 board I have pin for pin) which does state the TCC0 and waveform outputs, but translating that into the code is well above my understanding at this time. Normally I could piece it together through examples, but I just get further confused because there aren't good examples of it.

b0rn4

Hi darkhawk,

you should read previews pages in this post,they explain very well how configure and what pin can be use for PWM.

cheers

b0rn4

Finally i made some progress!! Outputs are 90° shifted.
However have a little insue. After each PER reg configuration need some delay, i don't not why, but if you miss that, code don't work properly.
My idea is put all configuration needed in a ISR, but can't use delay there.
What is our recomendation?

Regards.

Code: [Select]

#include <Adafruit_NeoPixel.h>

#define Pin13LED 13
#define NEOPIXEL 8  // Neopixel

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

int PER1 = 32000; // PER1 controla el ciclo de la salida
int PER2 = 16000; // PER2 controla el timer que se activa cada 1s

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;CC1;W0;Odd
  const uint8_t Pin_6 = 6;  // TCC0;CC2;W1;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;

  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
  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 cuenta hasta PER1
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;             
  while (TCC0->SYNCBUSY.bit.WAVE);    // Espero sincronizacion

  // Normal frecuency generator: timer TCC1 cuenta hasta PER2
  REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NFRQ;
  while (TCC1->SYNCBUSY.bit.WAVE);

 // Configuro matriz de salida y habilito la insercion de tiempo muerto en el canal 1
 REG_TCC0_WEXCTRL |= TCC_WEXCTRL_DTIEN1;

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

  // Configuro valor PER2
  REG_TCC1_PER = PER2;
  while(TCC1->SYNCBUSY.bit.PER)   // Espero sincronizacion
  delay(25);

  // Configuro valor CCx
  REG_TCC0_CC1 = 24000;       // TCC0 CC1 - controla PWM del PIN 5
  while(TCC0->SYNCBUSY.bit.CC1);
  REG_TCC0_CC2 = 8000;        // TCC0 CC2 - controla PWM del PIN 6
  while(TCC0->SYNCBUSY.bit.CC2);

  //habilito interrupciones en TCC1
  REG_TCC1_INTENSET = TCC_INTENSET_OVF; //configuro interrupcion por desbordamiento en TCC1
//  NVIC_SetPriority(TCC1_IRQn, 0);// Set the Nested Vector Interrupt Controller (NVIC) priority
    NVIC_EnableIRQ(TCC1_IRQn);

  // Configuro preescaler y habilito las salidas
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |
    TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);  // Espero por sincronizacion
  REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |
    TCC_CTRLA_ENABLE;
  while (TCC1->SYNCBUSY.bit.ENABLE);  // Espero por sincronizacion

}

void loop() {

}

void TCC1_Handler() { //Interrupcion cada 1s PER2 (1s)
  static bool b;
  pinMode(Pin13LED, OUTPUT);
  digitalWrite(Pin13LED, b=!b);
 
  REG_TCC1_INTFLAG = TC_INTFLAG_OVF; //Habilito nuevamente las interrupciones

}

darkhawk

While they are 90 degrees...they're only a 25% duty cycle.


Wouldn't work for my application either, as I need 50% duty cycle and 90 degree offset.

Still haven't identified a way to make it work for my application.

bnn1044

hi:MartinL
this code seen to affect my ADC 3 input.
any idea?


// Output 250kHz PWM on timer TCC0 (6-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 pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to digital output D7 - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_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

  // Dual slope PWM operation: timers countinuously count up to PER register value then down 0
  REG_TCC0_WAVE |= TCC_WAVE_POL(0xF) |         // Reverse the output polarity on all TCC0 outputs
                    TCC_WAVE_WAVEGEN_DSBOTH;    // Setup dual slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);               // Wait for synchronization

  // 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 = 96;         // Set the frequency of the PWM on TCC0 to 250kHz
  while (TCC0->SYNCBUSY.bit.PER);                // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle
  REG_TCC0_CC3 = 48;         // TCC0 CC3 - on D7
  while (TCC0->SYNCBUSY.bit.CC3);                // 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() { }

MartinL

Hi bnn1044,

Have you tried using another analog input?

You could also try changing the PWM to another digital pin, but this may require the use of another TCC timer (TCC1 or TCC2), or a different channel on TCC0.

At 250kHz the PWM signal is going pretty fast, does your application require this speed or could it use a slower frequency? What sort of load/device is your PWM driving?

I'd also try to keep any analog wiring separate as they come off the board.

sergun2311

Hello Everyone,

I am working with SAM15x15 which uses same MCU as Zero does. I am needed to have PWM at PA23 pin. I cant understand how to change code which I found in that topic for my needs.  Can someone help me please?

Code: [Select]
// Output 250kHz PWM on timer TCC0 (6-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 pin D7
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  
  // Connect the TCC0 timer to digital output D7 - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[PORTA].PMUX[20 >> 1].reg = PORT_PMUX_PMUXO_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

  // Dual slope PWM operation: timers countinuously count up to PER register value then down 0
  REG_TCC0_WAVE |= TCC_WAVE_POL(0xF) |         // Reverse the output polarity on all TCC0 outputs
                    TCC_WAVE_WAVEGEN_DSBOTH;    // Setup dual slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);               // Wait for synchronization

  // 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 = 96;         // Set the frequency of the PWM on TCC0 to 250kHz
  while (TCC0->SYNCBUSY.bit.PER);                // Wait for synchronization
  
  // Set the PWM signal to output 50% duty cycle
  REG_TCC0_CC3 = 48;         // TCC0 CC3 - on D7
  while (TCC0->SYNCBUSY.bit.CC3);                // 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() { }

MartinL

#158
Oct 18, 2017, 10:07 am Last Edit: Oct 18, 2017, 12:33 pm by MartinL
Hi sergun2311,

In the SAMD21 Datasheet's "PORT Function Multiplexing" table 7-1, you'll see that pin PA23 uses timer TCC0/W0[4] on peripheral F. Timer TCC0 has 4 channel outputs WO[0]..WO[3] that that correspond to counter compare registers CC0..CC3, these repeat for channels WO[4]..WO[7]. Therefore WO[4] uses the corresponding counter compare register REG_TCC0_CC0.

In your code, to switch the pin from GPIO to the peripheral multiplexer just change the 21 to 23:

Code: [Select]
// Enable the port multiplexer for the SCL pin
PORT->Group[PORTA].PINCFG[23].bit.PMUXEN = 1;

...and to select the TCC0 timer on peripheral F:

Code: [Select]
// Connect the TCC0 timer to pin SCL - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[PORTA].PMUX[23 >> 1].reg |= PORT_PMUX_PMUXO_F;

Here we just divide the port number 23 by 2 (23 >> 1), as there are 32 port pins, but only 16 PMUX registers. Each PMUX register can control two port pin pairs: odd (in this case PA23) and even (PA22).

Finally just change the counter compare register and the subsequent synchronization bit from CC3 to CC0:

Code: [Select]
// Set the PWM signal to output 50% duty cycle
REG_TCC0_CC0 = 48;         // TCC0 CC0 - on SCL
while (TCC0->SYNCBUSY.bit.CC0);                // Wait for synchronization

sergun2311

Hi MartinL,

Thanks a lot, you helped me so much.
In your explanation you've misprinted about channel WO[4], it is WO[5] but it pushed me to fix it and understand it better.

MartinL

Hi sergun2311,

Quote
In your explanation you've misprinted about channel WO[4], it is WO[5]
I misread the datasheet, thanks for the correction. So it should be register CC1 instead.

sergun2311

Hi MartinL,

Now I have PWM with frequency 200 kHz on PA23. I am also needed to have PWM output on PA22 with much lower frequency (>10kHz). Did I understood it right that it is impossible because they works with the same timer?

MartinL

#162
Oct 24, 2017, 11:26 am Last Edit: Oct 24, 2017, 11:26 am by MartinL
Hi sergun2311,

That's right, on the SAMD21 it's not possible to have different PWM frequencies on the same timer. This is because although a timer may have a number of output channels, it only has one period (PER) register that determines frequency.

Your options in this instance are either to choose a different pin using a different TCC timer, or alternatively switch one of the pins to peripheral E (rather than F) and use the TC4 timer instead. Although the TC timers have less functionality that the PWM oriented TCC ones, they're still capable of outputting basic PWM waveforms at a given frequency.

Go Up