PWM RAMP2 Operation

Hello! I decided make my own thread becose can be usefull to future ramp2 aplications.
I'm trying to make a code for pwm 2 outputs(pin 5 and pin 6), XXhz pwm frec.
Reading atmel SAMD21G datasheet found RAMP2 operation could be a solution, but is difficult due to huge register configuration needed of matches, modes, etc.
Upload picture show my idea how output should be work and that 90° off phases needed between W[5] and W[6] output. Only change % PWM and frec is constant.
I have problem to implement output idea due can't make a correct register configuration.
Someone can tell me if i'm in a right way, or suggest other solution, code examples, edit my code, etc? Any help is welcome.

Thanks!

#include <Adafruit_NeoPixel.h>

#define Pin13LED 13
#define NEOPIXEL 8  // Neopixel

int PERx = 32000; // PER=TOP value just for example

volatile int cont =0;

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

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;CCB1;Odd
  const uint8_t Pin_6 = 6;  // TCC0;CCB2;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;

  // Conecto el timer TCC0 con los puertos de salida
  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_CIPEREN |  // Diferentes PER en ciclo A y B
    TCC_WAVE_POL0 | // Change polarity output
    TCC_WAVE_POL1;
    TCC_WAVE_RAMP_RAMP2;  // RAMP2 operation
  while (TCC0->SYNCBUSY.bit.WAVE);    // Espero sincronizacion

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

  delay(50);

  // Configuro valor CCBx
  REG_TCC0_CC1 = (PERx/2);       // TCC0 CCB1 - 50% PWM on PIN 5
  while(TCC0->SYNCBUSY.bit.CC1);
  REG_TCC0_CC2 = (PERx/2);    // TCC0 CCB2 - 50% PWM on PIN 6
  while(TCC0->SYNCBUSY.bit.CC2);

  
  // Configuro preescaler y habilito las salidasTCC_CTRLBSET_IDXCMD_DISABLE |
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |
    TCC_CTRLA_CPTEN0 |  // capture channel 0 enable
    //TCC_CTRLBSET_IDXCMD_DISABLE |
    TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);  // Espero por sincronizacion

  delay(50);
  
}

void loop() 
{
  
  REG_TCC0_CCB1 = (PERx/2);                       // OUTPUT pin 5
  while(TCC0->SYNCBUSY.bit.CCB1);
  REG_TCC0_CCB2 = (PERx/2);                     //OUTPUT pin 6   
  while(TCC0->SYNCBUSY.bit.CCB2);

}

pwm output ideal.jpg

Hello b0rn4,

I'm a new user to the M0 pro as well. I try not to use any of the Arduino libraries because they are super slow and I need really high performance. I found a few resources that can help:

  1. Install Atmel studio. You can find it here: https://www.microchip.com/development-tools/atmel-studio-7

  2. After installing Atmel studio, in your computer (windows) you can go here:
    C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\SAMD21_DFP\1.2.276\samd21a\include\component
    This folder lists the component definitions for all the peripherals. This is super useful when you want to configure a timer, ADC, RTC etc.

  3. This wiki will help you to read and understand the component definitions if you're new to them:
    Getting Started with the SAM D21 Xplained Pro without ASF - Development Boards, Kits, Programmers - Engineering and Component Solution Forum - TechForum │ Digi-Key
    Once you understand the component definitions structure, its relatively simple to configure registers and peripherals just from reading the datasheet. Your code will also be very readable and easy to understand.

  4. Understand and use the Arduino pin definitions here:
    ArduinoCore-samd/variant.cpp at master · arduino/ArduinoCore-samd · GitHub
    For example, this is a more readable way to code:

    PORT->Group[PORTA].PINCFG[14].bit.PMUXEN = 1; // Peripheral mux enable PWM
    PORT->Group[PORTA].PINCFG[15].bit.PMUXEN = 1; // Peripheral mux enable PWM  
    PORT->Group[PORTA].PMUX[7].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F; // Enable PMUX group F both Odd and Even pins

This is not:

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

Both code samples do the exact same thing, but the first example is easier to debug.

Hello parsec326,

I need too high performance in my proyect and you make me curious about atmel studio.
I read many atmel application notes about pwm, all their examples are in atmel studio.
But my problem is that code is a small part of a big program to make a firmware for a stimulator. In other words, i also have made lot of interrups, functions, etc.
Its that all posible in atmel studio, and what about the time to learn??

Hope your response. Thanks!!

For large projects, you can split your functions into separate files. There is some prior discussion here, I'm sure you can find others with a simple Google search:

http://forum.arduino.cc/index.php?topic=41039.0

https://forum.arduino.cc/index.php?topic=69952.0

The way I do it is I create a new project:
new_project.ino
This file then calls functions like this: function1(); function2(); etc

Then I add multiple tabs with NO file extensions (Down facing arrow in upper right corner> New tabs):
function1_defn, function2_defn, function3_defn

The way arduino compiles is that it combines all the files (in alphabetical order) and then compiles the resulting file. By splitting your code in multiple files, it makes it easier to debug potential problems.

I don't use Atmel studio for programming, I only use the library definitions that are installed as part of Atmel studio. I believe the Arduino IDE is good enough for even complex projects.

Hi b0rn4,

The PWM RAMP2 operation allows two PWM channels to be interleaved, such that their two PWM timer outputs: CC0 (WO[0]/WO[4]) and CC1 (WO[1]/WO[5]) are never active at the same time. So if the both channels are set to the same frequency then the maximum duty cycle for each channel is 50%, as each can only be active half of the time.

It's also possible to change the frequency of each of the two channels, by loading the timer's PER (period) and PERB (period buffered) registers and setting the CIPEREN bit in the WAVE register. The microcontroller then switches between the PER and PERB values during operation. However, changing the frequency of one channel affects the frequency of the other.

The code below uses the PWM RAMP2 operation at 50Hz with the outputs on digital pins: D2 (on the Arduino M0/M0 Pro) or D4 (on the Arduino/Genuino Zero) and D3:

// Output 50Hz using timer TCC0 interleaved PWM outputs on D2/D4 and D3 (RAMP2 Operation)
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 digital pins D2/D4 and D3
  //PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;     // Comment out for Arduino/Genuino Zero
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;       // Don't comment out
  PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;       // Comment out for Arduino M0/M0 Pro
 
  // Connect the TCC0 timer to the port output D3 and D7 - port pins are paired odd PMUXO and even PMUXE
  // Peripherals E & F specify the timers: TCC0, TCC1 and TCC2
  
  // Comment out for Arduino/Genuino Zero
  //PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  
  // Comment out for Arduino M0/M0 Pro 
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  
  // 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

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= /*TCC_WAVE_CIPEREN |*/         // Set up circular PER/PERB buffer for inteleaved waveform frequency
    TCC_WAVE_RAMP_RAMP2 |                         // Set up RAMP2 operation to interleave channels CC0 and CC1
    TCC_WAVE_WAVEGEN_NPWM/* |                       // Setup single slope (normal) PWM on TCC0
    TCC_WAVE_POL0 |                               // Reverse the polarity of channel CC0
    TCC_WAVE_POL1*/;                                // Reverse the polarity of channel CC1
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Invert the drivers on TCC0/WO[0] (channel 0) and TCC0/WO[1] (channel 1)
  //REG_TCC0_DRVCTRL |= TCC_DRVCTRL_INVEN0 |      // Invert the driver on D2/D4
  //                    TCC_DRVCTRL_INVEN1;       // Invert the driver on D3
  
  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  // Formula: (GCLK_FREQUENCY / (TIMER_PRESCALER * PWM_FREQUENCY) - 1) / 2
  // (16MHz / (16 * 50) - 1) / 2 = 9999
  REG_TCC0_PER = 9999;                            // Set the frequency of the PWM on TCC0 to 50Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle on D2/D4
  REG_TCC0_CCB0 = 9999;                           // TCC0 CCB0 - on output on D2/D4 
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization

  // Set the PWM signal to output 25% duty cycle on D3
  REG_TCC0_CCB1 = 4999;                           // TCC0 CCB1 - on output on D3 
  while(TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
 
  // Divide the 16MHz signal by 16 giving a 1MHz (1.0us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV16 |   // Divide GCLK4 by 16
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds 
}

void loop(){    
  // Test the PWM outputs
  
  // Using the buffered CC0B and CCB1 registers allows the outputs to be changed on the next timer cycle
  // This prevents glitches that would occur with an instantaneous change using the CC0 and CC1 registers directly
  
  // Change the duty cycle to 25% on D2/D4 using the CCB0 buffered register
  REG_TCC0_CCB0 = 4999;                           // Load the CCB0 register with 25% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 37.5% on D3 using the CCB1 buffered register
  REG_TCC0_CCB1 = 7499;                           // Load the CCB1 register with 75% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
  
  // Change the duty cycle to 37.5% on D2/D4 using the CCB0 buffered register
  REG_TCC0_CCB0 = 7499;                          // Load the CCB0 register with 75% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 25% on D3 using the CCB1 buffered register
  REG_TCC0_CCB1 = 4999;                          // Load the CCB1 register with 50% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
}

MartinL,

I tried your code but had to change the pins. I made them 11 and 13. I got it semi-working, but I see a frequency of 100 Hz instead of 50 Hz, and my waveforms do not maintain a 50% duty cycle ratio between them.

How would I go about fixing this?

Also, how would I go about having 2 waveforms with a 50% duty cycle, but 90 degrees out of phase from each other? (ie, waveform 1 is on from 25% to 75%, while waveform 2 is on from 50% to 100%, with respect to the period)

// Output 50Hz using timer TCC0 interleaved PWM outputs on D2/D4 and D3 (RAMP2 Operation)
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 digital pins D2/D4 and D3
  //PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;     // Comment out for Arduino/Genuino Zero
  PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;       // Don't comment out
  PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;       // Comment out for Arduino M0/M0 Pro
 
  // Connect the TCC0 timer to the port output D3 and D7 - port pins are paired odd PMUXO and even PMUXE
  // Peripherals E & F specify the timers: TCC0, TCC1 and TCC2
  
  // Comment out for Arduino/Genuino Zero
  //PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  
  // Comment out for Arduino M0/M0 Pro 
  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[13].ulPort].PMUX[g_APinDescription[13].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

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= /*TCC_WAVE_CIPEREN |*/         // Set up circular PER/PERB buffer for inteleaved waveform frequency
    TCC_WAVE_RAMP_RAMP2 |                         // Set up RAMP2 operation to interleave channels CC0 and CC1
    TCC_WAVE_WAVEGEN_NPWM/* |                       // Setup single slope (normal) PWM on TCC0
    TCC_WAVE_POL0 |                               // Reverse the polarity of channel CC0
    TCC_WAVE_POL1*/;                                // Reverse the polarity of channel CC1
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Invert the drivers on TCC0/WO[0] (channel 0) and TCC0/WO[1] (channel 1)
  //REG_TCC0_DRVCTRL |= TCC_DRVCTRL_INVEN0 |      // Invert the driver on D2/D4
  //                    TCC_DRVCTRL_INVEN1;       // Invert the driver on D3
  
  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  // Formula: (GCLK_FREQUENCY / (TIMER_PRESCALER * PWM_FREQUENCY) - 1) / 2
  // (16MHz / (16 * 50) - 1) / 2 = 9999
  REG_TCC0_PER = 9999;                            // Set the frequency of the PWM on TCC0 to 50Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle on D2/D4
  REG_TCC0_CCB2 = 4999;                           // TCC0 CCB0 - on output on D2/D4 
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization

  // Set the PWM signal to output 25% duty cycle on D3
  REG_TCC0_CCB3 = 4999;                           // TCC0 CCB1 - on output on D3 
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
 
  // Divide the 16MHz signal by 16 giving a 1MHz (1.0us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV16 |   // Divide GCLK4 by 16
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds 
}

void loop(){    
  // Test the PWM outputs
  
  // Using the buffered CC0B and CCB1 registers allows the outputs to be changed on the next timer cycle
  // This prevents glitches that would occur with an instantaneous change using the CC0 and CC1 registers directly
  
  // Change the duty cycle to 25% on D2/D4 using the CCB0 buffered register
  REG_TCC0_CCB2 = 4999;                           // Load the CCB0 register with 25% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 37.5% on D3 using the CCB1 buffered register
  REG_TCC0_CCB3 = 7499;                           // Load the CCB1 register with 75% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
  
  // Change the duty cycle to 37.5% on D2/D4 using the CCB0 buffered register
  REG_TCC0_CCB2 = 7499;                          // Load the CCB0 register with 75% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 25% on D3 using the CCB1 buffered register
  REG_TCC0_CCB3 = 4999;                          // Load the CCB1 register with 50% of the PERB value
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
}

Hi darkhawk,

How would I go about fixing this?

The reason why RAMP2 operation it's not working on these pins, is that for digital pins 11 and 13 you have to use the TCC2 timer on channels 0 (WO[0]) and 1 (WO[1]):

// Output 50Hz using timer TCC2 interleaved PWM outputs on D11 and D13 (RAMP2 Operation)
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 digital pins D11 and D13
  PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;       
  PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;    
    
  // Connect the TCC2 timer to the port output D11 and D13 - port pins are paired odd PMUXO and even PMUXE
  // Peripherals E & F specify the timers: TCC2, TCC1 and TCC2
  PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  
  // Feed GCLK4 to TCC2 and TC3
  REG_GCLK_CLKCTRL = 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

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC2_WAVE |= /*TCC_WAVE_CIPEREN |*/         // Set up circular PER/PERB buffer for inteleaved waveform frequency
    TCC_WAVE_RAMP_RAMP2 |                         // Set up RAMP2 operation to interleave channels CC0 and CC1
    TCC_WAVE_WAVEGEN_NPWM/* |                       // Setup single slope (normal) PWM on TCC2
    TCC_WAVE_POL0 |                               // Reverse the polarity of channel CC0
    TCC_WAVE_POL1*/;                                // Reverse the polarity of channel CC1
  while (TCC2->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Invert the drivers on TCC2/WO[0] (channel 0) and TCC2/WO[1] (channel 1)
  //REG_TCC2_DRVCTRL |= TCC_DRVCTRL_INVEN0 |      // Invert the driver on D2/D4
  //                    TCC_DRVCTRL_INVEN1;       // Invert the driver on D3
  
  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  // Formula: (GCLK_FREQUENCY / (TIMER_PRESCALER * PWM_FREQUENCY) - 1) / 2
  // (16MHz / (16 * 50) - 1) / 2 = 9999
  REG_TCC2_PER = 9999;                            // Set the frequency of the PWM on TCC2 to 50Hz
  while(TCC2->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle on D2/D4
  REG_TCC2_CCB0 = 9999;                           // TCC2 CCB0 - on output on D2/D4 
  while(TCC2->SYNCBUSY.bit.CCB0);                 // Wait for synchronization

  // Set the PWM signal to output 25% duty cycle on D3
  REG_TCC2_CCB1 = 4999;                           // TCC2 CCB1 - on output on D3 
  while(TCC2->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
 
  // Divide the 16MHz signal by 16 giving a 1MHz (1.0us) TCC2 timer tick and enable the outputs
  REG_TCC2_CTRLA |= TCC_CTRLA_PRESCALER_DIV16 |   // Divide GCLK4 by 16
                    TCC_CTRLA_ENABLE;             // Enable the TCC2 output
  while (TCC2->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds 
}

void loop(){    
  // Test the PWM outputs
  
  // Using the buffered CC0B and CCB1 registers allows the outputs to be changed on the next timer cycle
  // This prevents glitches that would occur with an instantaneous change using the CC0 and CC1 registers directly
  
  // Change the duty cycle to 25% on D2/D4 using the CCB0 buffered register
  REG_TCC2_CCB0 = 4999;                           // Load the CCB0 register with 25% of the PERB value
  while(TCC2->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 37.5% on D3 using the CCB1 buffered register
  REG_TCC2_CCB1 = 7499;                           // Load the CCB1 register with 75% of the PERB value
  while(TCC2->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
  
  // Change the duty cycle to 37.5% on D2/D4 using the CCB0 buffered register
  REG_TCC2_CCB0 = 7499;                          // Load the CCB0 register with 75% of the PERB value
  while(TCC2->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds

  // Change the duty cycle to 25% on D3 using the CCB1 buffered register
  REG_TCC2_CCB1 = 4999;                          // Load the CCB1 register with 50% of the PERB value
  while(TCC2->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                    // Wait for 2 seconds
}

Hi darkhawk,

Also, how would I go about having 2 waveforms with a 50% duty cycle, but 90 degrees out of phase from each other? (ie, waveform 1 is on from 25% to 75%, while waveform 2 is on from 50% to 100%, with respect to the period)

To get a 90 degree phase shift it's probably easiest to use dual slope critical PWM, as this gives complete control over the rising and falling edges of the PWM waveform.

In the following code timer TCC0 is routed to outputs D11 and D13, the timer is set to output both waveforms at 50Hz. In dual slope critical PWM mode registers CC0 and CC2 control the respective falling and rising edges of the waveform on D11, while registers CC1 and CC3 control the respective falling and rising edges on D13. The CCx (or in this case the buffered CCBx) registers are loaded such that the waveforms are shifted 90 degrees with respect to each other:

// Output 50Hz using timer TCC0 with dual slope critical PWM on D11 and D13 with 90 degrees phase shift
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 digital pins D11 and D13
  PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;       
  PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;    
    
  // Connect the TCC0 timer to the port output D11 and D13 - port pins are paired odd PMUXO and even PMUXE
  // Peripherals E & F specify the timers: TCC0, TCC1 and TCC0
  PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].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

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_DSCRITICAL |  // Setup dual slope critical PWM on TCC0
                   TCC_WAVE_POL(0xF);             // Reverse the polarity of all TCC0 channels: CC0, CC1, CC2 and CC3
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization
  
  REG_TCC0_WEXCTRL |= TCC_WEXCTRL_OTMX(0x1);      // Set the output matrix to output CC0 on D11 and CC1 on D13
  
  // In dual slope mode the TCC0 timer counts up to a maximum or TOP value set by the PER register then down to zero
  // this determines the frequency of the PWM operation:
  // Formula: GCLK_FREQUENCY / (2 * TIMER_PRESCALER * PWM_FREQUENCY) - 1
  // 16MHz / (2 * 8 * 50) - 1 = 19999
  REG_TCC0_PER = 19999;                           // Set the frequency of the PWM on TCC0 to 50Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle on D11
  REG_TCC0_CCB0 = 14999;                          // Set the start of the waveform on up count
  while(TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  REG_TCC0_CCB2 = 4999;                           // Set the end of the waveform on down count
  while(TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  
  // Set the PWM signal to output 50% duty cycle on D13 with 90 degree phase shift
  REG_TCC0_CCB1 = 4999;                           // Set the start of the waveform on up count
  while(TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  REG_TCC0_CCB3 = 14999;                          // Set the end of the waveform on down count
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
 
  // Divide the 16MHz signal by 8 giving a 2MHz (0.5us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 |    // Divide GCLK4 by 8
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop(){}

Thank you so much. That makes it so much clearer for my application.

:slight_smile: