Hey Guys
I looking for some help on implementing two pwm waveforms on a feather M0. They are to control two switches that cant be on at the same time. A sketch of the waveforms required is attached. The other issue is to have two bursts of the waveform only. The current code I have attached uses a interupt and a count but unfortunately it still give me a minimum of three bursts.
With some help from the forum I was able to implement these waveform on the Arduino Due using complementary waveforms and dead-time but unfortunately the due cannot be used for the project and I need to use the feather M0. So im back to the drawing board in my knowledge.
The code I have so far gives two out puts one at 60us and one for 720us. If i could implement dead time on the second output so it starts after 120us this would achieve the aim of 60us on one pin then a break of 60us and then a 600us on the second pin. However I dont know how to do this as much as Ive looked through the datasheet so I'm not sure if this can be done. If anyone has any other way i could achieve the wave forms attached it would be greatly appreciated. My time is running out.
The current code is:
// Output 140Hz PWM on timer TCC0 (D12) and TCC2 (D13) on Adafruit Feather M0
void setup()
{
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) | // Divide the 48MHz clock source by divisor 1: 48MHz/4=12MHz
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 D13(PA17)), D12(PA19)
PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to digital output D13 = PA17 = ODD - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[13].ulPort].PMUX[g_APinDescription[13].ulPin >> 1].reg = PORT_PMUX_PMUXO_E ;
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg = PORT_PMUX_PMUXO_F ;
// 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
// 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
NVIC_SetPriority(TCC0_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn); // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)
// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
REG_TCC2_WAVE |= TCC_WAVE_WAVEGEN_NPWM; //single slope
while (TCC2->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM; //single slope
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_TCC2_PER = 43165; // Set the frequency of the PWM on TCC2 to 140Hz
while (TCC2->SYNCBUSY.bit.PER); // 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 = 43165; // Set the frequency of the PWM on TCC0 to 140Hz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Set the PWM signal to output 0%duty cycle
REG_TCC2_CC1 = 0; // TCC2 CC1 - on D13 initialise to 0% duty Cycle
while (TCC2->SYNCBUSY.bit.CC1); // Wait for synchronization
REG_TCC0_CC3 = 0; // TCC0 CC1 - on D12 initialise to 0% duty Cycle
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
// Divide the 48MHz signal by 4 giving 12MHz TCC2 timer tick and enable the outputs
REG_TCC2_CTRLA |= TCC_CTRLA_ENABLE; // Enable the TCC2 output
while (TCC2->SYNCBUSY.bit.ENABLE); // Wait for synchronization
// Divide the 48MHz signal by 4 giving 12MHz TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {
// Output a burst of 2 PWM pulses at 142Hz, 30% duty cycle every second
TCC0 ->INTENSET.bit.OVF = 1; // Enable overflow (OVF) interrupts on TCC0
delay(2000); // Wait 2 seconds
}
void TCC0_Handler()
{
static volatile uint32_t counter;
if (TCC0->INTENSET.bit.OVF && TCC0->INTFLAG.bit.OVF )
{
if (counter == 0)
{
counter++;
REG_TCC0_CCB3 = 363; // TCC0 CC1 - on D12 set to 50%Duty Cycle
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
REG_TCC2_CCB1 = 4351; // TCC2 CC1 - on D13 set to 50%Duty Cycle
while (TCC2->SYNCBUSY.bit.CC1); // Wait for synchronization
}
else if (counter < 3) // Have we counted up to 10?
{
counter++; // Increment the counter and continue
}
else
{
counter = 0; // Reset the counter
REG_TCC0_CCB3 = 0; // TCC0 CC3 - on D12 set to 0%Duty Cycle
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
REG_TCC2_CCB1 = 0; // TCC2 CC1 - on D13 set to 0%Duty Cycle
while (TCC2->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->INTENCLR.bit.OVF = 1; // Disable overflow (OVF) interrupts on TCC0
}
TCC0->INTFLAG.bit.OVF = 1; // Clear the interrupt flag
}
}
The easiest way to generate the pulses you require on the SAMD21 is to use the standard RAMP2 operation, rather than messing about with dead-time insertion and output override, like we had to do on the Arduino Due: How do I make a PWM signal run once only. - Arduino Due - Arduino Forum.
RAMP2 operation interleaves the two PWM channels with the period register (PER) and buffered period register (PERB) being automatically swapped each cycle, to account for the differing periods (and pulse widths) on each channel.
The interrupt service routine (ISR) then is activated to set-up two pulses on each of the two channels and then shut itself down once the total of 4 pulses have been sent.
The following code sets up timer TCC2 for standard RAMP2 operation on digital pins D11 and D13, generating two pulses on each channel every second:
// Use the TCC2 timer's standard RAMP2 Operation
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 2 PWM channels on 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 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_E | PORT_PMUX_PMUXE_E;
// 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
NVIC_SetPriority(TCC2_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC2 to 0 (highest)
NVIC_EnableIRQ(TCC2_IRQn); // Connect TCC2 to Nested Vector Interrupt Controller (NVIC)
// Interleave TCC2 channels 0 and 1
TCC2->WAVE.reg = //TCC_WAVE_CICCEN0 | // Enable the circular counter compare CC0 <--> CCB0 buffer
TCC_WAVE_CIPEREN | // Enable the circular period PER <--> PERB buffer
TCC_WAVE_RAMP_RAMP2 | // Enable alternative ramp 2 operation (RAMP2A)
TCC_WAVE_WAVEGEN_NPWM; // Set the PWM output to normal (single slope) PWM mode
while (TCC2->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC2->PER.reg = 119; // Set the frequency of the PWM on PER to 120us for the first channel on D11
while (TCC2->SYNCBUSY.bit.PER); // Wait for synchronization
TCC2->PERB.reg = 7027; // Set the frequency of the PWM on PERB to 7027us for the second channel on D13
while (TCC2->SYNCBUSY.bit.PERB); // Wait for synchronization
// The CCx/CCBx register values corresponds to the pulse width in microseconds (us)
TCC2->CC[0].reg = 0; // Set initial pulse width to 0us for the first channel on D11
while (TCC2->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC2->CC[1].reg = 0; // Set initial pulse width to 0us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC2->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC2->CTRLA.bit.ENABLE = 1; // Enable the TCC2 counter
while (TCC2->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {
// Output a burst of two PWM pulses on each channel every second
TCC2->INTENSET.bit.OVF = 1; // Enable overflow (OVF) interrupts on TCC2
delay(1000); // Wait 1 second
}
void TCC2_Handler()
{
static volatile uint32_t counter;
if (TCC2->INTENSET.bit.OVF && TCC2->INTFLAG.bit.OVF)
{
if (counter == 0)
{
if (TCC2->STATUS.bit.IDX == 0) // Sync the first cycle to Ramp A
{
TCC2->INTFLAG.bit.OVF = 1; // Clear the interrupt flag
return; // Exit this interrupt service routine (ISR) and wait for next cycle
}
counter++; // Increment the counter and continue
TCC2->CCB[0].reg = 59; // Set pulse width to 60us for the first channel on D11
while (TCC2->SYNCBUSY.bit.CCB0); // Wait for synchronization
TCC2->CCB[1].reg = 599; // Set the pulse width to 600us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CCB1); // Wait for synchronization
}
else if (counter < 4) // Have we counted up to 4 timer cycles?
{
counter++; // Increment the counter and continue
}
else
{
counter = 0; // Reset the counter
TCC2->CCB[0].reg = 0; // Set pulse width to 0us for the first channel on D11
while (TCC2->SYNCBUSY.bit.CCB0); // Wait for synchronization
TCC2->CCB[1].reg = 0; // Set the pulse width to 0us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CCB1); // Wait for synchronization
TCC2->INTENCLR.bit.OVF = 1; // Disable overflow (OVF) interrupts on TCC2
}
TCC2->INTFLAG.bit.OVF = 1; // Clear the interrupt flag
}
}
Once again you have given a great answer. And I can now see Ramp2 operation in the datasheet.
Im wondering though is there anyway to do such an operation on two odd channels such as PA017 and PA19 (D12 and D13 on the feather M0) it seems this operation can only be used on odd/even pin pairs of the same timer output is that correct?.
I ask this because these are the only two pins I have available on my project for this function and it will not be easy to change it unfortunately.
Implementing the RAMP2 code got me thinking that it would be possible to implement the two interleaved channels, just by simply changing the waveforms' duty-cycle period every other cycle in software in the interrupt service routine. This is instead of using fancy hardware functionality like dead-time insertion or RAMP2 operation.
I've written two sketches that implement your waveforms using this simpler method. The Arduino Due implementation I've posted on the Arduino Due forum, in the thead I linked to above.
The Arduino Zero/Feather M0 implementation here uses D12 and D13 using timers TCC0 and TCC2 on the separate respective channels. The two timers should be synchronized to each other by the fact that they're both clocked from generic clock 4 (GCLK4), I just attempt to start them as close as possible to each other by simply enabling one after the other, before synchronizing their respective registers.
This code outputs the same waveforms on D12 and D13 using timers TCC0 channel 3 and TCC2 channel 1:
// Use the TCC0 and TCC2 to generate two interleaved PWM signals of differing duty-cycle and period
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 2 PWM channels on D12 and D13
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].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 outputs - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg = PORT_PMUX_PMUXO_F;// | PORT_PMUX_PMUXE_E;
PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg = PORT_PMUX_PMUXO_E;// | 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
NVIC_SetPriority(TCC0_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn); // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)
//NVIC_SetPriority(TCC2_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC2 to 0 (highest)
//NVIC_EnableIRQ(TCC2_IRQn); // Connect TCC2 to Nested Vector Interrupt Controller (NVIC)
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set the PWM output to normal (single slope) PWM mode
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set the PWM output to normal (single slope) PWM mode
while (TCC2->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC0->PER.reg = 119; // Set the frequency of the PWM on PER to 120us for the first channel on D12
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC2->PER.reg = 119; // Set the frequency of the PWM on PERB to 120us for the second channel on D13
while (TCC2->SYNCBUSY.bit.PER); // Wait for synchronization
// The CCx register values corresponds to the pulse width in microseconds (us)
TCC0->CC[3].reg = 0; // Set initial pulse width to 0us for the first channel on D12
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
TCC2->CC[1].reg = 0; // Set initial pulse width to 0us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC2->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 counter
TCC2->CTRLA.bit.ENABLE = 1; // Enable the TCC2 counter
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
while (TCC2->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {
// Output a burst of two PWM pulses on each channel every second
TCC0->INTENSET.bit.OVF = 1; // Enable overflow (OVF) interrupts on TCC0
delay(1000); // Wait 1 second
}
void TCC0_Handler()
{
static uint32_t counter;
if (TCC0->INTENSET.bit.OVF && TCC0->INTFLAG.bit.OVF)
{
if (counter == 0 || counter == 2)
{
counter++; // Increment the counter and continue
TCC0->CCB[3].reg = 59; // Set pulse width to 60us for the first channel on D12
while (TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC2->CCB[1].reg = 0; // Set the pulse width to 0us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CCB1); // Wait for synchronization
TCC0->PERB.reg = 119; // Set the period to 120us for the first channel on D12
while (TCC0->SYNCBUSY.bit.PERB); // Wait for synchronization
TCC2->PERB.reg = 119; // Set the period to 120us for the second channel on D13
while (TCC2->SYNCBUSY.bit.PERB); // Wait for synchronization
}
else if (counter == 1 || counter == 3)
{
counter++; // Increment the counter and continue
TCC0->CCB[3].reg = 0; // Set pulse width to 0us for the first channel on D12
while (TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC2->CCB[1].reg = 599; // Set the pulse width to 600us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CCB1); // Wait for synchronization
TCC0->PERB.reg = 7027; // Set the period to 7028us for the first channel on D12
while (TCC0->SYNCBUSY.bit.PERB); // Wait for synchronization
TCC2->PERB.reg = 7027; // Set the period to 7028us for the second channel on D13
while (TCC2->SYNCBUSY.bit.PERB); // Wait for synchronization
}
else
{
counter = 0; // Reset the counter
TCC0->CCB[3].reg = 0; // Set pulse width to 0us for the first channel on D12
while (TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC2->CCB[1].reg = 0; // Set the pulse width to 0us for the second channel on D13
while (TCC2->SYNCBUSY.bit.CCB1); // Wait for synchronization
TCC0->PERB.reg = 119; // Set the period to 120us for the first channel on D12
while (TCC0->SYNCBUSY.bit.PERB); // Wait for synchronization
TCC2->PERB.reg = 119; // Set the period to 120us for the second channel on D13
while (TCC2->SYNCBUSY.bit.PERB); // Wait for synchronization
TCC0->INTENCLR.bit.OVF = 1; // Disable overflow (OVF) interrupts on TCC0
}
TCC0->INTFLAG.bit.OVF = 1; // Clear the interrupt flag
}
}