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
}
... 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.
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 ?
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
}
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;
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.
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.
Thank you so much for clearing that up. Shame on me for not reading the datasheet more carefully. It is well laid out in the Configuration Summary section.
Another question for you if you don't mind since you seem quite knowledgeable on this subject. Is there a recommended way to phase shift my PWM outputs so they don't all switch at the same time? I am driving MOSFETs directly from the MCU pins which will drive LEDs, I am just trying to spread out current spikes.
I am using all 3 TCCs and need to be able to vary the duty cycle dynamically for each channel.
Shift pulses within the period timeframe, basically boils down to 3 options, but depends on which channels you're using on the TCCx timers:
1) Deadtime Insertion
Deadtime Insertion can delay the leading edge of the complementary low side output, by loading the 8-bit DTLS register with the number of GCLK timer ticks the waveform is to be delayed. However, this functionality is only available on the TCC0 timer and the single deadtime counter can either be activated or deactivated on each channel (and it's inverted complementary output).
2) Dual-Slope Critical PWM Mode
Dual-Slope Critical PWM Mode sets up a centre-aligned PWM pulse and uses one CCx channel to control the postion of the leading edge and a second to control the trailing. This method is only available on TCC0 and essentially sacrifices two CC channels per waveform, such that TCC0 can provide only two Dual Slope Critical waveforms.
3) Output Inversion/Channel Polarity
Simply inverting the output and the CCx register's value with respect to the period (PER-CCx), allows the waveform to be moved from the front to the rear of period timeframe.
It's also possible to achieve the same effect by altering the channel's polarity, in other words reversing the compare output.
Here's an example that uses Dual-Slope Critical PWM on TCC0 and Dual-Slope PWM on TCC1, together with channel polarity to generate the phase shifted signals on four outputs at 100Hz: D4 (yellow), D2 (light blue), D5 (pink) and D3 (dark blue):
// Set-up TCC0 and TCC1 for Dual-Slope and Dual-Slope Critical PWM on digital pins D4, D2, D5, D3
void setup()
{
// Feed GCLK0 to TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the PWM channels on TCC0: D2 & D5, TCC1 D4 & D3
PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
// Connect port multiplexer pins to the TCC0 and TCC1 timers
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
TCC0->WAVE.reg = TCC_WAVE_POL(0x5) | // Reverse the output polarity on TCC0 outputs 0 and 2
TCC_WAVE_WAVEGEN_DSCRITICAL; // Set the PWM output to dual-slope critical PWM mode
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC1->WAVE.reg = TCC_WAVE_POL(0x1) | // Reverse the output polarity on TCC1 channel 0
TCC_WAVE_WAVEGEN_DSBOTTOM; // Set the PWM output to dual slope PWM mode
while (TCC1->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC0->PER.reg = 239999; // Set the frequency of the PWM on PER to 100Hz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC1->PER.reg = 239999; // Set the frequency of the PWM on PER to 100Hz
while (TCC1->SYNCBUSY.bit.PER); // Wait for synchronization
// CC0 and CC2 pair on channel 0, 50% duty-cycle, phase delay 60 degrees
TCC0->CC[0].reg = 200000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CC[2].reg = 40000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
// CC1 and CC3 pair on channel 1, 50% duty-cycle, phase delay 120 degrees
TCC0->CC[1].reg = 40000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CC[3].reg = 200000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
// CC0, 50% duty-cycle, phase delay 0 degrees
TCC1->CC[0].reg = 120000; // Set the duty-cycle to 50%
while (TCC1->SYNCBUSY.bit.CC0); // Wait for synchronization
// CC1, 50% duty-cycle, phase delay 180 degrees
TCC1->CC[1].reg = 120000; // Set the duty-cycle to 50%
while (TCC1->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 counter
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
TCC1->CTRLA.bit.ENABLE = 1; // Enable the TCC1 counter
while (TCC1->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {}
I was mulling over the possibilities and realised that it is actually possible to get staggered outputs from all the TCC timers: TCC0, TCC1 and TCC2. The is achieved using Dual-Slope in DSBOTH mode and the circular buffer that switches between the CCx and buffered CCBx registers.
The following example outputs the same signals in the oscilloscope image above, but on D2, D5, D6 and D7, only using the TCC0 timer in DSBOTH mode.
If the PWM signals are slow enough, it's possible to change the CCx/CCB registers using an overflow interrupt service routine, which I've also included in the code. If the signals are fast (around 10kHz+) then it might be better to use the Direct Memory Access Controller (DMAC) to automatically load the CCx/CCBx registers from memory.
// Set-up TCC0 Dual-Slope DSBOTH PWM on digital pins D4, D2, D6, D7
void setup()
{
// Feed GCLK0 to TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the PWM channels on TCC0: D2, D5, TCC1 D4 & D3
PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
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 port multiplexer pins to the TCC0 and TCC1 timers
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
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)
//TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Enable the overflow (OVF) interrupt
TCC0->WAVE.reg = TCC_WAVE_CICCEN3 | // Enable the circular counter compare CC3 <--> CCB3 buffer
TCC_WAVE_CICCEN2 | // Enable the circular counter compare CC2 <--> CCB2 buffer
TCC_WAVE_CICCEN1 | // Enable the circular counter compare CC1 <--> CCB1 buffer
TCC_WAVE_CICCEN0 | // Enable the circular counter compare CC0 <--> CCB0 buffer
TCC_WAVE_CIPEREN | // Enable the circular period PER <--> PERB buffer
TCC_WAVE_POL(0xF) | // Reverse the output polarity on all TCC0 outputs
TCC_WAVE_WAVEGEN_DSBOTH; // Set the PWM output to dual-slope critical PWM mode
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC0->DRVCTRL.reg |= TCC_DRVCTRL_INVEN7 | // Invert the CC3/CCB3 output
TCC_DRVCTRL_INVEN6; // Invert the CC2/CCB2 output
TCC0->PER.reg = 239999; // Set the frequency of the PWM on PER to 100Hz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC0->PERB.reg = 239999; // Set the frequency of the PWM on PER to 100Hz
while (TCC0->SYNCBUSY.bit.PERB); // Wait for synchronization
// CC0 and CCB0 pair on channel 0, 50% duty-cycle, phase delay 0 degrees
TCC0->CC[0].reg = 120000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CCB[0].reg = 120000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CCB0); // Wait for synchronization
// CC1 and CCB1 pair on channel 1, 50% duty-cycle, phase delay 60 degrees
TCC0->CC[1].reg = 200000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CCB[1].reg = 40000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CCB1); // Wait for synchronization
// CC2 and CCB2 pair on channel 1, 50% duty-cycle, phase delay 120 degrees
TCC0->CC[2].reg = 40000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC0->CCB[2].reg = 200000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CCB2); // Wait for synchronization
// CC3 and CCB3 pair on channel 1, 50% duty-cycle, phase delay 180 degrees
TCC0->CC[3].reg = 120000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
TCC0->CCB[3].reg = 120000; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 counter
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {}
void TCC0_Handler()
{
if (TCC0->INTFLAG.bit.OVF) // Test the overflow (OVF) interrupt flag
{
TCC0->INTFLAG.bit.OVF = 1; // Clear the OVF interrupt flag
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Initalise read synchronization
while(TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchronization
while(TCC0->SYNCBUSY.bit.COUNT); // Wait for read synchronization
uint32_t count = TCC0->COUNT.reg; // Read the TCC0 COUNT register
if (count < 100) // Test if it's a ZERO (rather than TOP update)
{
if (TCC0->CC[3].reg == 120000)
{
TCC0->CC[3].reg = 60000; // Change the CCx and CCBx registers here...
TCC0->CCB[3].reg = 60000;
}
else
{
TCC0->CC[3].reg = 120000;
TCC0->CCB[3].reg = 120000;
}
}
}
}
you are doing a great work thanks for all replys.I could't figure out to get 31Khz or 30 Khz on pins 9 and 10. I hope you can help me too.Thanks for your help
Here's some example code that outputs PWM at a 30kHz frequency and 50% duty-cycle on digital pins: D9 and D10. The code utilises TCC0 channel 2 and TCC1 channel 1:
// Set-up TCC0 and TCC1 for 30kHz PWM, 50% duty-cycle on digital pins 9 and 10
void setup()
{
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TCC0_TCC1; // Connect GCLK0 to timers TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the PWM channel for digital pins 9 and 10
PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 channel 2 and TCC1 channel 1 outputs to digital pins 9 and 10
PORT->Group[g_APinDescription[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set the PWM output to dual-slope critical PWM mode
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set the PWM output to dual-slope critical PWM mode
while (TCC1->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC0->PER.reg = 1599; // Set the frequency of the PWM to 30kHz ((48MHz/30kHz)-1) on D9
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC1->PER.reg = 1599; // Set the frequency of the PWM to 30kHz ((48MHz/30kHz)-1) on D10
while (TCC1->SYNCBUSY.bit.PER); // Wait for synchronization
TCC0->CC[2].reg = 800; // Set the duty-cycle to 50%
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC1->CC[1].reg = 800; // Set the duty-cycle to 50%
while (TCC1->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 timer
TCC1->CTRLA.bit.ENABLE = 1; // Enable the TCC1 timer
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
while (TCC1->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {}
I've been attempting to get 10 independent PWM signals at 25khz for a couple days now. Ideally, I would like to change the duty cycle of each so I have independent control. I'm thinking of using variables to create fun PWM patterns with math.
So far, I've had luck with altering this snippet for 3 PWM example a few pages back:
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation: 16MHz / (1 * (319 + 1) * 2) = 25kHz
TCC0->PER.reg = 319; // Set the frequency of the PWM on TCC0 to 25kHz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Set the CC[x] registers for 50% duty-cycle
TCC0->CC[0].reg = 16; // TCC0 CC0 - 5% duty cycle on D4
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CC[2].reg = 16; // TCC0 CC1 - 5% duty cycle on D2
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC0->CC[3].reg = 16; // TCC0 CC2 - 5% duty cycle on D3
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
However, when I try to add more pins for example pin 5 with CC1. It breaks to code. I tried hacking together some examples but it always breaks the code for all the other pins.
I'm also aware this is close to 8-bit resolution. Would it be possible to go to 10-bit or higher?
Thanks in advance. My eyes are melting.
Full code:
// MKR Zero: Output 20kHz PWM on timer TCC0 (8-bit resolution) on 3 channels on D4 (0), D2 (2) and D3 (3)
void setup()
{
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) | // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the 3 PWM channels: timer TCC0 outputs on D4, D2 and D3 + D5
PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
//PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1; //breaks code
// Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
// Peripheral F specifies the timer: TCC0
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;
//i had a line here but it only broke the code//
// Feed GCLK4 to TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
TCC0->WAVE.reg = TCC_WAVE_POL(0xF) | // Reverse the output polarity on all TCC0 outputs
TCC_WAVE_WAVEGEN_DSBOTTOM; // Set up dual slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation: 16MHz / (1 * (319 + 1) * 2) = 25kHz
TCC0->PER.reg = 319; // Set the frequency of the PWM on TCC0 to 25kHz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Set the CC[x] registers for 50% duty-cycle
TCC0->CC[0].reg = 16; // TCC0 CC0 - 5% duty cycle on D4
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CC[2].reg = 16; // TCC0 CC1 - 5% duty cycle on D2
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC0->CC[3].reg = 16; // TCC0 CC2 - 5% duty cycle on D3
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
// TCC0->CC[4].reg = 20; // TCC0 CC2 - 50% duty cycle on D5 //doesn't work
// while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization
// Divide the 16MHz signal by prescaler 1 giving 16MHz (62.5ns) TCC0 timer tick
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1; // Divide GCLK4 by 1
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 timer
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() // Change TCC0 timer channel 0 between 25% and 75% duty-cycle using buffered CCB[0] register
{
// TCC0->CCB[0].reg = 50; // TCC0 CCB0 - 25% duty cycle on D4
// while (TCC0->SYNCBUSY.bit.CCB0); // Wait for synchronization
// delay(2000); // Wait for 1 second
// TCC0->CCB[0].reg = 240; // TCC0 CCB0 - 75% duty cycle on D4
// while (TCC0->SYNCBUSY.bit.CCB0); // Wait for synchronization
// delay(1000); // Wait for 1 second
}