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);
}
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:
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.
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??
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:
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.
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
}
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
}
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
}
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(){}