Changing Arduino Zero PWM Frequency

Hi Miruta22,

If you require the highest PWM resolution possible on all three channels then it's necessary to 3 separate timers: TCC0, TCC1 and TCC2. The timers operate independently from one another.

The following code for the MKRZero outputs 15kHz on D2, 10kHz on D3 and 1kHz on the MOSI pin with a 50% duty-cycle:

// Output 15kHz, 10kHz and 1kHz PWM on timers TCC0, TCC1 and TCC2 respectively
void setup()
{
  GCLK->GENDIV.reg = 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

  GCLK->GENCTRL.reg = 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 3 PWM channels: timer TCC0 outputs
  const uint8_t pwmPins[] = { 2, 3, MOSI };
  for (uint8_t i = 0; i < 3; i++)
  {
     PORT->Group[g_APinDescription[pwmPins[i]].ulPort].PINCFG[g_APinDescription[pwmPins[i]].ulPin].bit.PMUXEN = 1;
  }
  // 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[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_E;
  PORT->Group[g_APinDescription[MOSI].ulPort].PMUX[g_APinDescription[MOSI].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;

  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 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

  // Feed GCLK4 to TCC2 and TC3
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 and TC3
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TCC0->PER.reg = 3199;        // Set the frequency of the PWM on TCC0 to 15kHz
  while (TCC0->SYNCBUSY.bit.PER);
  TCC1->PER.reg = 4799;        // Set the frequency of the PWM on TCC1 to 10kHz
  while (TCC1->SYNCBUSY.bit.PER);
  TCC2->PER.reg = 47999;       // Set the frequency of the PWM on TCC2 to 1kHz
  while (TCC2->SYNCBUSY.bit.PER);
  
  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  TCC0->CC[3].reg = 1599;       // TCC0 CC3 - 50% duty cycle on D3
  while (TCC0->SYNCBUSY.bit.CC3);
  TCC1->CC[0].reg = 2399;       // TCC1 CC0 - 50% duty cycle on D2
  while (TCC1->SYNCBUSY.bit.CC0);
  TCC2->CC[0].reg = 23999;      // TCC2 CC0 - 50% duty cycle on MOSI
  while (TCC2->SYNCBUSY.bit.CC0);
  
  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
  TCC2->CTRLA.bit.ENABLE = 1;                     // Enable the TCC2 counter
  while (TCC2->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { }

Hi, MartinL,

I need to generate 100KHz (50% duty cycle is fine for me) on D5 pin of Arduino MKR1300, on which D5 is PB11 of SAMD21G18A

I've tried fumbling after a few code examples in this forum, but with no success.

Wondering if you can provide me any guidance on this?

Thank you very much

Hi, MartinL,

I now get 100KHz on D5 (PB11?) of MKR1300 using the following modified code, but also get 25KHz on D6 (PA20?)

how to get rid of the unwanted 25KHz on D6(PA20?)?

Thank you very much

// 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;
PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].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;
PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].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 = 240; // 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
REG_TCC0_CC1 = 120; // TCC0 CC3 - on D7
while (TCC0->SYNCBUSY.bit.CC1); // 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() { }

Hi, MartinL,

Please disregard my previous post, the 25KHz signal on D6 is due to my shield.

Thanks

MartinL:
The pin mapping for the TCC0, TCC1 and TCC2 are as follows:

REG_TCC0_CCB0 – digital output D2 (Zero Pro/M0 Pro/M0 – digital pin D4)
REG_TCC0_CCB1 – digital output D5
REG_TCC0_CCB2 – digital output D6
REG_TCC0_CCB3 – digital output D7
REG_TCC1_CCB0 – digital output D4 (Zero Pro/M0 Pro/M0 – digital pin D2)
REG_TCC1_CCB1 – digital output D3
REG_TCC2_CCB0 – digital output D11
REG_TCC2_CCB1 – digital output D13

Hi Martin,

I'm using MKR1000 and the pin map seems to be different. I need to drive PWM on pin PA8 which seems to use register TCC0 CCB0 for duty cycle and correspond to D11 Arduino port for MKR1000, but I can't get any PWM signal out on PA8. (But I can toggle the PA8 with direct port manipulation)

I did these modifications to your code to direct PWM to PA11:

// Enable the port multiplexer for the digital pin PA8
PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;

// Connect the TCC0 timer to digital output D0 - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg = PORT_PMUX_PMUXE_F;

// Set the PWM signal to output 50% duty cycle
REG_TCC0_CCB0 = pulsewidth; // TCC0 CCB0 - on PA8

Is there a way to set the PORT command with generic SAMD21 pin numbers instead of using g_APinDescription() and Arduino pin numbers? The Arduino pin/port mapping is constantly changing between board version and it's very confusing

Thanks!

Hi evi7538,

To configure port pin PA08 without referencing Arduino pins:

First enable the port multiplexer:

PORT->Group[PORTA].PINCFG[8].bit.PMUXEN = 1;

...then switch the port multplexer to the correct peripheral. In this case the port is even (port eight) and the TCC0/W[0] is on switch E (TC/TCC), (I/O Multiplexing and Considerations table in the SAMD21 datasheet):

PORT->Group[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_E;

If you prefer the PORTA definition can be replaced by 0, and likewise PORTB with 1.

Martin, thank you for quick response. I also just realized that I need to use TCC2 for PA8 (is this correct?). Still no luck. Here is my code.

Do I need to enable the pin with pinMode(11, OUTPUT) for PWM?

Any help is much appreciated!!!

 pinMode(11, OUTPUT);

  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

 PORT->Group[PORTA].PINCFG[8].bit.PMUXEN = 1;
 PORT->Group[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_E;

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC2_TC3;   // Feed GCLK4 to TCC2 and TC3
   while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

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

   REG_TCC2_PER = period;         // Set the frequency of the PWM on TCC to 250kHz
  while (TCC2->SYNCBUSY.bit.PER);                // Wait for synchronization
  
  // Set the PWM signal to output 50% duty cycle
  REG_TCC2_CCB0 = pulsewidth;         // TCC2 CCB0 - on PA8
  while (TCC2->SYNCBUSY.bit.CC3);                // Wait for synchronization
  
  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
  REG_TCC2_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC output
  while (TCC2->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

Timer TCC1 on channel 0 (CC0/CCB0) is the other timer available on port PA08, it's on peripheral switch position F:

PORT->Group[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_F;

Just change TCC2 to TCC1 and set the counter compare and counter compare buffered registers to CC0 and CCB0 respectively.

You'll also need to connect the TCC1 timer to the generic clock, by changing the GCLK_CLKCTRL_ID_TCC2_TC3 register bitfield to GCLK_CLKCTRL_ID_TCC0_TCC1 instead:

REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC
                   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

Unlike the AVR Arduino boards, on the SAMD21 it isn't necessary to use the pinMode() function to activate the pin as an output, so this line can be deleted.

WOW, it actually works now, thank you so much, Martin!

Hi,

I am looking to migrate my project from Arduino MEGA2560 to MKR ZERO.
My sketch controls 3 motor driver ICs and does it via 3 separate pins per driver IC, and sets up the pins to 20KHz via timers, and then directly modify the duty cycle in the program to adjust the speed.

Right now i have this for my MEGA2560:

  // Timer 4 (TCCR4) configuration controls pins 6, 7, and 8. 
  // PWM frequency calculation: [ 16MHz / 1 (prescaler) / 2 (phase-correct) / 400 (top) = 20kHz ]
  int eightOnes = 255;          // Equivalent to 11111111 in binary 
  TCCR3A &= ~eightOnes;         // Set the eight bits in register to 0
  TCCR3B &= ~eightOnes;         // Set the eight bits in register to 0
  ICR3 = (F_CPU/20000)/2;       // Top = 400 = (16Mhz/20Khz)/2
  TCCR3A = _BV(COM3A1)          // pin 6 - non-inverted PWM output    
         | _BV(COM3B1)          // pin 7 - non-inverted PWM output
         | _BV(COM3C1)          // pin 8 - non-inverted PWM output
         | _BV(WGM31);          // mode 10: phase correct PWM with ICR4 as Top
  TCCR3B = _BV(WGM33) 
         | _BV(CS30);           // Prescaler = 1

and control the duty cycle by updating OCR3A, OCR3B, OCR3C:

// speed = range between -400 to 400 
OCR3A = speed
OCR3B = speed

Anyone could help with a solution or could point me the right direction? thanks!

Hi manuelx10,

Here's some example code for the MKR Zero that outputs 20kHz PWM on digital pins 2, 3 and 4:

// MKR Zero: Output 20kHz PWM on timer TCC0 (8-bit resolution) on 3 channels on D4 (0), D2 (2) and D3 (3)
void setup()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                     GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->GENCTRL.reg = 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 3 PWM channels: timer TCC0 outputs on D4, D2 and D3
  PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].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 the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // Peripheral F specifies the timer: TCC0
  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[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;

  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 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
  TCC0->WAVE.reg = TCC_WAVE_POL(0xF) |            // Reverse the output polarity on all TCC0 outputs
                   TCC_WAVE_WAVEGEN_DSBOTTOM;     // Set up 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: 16MHz / (1 * (399 + 1) * 2) = 20kHz
  TCC0->PER.reg = 399;                            // Set the frequency of the PWM on TCC0 to 20kHz
  while (TCC0->SYNCBUSY.bit.PER);                 // Wait for synchronization

  // Set the CC[x] registers for 50% duty-cycle
  TCC0->CC[0].reg = 199;                          // TCC0 CC0 - 50% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CC0);                 // Wait for synchronization
  TCC0->CC[2].reg = 199;                          // TCC0 CC1 - 50% duty cycle on D2
  while (TCC0->SYNCBUSY.bit.CC2);                 // Wait for synchronization
  TCC0->CC[3].reg = 199;                          // TCC0 CC2 - 50% duty cycle on D3
  while (TCC0->SYNCBUSY.bit.CC3);                 // Wait for synchronization

  // Divide the 16MHz signal by prescaler 1 giving 16MHz (62.5ns) TCC0 timer tick
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1;    // Divide GCLK4 by 1
  
  TCC0->CTRLA.bit.ENABLE = 1;                     // Enable the TCC0 timer
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop()   // Change TCC0 timer channel 0 between 25% and 75% duty-cycle using buffered CCB[0] register
{ 
  TCC0->CCB[0].reg = 99;                          // TCC0 CCB0 - 25% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CCB0);                // Wait for synchronization
  delay(1000);                                    // Wait for 1 second
  TCC0->CCB[0].reg = 299;                         // TCC0 CCB0 - 75% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CCB0);                // Wait for synchronization
  delay(1000);                                    // Wait for 1 second
}

I've set up the generic clock 4 (GCLK4) to generate 16MHz to timer TCC0, thereby providing the same resolution as the Mega, (although the MKR Zero is capable of much higher resolution if required). It also uses dual slope PWM, the same as phase correct PWM on the AVR microcontrollers.

Hi MartinL,

Thanks for the quick reply! and the sample sketch, great starting point.

What is the difference between using the CC.reg and CCB.reg?

Also, is the delay and while loop for wait for sync necessary? will this affect simultaneous / instant control of the 3 pwm ?

while (TCC0->SYNCBUSY.bit.CCB0);                // Wait for synchronization
  delay(1000);                                    // Wait for 1 second

What is the difference between using the CC.reg and CCB.reg?

The CCBx.reg are buffered counter compare registers that automatically update the CCx only at the beginning of a new timer cycle (period). This allows the PWM duty-cycle to be changed during operation without causing glitches to appear on the PWM output. Changes by writing to CCx registers directly by contrast take effect (almost) immediately at the output.

Also, is the delay and while loop for wait for sync necessary?

The delay isn't necessary, as it's only in this example for demonstration purposes.

The register synchronisation is sometimes required, because CPU and its corresponding AHB/APB bus system is driven by a clock signal that is asynchronous respect the generic clock that drives the given peripheral. In this case the CPU clock domain is asynchronous with respect to GCLK4 that drives the TCC0 timer peripheral.

After writing to a TCC0 register that requires write synchronisation, the CPU can wait for the corresponding CCBx SYNCBUSY bit to clear before proceeding. This ensures that the written data has been successfully clocked into the TCC0 register.

However, register synchronisation happens anyway, whether you poll the corresponding SYNCBUSY bit or not, therefore waiting for the SYNCBUSY bit to clear in a while loop is only really necessary if there is a chance TCC0 register could be written two times in quick succession.

Thanks for this topic !! And all the excellent information from Martin L!

I try to drive two motors with two Motor driver cards Adafruit MD 8871, to be able to drive more powerful electric motors. 3,5 A each. I need 4 separate channels with PWM to drive both motors forward and backwards.
I use a small Adafruit ItsyBitsy M0 connected via I2C to have a more flexible motor driver.
I have almost got everything to work, but it seems that the channels are connected in some way, and only two channels work.. I think its the connection pins between the M0 chip and the pins that I really don't can figure out.. I get perfect 20k PWM signals from your code but I don't get 4 separate channels.

Any one has any clue? (Part of the code I use from this topic)
I have attached a Screendump width the pin configuration on the Adafruit ItsyBitsy M0.. And looked in the ATSAMD21G18 32-bit Cortex M0+ data sheet but I still don't get it... :frowning:

REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
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 4 PWM channels: timer TCC0 outputs
const uint8_t CHANNELS = 4;
const uint8_t pwmPins[] = { 11, 13, 10, 12 }; // CCB0 = 11, CCB13 = 5, CCB2 = 10, CCB3 = 12 ????
for (uint8_t i = 0; i < CHANNELS; i++)
{
PORT->Group[g_APinDescription[pwmPins].ulPort].PINCFG[g_APinDescription[pwmPins*].ulPin].bit.PMUXEN = 1;
_
}_
_
// 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[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].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*
* // 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_DSBOTTOM; // 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:*
* // 400 = 20kHz*
* REG_TCC0_PER = 400; // Set the frequency of the PWM on TCC0 to 20kHz*
* while(TCC0->SYNCBUSY.bit.PER);*
* // The CCBx register value corresponds to the pulsewidth in microseconds (us)*
* REG_TCC0_CCB0 = 0; // TCC0 CCB0 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB0);_
REG_TCC0_CCB1 = 0; // TCC0 CCB1 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB1);_
REG_TCC0_CCB2 = 0; // TCC0 CCB2 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB2);_
REG_TCC0_CCB3 = 0; // TCC0 CCB3 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB3);_
_
// Divide the 16MHz signal by 1 giving 16MHz (62.5ns) 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*

I found the problem! (some hours later... ) Now the code is working!

When I changed pins so they corresponded to the ItsyBitsy M0 layout (PAXX to Digital pin) and the M0 data sheet and the E or F column in the Table 6-1. PORT Function Multiplexing in the ATMEL SAMD21G
data sheet. Previous pairs (11,13 ) and (10,12) of pins had the same (W O numbers)

TCC0 (W O 0) E PA08 = 4
TCC0 (W O 1) E PA09 = 3
TCC0 (W O 2) F PA18 = 10
TCC0 (W O 3) F PA19 = 12

CODE:

// Output 20kHz PWM on timer TCC0 (8-bit resolution)
void setup()
{
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
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 4 PWM channels: timer TCC0 outputs
const uint8_t CHANNELS = 4;
const uint8_t pwmPins[] = { 4, 3, 10, 12 }; // TCC0 (W O 0) E PA08 = 4, TCC0 (W O 1) E PA09 = 3, TCC0 (W O 2) F PA18 = 10, TCC0 (W O 3) F PA19 = 12
for (uint8_t i = 0; i < CHANNELS; i++)
{
PORT->Group[g_APinDescription[pwmPins].ulPort].PINCFG[g_APinDescription[pwmPins*].ulPin].bit.PMUXEN = 1;
_
}_
_
// 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[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].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*
* // 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_DSBOTTOM; // 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:*
* // 400 = 20kHz*
* REG_TCC0_PER = 400; // Set the frequency of the PWM on TCC0 to 20kHz*
* while(TCC0->SYNCBUSY.bit.PER);*
* // The CCBx register value corresponds to the pulsewidth in microseconds (us)*
* REG_TCC0_CCB0 = 0; // TCC0 CCB0 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB0);_
REG_TCC0_CCB1 = 0; // TCC0 CCB1 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB1);_
REG_TCC0_CCB2 = 0; // TCC0 CCB2 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB2);_
REG_TCC0_CCB3 = 0; // TCC0 CCB3 - 200 = 50% duty cycle on D2 (0-400)
_
while(TCC0->SYNCBUSY.bit.CCB3);_
_
// Divide the 16MHz signal by 1 giving 16MHz (62.5ns) 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*

Hi Martin,

I got the PWM routine working fine on Zero with your help (Changing Arduino Zero PWM Frequency - #193 by MartinL - Arduino Zero - Arduino Forum ). But I now need to synchronize my ADC measurements with PWM signal. As a reminder, I have the PWM directed to the PA08:

PORT->Group[PORTA].PINCFG[8].bit.PMUXEN = 1;
PORT->Group[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_F;

For synchronization I'm trying to do direct read of the bit 08 of the PA register, something like:
PwmOut = REG_PORT_OUT0 & (1 << 8 );

But I'm getting only constant values, 0 or 1 depending on how I set the port values in the setup(). The PWM routine does not seem to affect the state of the REG_PORT_OUT0 register, although I can clearly see the PWM squarewave signal on the PA08 pin, so I guess the MUX connects the PA8 to the timer output instead of the port register?

So how do I read the state of the PA08 pin when it's driven by timer in the PWM mode?

I wanted to try the simplest method first (which is just polling the PA08 register bit in a while loop) but I know that there are other ways to do that which are a bit more complicated and have other consequences:

  • I can connect PA08 electrically to some other pin and do digital read, but for that I will need to modify the board.
  • I can trigger ADC read by an interrupt from the counter overflow, this will slightly increase the execution time (every us counts in my application)

Your help is much appreciated!

Hi evi7538,

The easiest way to trigger an ADC conversion from a PWM output is to use the SAMD21's event system. The event system is a 12-channel highway that can be used to allow peripheral-to-peripheral communication without CPU intervention.

It's possible to set up a peripheral to be the event sender (generator) and any number of peripherals to be the event receiver (user), on a given event channel (0 to 11). The peripherals can be configured to send and receive events on a number of peripheral specific criteria.

Here's some example code that sets up timer TCC1 to output a 1Hz PWM signal on port PA08. The TCC1 is configured so that it triggers an event each time it overflows (every second) on event channel 0. The ADC timer is initialised to start a conversion on analog pin A0 each time it receives this event from the TCC1 timer (on event channel 0). Once the ADC conversion is complete, the it generates an interrupt to output the result to the console:

// Use event system to trigger ADC conversion every second from a 1Hz PWM signal
void setup()
{
  SerialUSB.begin(115200);
  while(!SerialUSB);
  
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral

  // Intialise the ADC - generic clock already routed to ADC
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |    // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_12BIT;         // Set ADC resolution to 12 bits
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->SAMPCTRL.reg = 0x00;                        // Set max Sampling Time Length to half divided ADC clock pulse (5.33us)
  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                 // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization

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

  ADC->INTENSET.reg = ADC_INTENSET_RESRDY;        // Set ADC to generate an interrupt when the result is ready
  
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |         // Divide the 48MHz clock source by divisor 1: 48MHz/3=16MHz
                     GCLK_GENDIV_ID(4);           // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  GCLK->GENCTRL.reg = 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

  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 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

  // Enable the port multiplexer for the TCC1 PWM channel 0 on port pin PA08 
  PORT->Group[PORTA].PINCFG[8].bit.PMUXEN = 1;
  
  // Connect the TCC1 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[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_F;// | PORT_PMUX_PMUXO_F;

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_ADC_START);              // Set the event user (receiver) as ADC

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI;                                   // Enable the ADC start conversion on event input

  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC1_OVF) |        // Set event generator (sender) as timer TCC1 overflow
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0
  
  TCC1->EVCTRL.reg |= TCC_EVCTRL_OVFEO;                                    // Enable the TCC1 overflow event output
                      
  // Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
  TCC1->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single slope PWM on TCC1
  while (TCC1->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:
  TCC1->PER.reg = 15999999;                         // Set the frequency of the PWM on TCC1 to 1Hz
  while (TCC1->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  TCC1->CC[0].reg = 7999999;                        // TCC1 CC0 - 50% duty cycle
  while (TCC1->SYNCBUSY.bit.CC0);
  
  // Divide the 16MHz signal by 1 giving 16MHz (62.5ns) TCC1 timer tick and enable the outputs
  TCC1->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1;      // Divide GCLK4 by 1

  ADC->CTRLA.bit.ENABLE = 0x01;                    // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization

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

void loop() {}

void ADC_Handler()
{
  while (ADC->STATUS.bit.SYNCBUSY);                 // Read synchronization
  SerialUSB.println(ADC->RESULT.reg);               // Output the result
  ADC->INTFLAG.bit.RESRDY = 1;                      // Clear the RESRDY interrupt flag
}

Martin, thank you for good suggestion but sorry, I forgot to mention, I'm using an external 20-bit ADC through SPI. But I guess may be I can still use your idea (without initializing the ADC ) but somehow redirect the overflow event to a different event channel that I can just read directly in the interrupt handler function? I still use the internal ADC for other purposes so I don't want to disturb it with PWM.

Another thing: can I just poll that specific event bit from the channel without using interrupts?

Hi evi7538,

I'm using an external 20-bit ADC through SPI.

No problem, is the ADC conversion started through an SPI command, or with an external sync pulse from the microcontroller?

MartinL:
Hi evi7538,

No problem, is the ADC conversion started through an SPI command, or with an external sync pulse from the microcontroller?

The ADC starts and it is controlled entirely through SPI

I guess another possibility (if I still have to use interrupt handler anyway to clear the overflow) is just to trigger the interrupt by TCC1 overflow and run the ADC conversion in the interrupt handler without using the event system.