Go Down

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

VincentVega

Hi everyone,

thank you all (and especially MartinL) for your work here and helping me understand this topic. It was a lot to read and understand, and my results tell me I am not even there...

My situation:
I am working on a Zero and need to create a 20kHz pwm signal to pins D3 (PA09) and D5 (PA15).
After a long study on the SAMD21 data sheet I thought I could take the TCCO CC1 (which should address WO1 for D3/PA09 and WO5 for D5/PA15) to do so.
The test results tell me I was wrong. But I can not figure it out...

Here is my code:

Code: [Select]
void setup()
{

  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 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 port multiplexer
 PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;  // D3(PA09)
 PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;  // D5(PA15)
 
  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
 PORT->Group[g_APinDescription[5].ulPort].PMUX[g_APinDescription[5].ulPin>>1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E; // D5
 
  // Feed GCLK4 to TCC0 and TCC1         ??? Is there any way to feed GCLK4 only to TCC0?
  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

  // Single slope PWM operation

   REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_DSBOTTOM;

   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 = 1200;      // Set the frequency of the PWM on TCC1 to
  while(TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB1 = 600;       // TCC0 CCB0 - 50% duty cycle on D2
  while(TCC0->SYNCBUSY.bit.CCB1);


  // Divide the 48MHz signal by 1 giving 48MHz
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC1 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}


I would be happy about any kind of help, thank you in advance!

VincentVega

Sometimes some fresh air helps...
After a short break I found some copy paste errors and got it up working.
To be able to control the pwm on both pins separately, I changed D3 to TCC1 CBB1 and now I am able to do what I wanted.

Here is my now working code:
Code: [Select]
void setup()
{
  pinMode(13,INPUT);

  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 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 port multiplexer
 PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;  // D3(PA09)
 PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;  // D5(PA15)
 
  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F; // D3
  PORT->Group[g_APinDescription[5].ulPort].PMUX[g_APinDescription[5].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F; // D5
 
  // Feed GCLK4 to TCC0 and TCC1         ??? Is there any way to feed GCLK4 only to TCC0?
  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

  // Single slope PWM operation

   REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;
   while (TCC0->SYNCBUSY.bit.WAVE);               // Wait for synchronization
   REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NPWM;
   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 = 2400;      // Set the frequency of the PWM on TCC0 to
  while(TCC0->SYNCBUSY.bit.PER);

  REG_TCC1_PER = 2400;      // Set the frequency of the PWM on TCC1 to
  while(TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB1 = 1600;       // TCC0 CCB0 - 50% duty cycle on D2
  while(TCC0->SYNCBUSY.bit.CCB1);

  REG_TCC1_CCB1 = 800;       // TCC0 CCB0 - 50% duty cycle on D2
  while(TCC0->SYNCBUSY.bit.CCB1);


  // Divide the 48MHz signal by 1 giving 48MHz
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC1 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC1 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}


Thank you all again for your provided knowledge! I would not have been able to do this without this topic.

farlane

Having a bit of a problem using the TCC0 timer on the SAMD21G18 which is on the Arduino Tian. I use the TCC0 in dual slope mode, using only buffered registers to generate waveforms on two pins to avoid corrupting the waveforms. Generating the waveforms is not a problem, but i see some strange effects regarding the PER/PERB register.

According to the datasheet the PERB register is copied to the PER register on an UPDATE event (BOTTOM for dual slope mode). This should be the same for the other buffered registers CCB[X].

What i am seeing is that the CC registers (when read) actually do reflect the actual value of the compare values written using the CCB registers.

The PER register however always reads back as 0xFFFFFF, its reset and also maximum value. (Directly writing to the PER register works like one would expect and reads back correctly.)

Any of you guys have a clue as to what is going on there?

MartinL

Hi farlane,

I ran a small test. It looks like a read of the PER register returns the last value written to it, but like you mention it's not updated directly from the PERB register during double buffering, as described in the SAMD21 datasheet.

This suggests to me that perhaps internally the period double buffering isn't actually using the PER register.

Looks like another "undocumented feature" in the SAMD21 datasheet.

farlane

Hi farlane,

I ran a small test. It looks like a read of the PER register returns the last value written to it, but like you mention it's not updated directly from the PERB register during double buffering, as described in the SAMD21 datasheet.

This suggests to me that perhaps internally the period double buffering isn't actually using the PER register.

Looks like another "undocumented feature" in the SAMD21 datasheet.
Hi Martin, thanks for your time and troubles double checking this. A very strange behavior indeed given that the CC[X] registers are in fact updated with their buffered counterparts ...

Cudos & thanks again :)

Go Up