Go Down

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

Nilab

Thanx MartinL
But i have another question
Arduino Due have  simple interrrupt config by timer

Code: [Select]

Timer3.attachInterrupt(myHandler).setFrequency(10000).start();


How do that? in Zero ?

MartinL

#226
Jun 26, 2019, 08:59 am Last Edit: Jun 26, 2019, 09:00 am by MartinL
Hi Nilab,

Here's a link to an explanation of how to configure the TCC timer to handle interrupts, the description is on post #20: https://forum.arduino.cc/index.php?topic=589655.15.

This disscussion is for PWM generation on the SAMD51 microcontroller, but the TCC interrupt code on the SAMD21 is identical.

path90234

#227
May 17, 2020, 07:53 pm Last Edit: May 17, 2020, 08:01 pm by path90234
I am curious why CCB2 and CCB3 do not seem to be included for TCC1 and TCC2.  I tried enabling TCC1_CCB2 using the following code but it seemed to not do anything.

Code: [Select]

#if (defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))
#define REG_TCC1_CCB2 (0x42002478U)
#else
#define REG_TCC1_CCB2 (*(RwReg  *)0x42002478U)
#endif


And why does TCC3 not seem to be available at all?

Thank you! 

MartinL

#228
May 18, 2020, 11:13 am Last Edit: May 18, 2020, 11:14 am by MartinL
Hi path90234,

The SAMD21G18A microcontroller on the Arduino Zero and MKR boards only has 3 TCC timers, namely: TCC0, TCC1 and TCC2. Only the later SAMD21 variants, such as the SAMD21G17D and SAMD21G17L incorporate the TCC3 timer.

TCC0 has 4 channels output on WO[0] to WO[3] and repeated on WO[4] to WO[7].

TCC1 has 2 channels output on WO[0] to WO[1] and repeated on WO[2] to WO[3].

TCC2 has 2 channels output on WO[0] to WO[1], but are not repeated.

The repeated outputs can act either as an alternative timer output option, or as an inverted complementary output.

The SAMD21G18A also includes TC timers: TC3, TC4 and TC5 that provide more basic PWM functionality.

path90234

Martin,

Thank you so much for clearing that up.  Shame on me for not reading the datasheet more carefully.  It is well laid out in the Configuration Summary section. 

path90234

Martin,

Another question for you if you don't mind since you seem quite knowledgeable on this subject.  Is there a recommended way to phase shift my PWM outputs so they don't all switch at the same time?  I am driving MOSFETs directly from the MCU pins which will drive LEDs, I am just trying to spread out current spikes. 

I am using all 3 TCCs and need to be able to vary the duty cycle dynamically for each channel. 

Something like this is what I am talking about:



Even if I could just get some of them offset that would help. 

Thank you very much!

MartinL

#231
May 26, 2020, 12:00 pm Last Edit: May 26, 2020, 03:11 pm by MartinL
Hi path90234,

Shift pulses within the period timeframe, basically boils down to 3 options, but depends on which channels you're using on the TCCx timers:

1) Deadtime Insertion

Deadtime Insertion can delay the leading edge of the complementary low side output, by loading the 8-bit DTLS register with the number of GCLK timer ticks the waveform is to be delayed. However, this functionality is only available on the TCC0 timer and the single deadtime counter can either be activated or deactivated on each channel (and it's inverted complementary output).

2) Dual-Slope Critical PWM Mode

Dual-Slope Critical PWM Mode sets up a centre-aligned PWM pulse and uses one CCx channel to control the postion of the leading edge and a second to control the trailing. This method is only available on TCC0 and essentially sacrifices two CC channels per waveform, such that TCC0 can provide only two Dual Slope Critical waveforms.

3) Output Inversion/Channel Polarity

Simply inverting the output and the CCx register's value with respect to the period (PER-CCx), allows the waveform to be moved from the front to the rear of period timeframe.

It's also possible to achieve the same effect by altering the channel's polarity, in other words reversing the compare output.

MartinL

#232
May 26, 2020, 05:12 pm Last Edit: May 26, 2020, 05:14 pm by MartinL
Here's an example that uses Dual-Slope Critical PWM on TCC0 and Dual-Slope PWM on TCC1, together with channel polarity to generate the phase shifted signals on four outputs at 100Hz: D4 (yellow), D2 (light blue), D5 (pink) and D3 (dark blue):



Code: [Select]
// Set-up TCC0 and TCC1 for Dual-Slope and Dual-Slope Critical PWM on digital pins D4, D2, D5, D3
void setup()
{
  // Feed GCLK0 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for the PWM channels on TCC0: D2 & D5, TCC1 D4 & D3
  PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
 
  // Connect port multiplexer pins to the TCC0 and TCC1 timers
  PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_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;

  TCC0->WAVE.reg = TCC_WAVE_POL(0x5) |             // Reverse the output polarity on TCC0 outputs 0 and 2           
                   TCC_WAVE_WAVEGEN_DSCRITICAL;    // Set the PWM output to dual-slope critical PWM mode
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC1->WAVE.reg = TCC_WAVE_POL(0x1) |             // Reverse the output polarity on TCC1 channel 0         
                   TCC_WAVE_WAVEGEN_DSBOTTOM;      // Set the PWM output to dual slope PWM mode
  while (TCC1->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC0->PER.reg = 239999;                          // Set the frequency of the PWM on PER to 100Hz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

  TCC1->PER.reg = 239999;                          // Set the frequency of the PWM on PER to 100Hz
  while (TCC1->SYNCBUSY.bit.PER);                  // Wait for synchronization

  // CC0 and CC2 pair on channel 0, 50% duty-cycle, phase delay 60 degrees
  TCC0->CC[0].reg = 200000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
  TCC0->CC[2].reg = 40000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization

  // CC1 and CC3 pair on channel 1, 50% duty-cycle, phase delay 120 degrees
  TCC0->CC[1].reg = 40000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
  TCC0->CC[3].reg = 200000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization

  // CC0, 50% duty-cycle, phase delay 0 degrees
  TCC1->CC[0].reg = 120000;                        // Set the duty-cycle to 50%
  while (TCC1->SYNCBUSY.bit.CC0);                  // Wait for synchronization

  // CC1, 50% duty-cycle, phase delay 180 degrees
  TCC1->CC[1].reg = 120000;                        // Set the duty-cycle to 50%
  while (TCC1->SYNCBUSY.bit.CC1);                  // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 counter
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
  TCC1->CTRLA.bit.ENABLE = 1;                      // Enable the TCC1 counter
  while (TCC1->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop() {}

path90234

Thank you Martin!  Incredibly helpful and in-depth, as always.  I hope you are a teacher IRL, you'd have some lucky students! 

MartinL

#234
May 27, 2020, 10:15 am Last Edit: May 27, 2020, 10:49 am by MartinL
Hi path90234,

I was mulling over the possibilities and realised that it is actually possible to get staggered outputs from all the TCC timers: TCC0, TCC1 and TCC2. The is achieved using Dual-Slope in DSBOTH mode and the circular buffer that switches between the CCx and buffered CCBx registers.

The following example outputs the same signals in the oscilloscope image above, but on D2, D5, D6 and D7, only using the TCC0 timer in DSBOTH mode.

If the PWM signals are slow enough, it's possible to change the CCx/CCB registers using an overflow interrupt service routine, which I've also included in the code. If the signals are fast (around 10kHz+) then it might be better to use the Direct Memory Access Controller (DMAC) to automatically load the CCx/CCBx registers from memory.

Code: [Select]
// Set-up TCC0  Dual-Slope DSBOTH PWM on digital pins D4, D2, D6, D7
void setup()
{
  // Feed GCLK0 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for the PWM channels on TCC0: D2, D5, TCC1 D4 & D3
  PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Connect port multiplexer pins to the TCC0 and TCC1 timers
  PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].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;

  NVIC_SetPriority(TCC0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);         // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)

  //TCC0->INTENSET.reg = TCC_INTENSET_OVF;           // Enable the overflow (OVF) interrupt

  TCC0->WAVE.reg = TCC_WAVE_CICCEN3 |              // Enable the circular counter compare CC3 <--> CCB3 buffer
                   TCC_WAVE_CICCEN2 |              // Enable the circular counter compare CC2 <--> CCB2 buffer
                   TCC_WAVE_CICCEN1 |              // Enable the circular counter compare CC1 <--> CCB1 buffer
                   TCC_WAVE_CICCEN0 |              // Enable the circular counter compare CC0 <--> CCB0 buffer
                   TCC_WAVE_CIPEREN |              // Enable the circular period PER <--> PERB buffer
                   TCC_WAVE_POL(0xF) |             // Reverse the output polarity on all TCC0 outputs           
                   TCC_WAVE_WAVEGEN_DSBOTH;        // Set the PWM output to dual-slope critical PWM mode
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC0->DRVCTRL.reg |= TCC_DRVCTRL_INVEN7 |        // Invert the CC3/CCB3 output
                       TCC_DRVCTRL_INVEN6;         // Invert the CC2/CCB2 output

  TCC0->PER.reg = 239999;                          // Set the frequency of the PWM on PER to 100Hz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  TCC0->PERB.reg = 239999;                         // Set the frequency of the PWM on PER to 100Hz
  while (TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization

  // CC0 and CCB0 pair on channel 0, 50% duty-cycle, phase delay 0 degrees
  TCC0->CC[0].reg = 120000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
  TCC0->CCB[0].reg = 120000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CCB0);                  // Wait for synchronization

  // CC1 and CCB1 pair on channel 1, 50% duty-cycle, phase delay 60 degrees
  TCC0->CC[1].reg = 200000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
  TCC0->CCB[1].reg = 40000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CCB1);                  // Wait for synchronization

   // CC2 and CCB2 pair on channel 1, 50% duty-cycle, phase delay 120 degrees
  TCC0->CC[2].reg = 40000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization
  TCC0->CCB[2].reg = 200000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CCB2);                  // Wait for synchronization

   // CC3 and CCB3 pair on channel 1, 50% duty-cycle, phase delay 180 degrees
  TCC0->CC[3].reg = 120000;                         // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization
  TCC0->CCB[3].reg = 120000;                        // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CCB3);                  // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 counter
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop() {}

void TCC0_Handler()
{
  if (TCC0->INTFLAG.bit.OVF)                      // Test the overflow (OVF) interrupt flag
  {
    TCC0->INTFLAG.bit.OVF = 1;                    // Clear the OVF interrupt flag
    TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Initalise read synchronization
    while(TCC0->SYNCBUSY.bit.CTRLB);              // Wait for synchronization
    while(TCC0->SYNCBUSY.bit.COUNT);              // Wait for read synchronization
    uint32_t count = TCC0->COUNT.reg;             // Read the TCC0 COUNT register
    if (count < 100)                              // Test if it's a ZERO (rather than TOP update)
    {
      if (TCC0->CC[3].reg == 120000)
      {
        TCC0->CC[3].reg = 60000;                   // Change the CCx and CCBx registers here...
        TCC0->CCB[3].reg = 60000;
      }
      else
      {
        TCC0->CC[3].reg = 120000;                   
        TCC0->CCB[3].reg = 120000;
      }
    }
  }
}

peaceblackhair

Hi Martin,

you are doing a great work thanks for all replys.I could't figure out to get 31Khz or 30 Khz on pins  9 and 10. I hope you can help me too.Thanks for your help

MartinL

Hi pearceblackhair,

Here's some example code that outputs PWM at a 30kHz frequency and 50% duty-cycle on digital pins: D9 and D10. The code utilises TCC0 channel 2 and TCC1 channel 1:

Code: [Select]
// Set-up TCC0 and TCC1 for 30kHz PWM, 50% duty-cycle on digital pins 9 and 10
void setup()
{
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Connect GCLK0 to timers TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for the PWM channel for digital pins 9 and 10
  PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 channel 2 and TCC1 channel 1 outputs to digital pins 9 and 10
  PORT->Group[g_APinDescription[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;          // Set the PWM output to dual-slope critical PWM mode
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
  TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;          // Set the PWM output to dual-slope critical PWM mode
  while (TCC1->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

  TCC0->PER.reg = 1599;                            // Set the frequency of the PWM to 30kHz ((48MHz/30kHz)-1) on D9
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  TCC1->PER.reg = 1599;                            // Set the frequency of the PWM to 30kHz ((48MHz/30kHz)-1) on D10
  while (TCC1->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  TCC0->CC[2].reg = 800;                           // Set the duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization
  TCC1->CC[1].reg = 800;                           // Set the duty-cycle to 50%
  while (TCC1->SYNCBUSY.bit.CC1);                  // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 timer
  TCC1->CTRLA.bit.ENABLE = 1;                      // Enable the TCC1 timer
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
  while (TCC1->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop() {}
 

Go Up