Changing Arduino Zero PWM Frequency

Here's the code:

// Use event system to use 62.5kHz PWM signal to trigger DMAC to send SPI ADC conversion
#include <SPI.h>

typedef struct                              // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

// DMAC_CH_NUM = 12 channels on SAMD21
volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));        // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));  // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                       // Place holder descriptor

uint8_t data[2] = { 0x68, 0x77 };            // Dummy SPI command: 0x68 = Register address, 0x77 = Command

void setup()
{
  SPI.begin();
  SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));   // SPI at 10MHz
  
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels
 
  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  // Set DMAC channel 0 to priority level 3 (highest), to trigger on TCC0 overflow and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(3) |                    
                      DMAC_CHCTRLB_TRIGACT_BEAT |
                      DMAC_CHCTRLB_EVIE |
                      DMAC_CHCTRLB_EVACT_TRIG;

  DMAC->CHID.reg = DMAC_CHID_ID(1);                                 // Select DMAC channel 1
  // Set DMAC channel 1 to priority level 3 (highest), to trigger on TCC0 overflow and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(3) | 
                      DMAC_CHCTRLB_TRIGACT_BEAT |
                      DMAC_CHCTRLB_EVIE |
                      DMAC_CHCTRLB_EVACT_TRIG;

  descriptor.descaddr = (uint32_t)&descriptor_section[0];          // Circular descriptor
  descriptor.srcaddr = (uint32_t)&data[0] + 1;
  descriptor.dstaddr = (uint32_t)&SERCOM4->SPI.DATA.reg;
  descriptor.btcnt = 1;
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_BYTE | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID;
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));

  descriptor.descaddr = (uint32_t)&descriptor_section[1];          // Circular descriptor
  descriptor.srcaddr = (uint32_t)&data[1] + 1;
  descriptor.dstaddr = (uint32_t)&SERCOM4->SPI.DATA.reg;
  descriptor.btcnt = 1;
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_BYTE | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID;
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));
  
  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

  // 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

  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 the TCC timers 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[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_DMAC_CH_0);              // Set the event user (receiver) as DMAC channel 0

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

  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_TCC0_MCX_0) |      // Set event generator (sender) as timer TCC0 match compare channel 0
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  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_TCC0_MCX_1) |      // Set event generator (sender) as timer TCC0 match compare channel 1
                       EVSYS_CHANNEL_CHANNEL(1);                           // Attach the generator (sender) to channel 0
  
  TCC0->EVCTRL.reg |= TCC_EVCTRL_MCEO0 |                                   // Enable the TCC0 match compare channel 0 output
                      TCC_EVCTRL_MCEO1;                                    // Enable the TCC0 match compare channel 1 output
                                           
  // Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
  TCC0->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single 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:
  TCC0->PER.reg = 767;                          // Set the frequency of the PWM on TCC0 to 62.5kHz
  while (TCC0->SYNCBUSY.bit.PER);               // Wait for synchronization
  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  TCC0->CC[2].reg = 384;                        // TCC0 CC2 - 50% duty cycle
  while (TCC0->SYNCBUSY.bit.CC2);               // Wait for synchronization
  TCC0->CC[3].reg = 129;                        // TCC0 CC3 - set duty cycle
  while (TCC0->SYNCBUSY.bit.CC3);               // Wait for synchronization  
  TCC0->DRVCTRL.reg |= TCC_DRVCTRL_INVEN7;      // Invert CC3 = WO[7]
  TCC0->CC[0].reg = 1;                         // TCC0 CC0 - set duty cycle
  while (TCC0->SYNCBUSY.bit.CC0);               // Wait for synchronization
  TCC0->CC[1].reg = 49;                         // TCC0 CC1 - set duty cycle
  while (TCC0->SYNCBUSY.bit.CC1);               // Wait for synchronization
  
  // Divide the 16MHz signal by 1 giving 16MHz (62.5ns) TCC0 timer tick and enable the outputs
  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

  DMAC->CHID.reg = DMAC_CHID_ID(0);                 // Select DMAC channel 0
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 0
  DMAC->CHID.reg = DMAC_CHID_ID(1);                 // Select DMAC channel 1
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 1
}

void loop() {}

Dear MartinL,

Great work with this topic. I have learnt quite a lot reading through. I have some troubles understanding a few things.

I am using an Adafruit M0 Feather board and I am trying to get a 100kHz waveform on D10, D12. I am having trouble understanding the section of the code where you connect TCC timer to the ports. I am not sure how to go about doing that for any board(with a custom mapping) for any particular pin. Will you please help explain that or point to where in the Atsamd21 datasheet, I should be reading for clarification.

I am attaching the variant.cpp file for pin Mapping for Adafruit M0 if that helps.

variant.cpp (8 KB)

Hi dev_000,

Looking at the Adafruit Feather M0 schematic: https://cdn-learn.adafruit.com/assets/assets/000/040/553/original/arduino_schem.png?1490994398, it can be seen that digital pin D10 is connected to SAMD21's port pin PA18 and D12 to PA19.

By default the microcontroller's pins are set to General Purpose Input Output (GPIO), however the pins can also be switched over to act as peripheral inputs and outputs. Switching over from GPIO to peripheral IO is performed by setting the PMUXEN (Peripheral Multiplexer Enable) bit in a given pin's CONFIG (Configuration) register:

This can be set either for an Arduino style digtial pin:

// Set PMUXEN for digital pin D10
PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;

...or using the SAMD21's port pin:

PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 1;    // Set PMUXEN for port pin PA18

The SAMD21's datasheet contains a table, called "Table 6-1 PORT Function Multiplexing". It lists each of the microcontroller's pins against peripherals labelled A to H. Going down the table we can see that port pin PA18 (D10) has a timer TCC0/WO[2] on peripheral F. WO[2] indicates that this pin uses TCC0 channel 2. Port pin PA19 (D12) has timer TCC0/WO[3], also on peripheral F.

On the SAMD21 the port pins a paired as odd and even, starting PA00 and PA01, PA02 and PA03 and so on... Port pin PA18 on D10, happens to be paired with PA19 on D12. Using the pins' PMUX register we can activate both peripheral outputs. Each PMUX register is 8-bits wide with the upper nibble (4-bits) determining the ODD port peripheral IO: A through to H and the lower nibble determining the EVEN port peripheral IO, again: A through to H.

Therefore to activate the even PA18 (D10) and the odd PA19 (D12) as PWM outputs, it's necessary to set both the even and odd nibbles to peripheral F.

Again, this can be set either for an Arduino style digtial pins:

PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;

...or using the SAMD21's port pins:

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

Each port has 32 pins (PA00-PA31) (although not all port pins are available on some SAMD21 variants), this means there are 32 port pin CONFIG registers (one for each pin), but only 16 PMUX registers (one for each port pair), the logical shift right ">>" essentially divides the value by to 2, to account for half the number of PMUX registers.

Thanks a lot for taking the time to explain that beautifully. Got it working fine at 100kHz.

Just a small correction. In the newer datasheet, its Table 7-1. I had searched for Table 6-1 and couldn't find it. For anyone searching for it in the future, search for "PORT Function Multiplexing" in the datasheet.

Hi MartinL,
your post has been super useful to me to understand how PWM on Arduino Zero works.

I'm trying to make a LED (pin 8 ) blink with PWM, changing the frequency to value visible to the eyes. Is it possible in some ways?

Hi Nico,

I'm trying to make a LED (pin 8 ) blink with PWM, changing the frequency to value visible to the eyes. Is it possible in some ways?

If your intention is to simply blink an LED, then it's possible to run the TC or TCC timers at a lower frequency.

Here's some code that blinks an LED at 0.5Hz, 1 second on, 1 second off on digital pin D8. It uses the TCC1 timer in Normal Frequency (NFRQ) mode, this mode simply toggles the D8 output each time the timer overflows (every second):

// Toggle LED output 1 second on, 1 second off (0.5Hz) on digital pin D8 using timer TCC1
void setup()
{ 
  // Feed GCLK0 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0 
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for the TCC1 PWM channel 0 (digital pin D8), SAMD21 pin PA06
  PORT->Group[g_APinDescription[8].ulPort].PINCFG[g_APinDescription[8].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[8].ulPort].PMUX[g_APinDescription[8].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
  
  TCC1->PER.reg = 46874;                          // Set TCC1 timer to overflow every 1 second: 48MHz / (1024 * 46874 + 1) = 1Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  TCC1->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1024; // Set TCC1 prescaler to 1024
  TCC1->CTRLA.bit.ENABLE = 1;                     // Enable the TCC1 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {}

Amazing, thanks a lot.
And then I simply use the AnalogWrite for PWM as always right?

Last question; if I would like to use the digital pins 2-3 for the same purpose, do I need to change the value to another timer or is the code the same?

Thanks again

Hi Nico,

And then I simply use the AnalogWrite for PWM as always right?

Unfortunately the issue with the analogWrite() function, is that it will set up a predefined timer and frequency on a given pin, this will interfere with code above.

if I would like to use the digital pins 2-3 for the same purpose, do I need to change the value to another timer or is the code the same?

That depends if you need to change the signal's frequency (period) or duty-cycle. Changing the frequency affects the whole timer and all of its output channels, while the duty-cycle can be set for each individual channel.

What would you like the LED outputs to do? What frequency (range) and duty-cycle do you intend to use?

Unfortunately the issue with the analogWrite() function, is that it will set up a predefined timer and frequency on a given pin, this will interfere with code above.

I've tried the analogWrite(8,x) with your code and it works fine. Changing the value of x between 0 and 255 change the time which the led stay on in a period of one second.

What would you like the LED outputs to do? What frequency (range) and duty-cycle do you intend to use?

The purpose is the same as the pin 8. I need to make two led connected on pin 2 and 3 blinks with the PWM so, it would be amazing if I can set the frequency to 0.5Hz as on the previous example and then I make it blink slower or faster changing the duty cycle with analogWrite(2,x) and analogWrite(3,x)

Does the changing of the PWM frequency on pins 2 and 3 will change the running time of my whole firmware (like will change delays) or is it unlinked from the actual clock speed of the board?

Hi Nico,

I've tried the analogWrite(8,x) with your code and it works fine. Changing the value of x between 0 and 255 change the time which the led stay on in a period of one second.

The analogWrite() function sort of works in this instance, because it's using the same timer (TCC1). However, behind the scenes it does change the timer's configuration and should now be giving you an on/off time of around 1.4s (0.7Hz).

If you require control over the duty-cycle of the signal on digital pin D8 then it's better to set up TCC1 for Normal PWM (NPWM) operation. Changes to the duty-cycle are made by loading the Counter Compare Buffered Channel 0 (CCB0) register with a value between 0 (0%) and the Period (PER) register (100%).

Here's the code to do that:

// Set up timer TCC1 to output a 0.5Hz signal on digtal pin D8
void setup()
{ 
  // Feed GCLK0 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0 
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for the TCC1 PWM channel 0 (digital pin D8), SAMD21 pin PA06
  PORT->Group[g_APinDescription[8].ulPort].PINCFG[g_APinDescription[8].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[8].ulPort].PMUX[g_APinDescription[8].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;

  TCC1->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;        // Setup single slope PWM on TCC1
  while (TCC1->SYNCBUSY.bit.WAVE);                // Wait for synchronization
  
  TCC1->PER.reg = 93749;                          // Set the TCC1 timer to overflow every 1 second: 48MHz / (1024 * 93749 + 1) = 0.5Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

  TCC1->CC[0].reg = 46875;                        // Set the TCC1 duty cycle to 50% (PER / 2)
  while(TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
 
  TCC1->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1024; // Set TCC1 prescaler to 1024
  TCC1->CTRLA.bit.ENABLE = 1;                     // Enable the TCC1 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() 
{
  TCC1->CCB[0].reg = 23437;                       // Set the TCC1 duty cycle to 25% (PER / 4)
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization  
  delay(4000);                                    // Wait for 4 seconds
  TCC1->CCB[0].reg = 70312;                       // Set the TCC1 duty cycle to 75% (PER * 3 / 4)
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization  
  delay(4000);                                    // Wait for 4 seconds
}

Regarding obtaining the same output on D2 and D3:

On D2 change the pin output configuration to:

PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;

...and the timer and timer channel to TCC0, channel 0, for example:

TCC0->CCB[0].reg = 23437;                       // Set the TCC0 duty cycle to 25% (PER / 4)
while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization

On D3 change the pin output configuration to:

PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F;

... and the timer and timer channel to TCC1, channel 1, for example:

TCC1->CCB[1].reg = 23437;                       // Set the TCC1 duty cycle to 25% (PER / 4)
while(TCC1->SYNCBUSY.bit.CCB1);                 // Wait for synchronization

Making changes to the TCCx timers won't affect the timing delay(), delayMicroseconds(), millis() or micros() functions, as they're generated using the SAMD21's Systick (System Tick) timer.

Amazing, thanks a lot MartinL

Hi MartinL
How to set d3 and d4 pin to 20kHz or 30khz (arduino M0)

and change duty cycle in loop to control the motor with high resolution (10 bit) for example
can i connect to it to tcc0 ? or i need tcc0 to d4 and tcc1 to d3 ?

Hi Nilab,

Here's an example of that generates 30kHz PWM on digital pins D3 and D4, it provides 10-bit resolution (log(1600)/log(2)) on both outputs.

Note that on the Arduino M0 board's D2 and D4 pins are reversed with respect to the Arduino Zero's, (I've accounted for that in the code):

// Output 30kHz PWM on digital pins D3 and D4 on Arduino.org M0 board
// Note: D2 and D4 are reversed on Arduino MO with respect to Arduino Zero
void setup()
{
  // Feed GCLK0 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK0 as a clock source
                      GCLK_CLKCTRL_GEN_GCLK0 |     // Select GCLK0
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer for pins D3 and D4 
  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;

  // D3 is on ODD port pin PA09 and TCC0/WO[1] channel 1 is on peripheral E
  // D4 is on EVEN port pin PA14 and TCCO/WO[4] channel 0 is on peripheral F
  PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;

  // Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
  TCC0->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single slope PWM on TCC1
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
  
  TCC0->PER.reg = 1599;                            // Set the frequency of the PWM on TCC0 to 30kHz: 48MHz / (1599 + 1) = 30kHz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  
  // The CCx register value corresponds to the pulsewidth in microseconds (us)
  TCC0->CC[1].reg = 800;                          // TCC0 CC1 - 50% duty cycle on D3
  while (TCC0->SYNCBUSY.bit.CC1);                 // Wait for synchronization
  TCC0->CC[0].reg = 800;                          // TCC0 CC0 - 50% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CC0);                 // Wait for synchronization
  
  TCC0->CTRLA.bit.ENABLE = 1;                     // Enable the TCC0 counter
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() 
{ 
  // Using buffered counter compare registers (CCBx)
  TCC0->CCB[1].reg = 400;                         // TCC0 CCB1 - 25% duty cycle on D3
  while (TCC0->SYNCBUSY.bit.CCB1);                // Wait for synchrnoization
  TCC0->CCB[0].reg = 400;                         // TCC0 CCB1 - 25% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CCB0);                // Wait for synchronization
  delay(1000);                                    // Wait for 1 second
  TCC0->CCB[1].reg = 1200;                        // TCC0 CCB1 - 75% duty cycle on D3
  while (TCC0->SYNCBUSY.bit.CCB1);                // Wait for synchrnoization
  TCC0->CCB[0].reg = 1200;                        // TCC0 CCB1 - 75% duty cycle on D4
  while (TCC0->SYNCBUSY.bit.CCB0);                // Wait for synchronization
  delay(1000);                                    // Wait for 1 second
}

Sorry MartinL ! I am not to said! I use robotdyn m0-mini robotdyn_arduino_zero_arduino_m0

in attachement Schematic
d3 connected to "pa09"
d4 connected to "pa08"
Is that means my d3 and d4 pin on peripheral E ? for timer tcc0?

Hi Nilab

in attachement Schematic
d3 connected to "pa09"
d4 connected to "pa08"
Is that means my d3 and d4 pin on peripheral E ? for timer tcc0?

Yes that's right. In this instance the TCC0 channels 0 and 1 are now on peripheral E for pins D4 and D3 respectively, the same as the Arduino Zero.

Just replace the Peripheral Multiplexing (PMUX) lines in the above example with the following line:

// D3 is on ODD port pin PA09 and TCC0/WO[1] channel 1 is on peripheral E
// D4 is on EVEN port pin PA08 and TCCO/WO[0] channel 0 is on peripheral E
PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;

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

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

How do that? in Zero ?

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: Metro M4 Express ATSAMD51 PWM Frequency and Resolution - Microcontrollers - Arduino Forum.

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

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.

#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! **

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.