I have an Arduino Due, which has an Atmel SAM3XE MCU.
I want to generate a PWM signal and I want to change the duty cycle every cycle of the PWM reliably so I can generate an output signal such as a sine on an output pin.
How can I implement this using an interrupt? I need to add an interrupt routine, that will be run in time before next PWM cycle starts.
I have this code so far:
// Output 50% duty cycle PWM at 10kHz on digital pins D4 and D5 using TC6
void setup() {
REG_PMC_PCER1 |= PMC_PCER1_PID33; // Enable peripheral TC6 (TC2 Channel 0)
REG_PIOC_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25; // Switch the multiplexer to peripheral B for TIOA6 and TIOB6
REG_PIOC_PDR |= PIO_PDR_P26 | PIO_PDR_P25; // Disable the GPIO on the corresponding pins
REG_TC2_CMR0 = TC_CMR_BCPC_SET | // Set TIOB on counter match with RC0
TC_CMR_ACPC_SET | // Set TIOA on counter match with RC0
TC_CMR_BCPB_CLEAR | // Clear TIOB on counter match with RB0
TC_CMR_ACPA_CLEAR | // Clear TIOA on counter match with RA0
TC_CMR_WAVE | // Enable wave mode
TC_CMR_WAVSEL_UP_RC | // Count up with automatic trigger on RC compare
TC_CMR_EEVT_XC0 | // Set event selection to XC0 to make TIOB an output
TC_CMR_TCCLKS_TIMER_CLOCK1; // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 48MHz)
REG_TC2_RC0 = 1400; // Load the RC0 register, 30 kHz PWM
REG_TC2_RA0 = 350; // Load the RB0 register
REG_TC2_RB0 = 700+350; // Load the RB0 register
REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Enable the timer TC6
}
void loop() {}
-
\$\begingroup\$ well, then do write an interrupt service routine that adjusts the duty cycle, and then "register" it as to be triggered on every timer reset event? \$\endgroup\$Marcus Müller– Marcus Müller2018年03月11日 11:43:43 +00:00Commented Mar 11, 2018 at 11:43
1 Answer 1
You can do something like this. This enables the interrupt handler and you can update the R* register values in the interrupt handler. However I'm not entirely sure if this is the safest way to do it.
// Output 50% duty cycle PWM at 10kHz on digital pins D4 and D5 using TC6
void setup() {
REG_PMC_PCER1 |= PMC_PCER1_PID33; // Enable peripheral TC6 (TC2 Channel 0)
REG_PIOC_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25; // Switch the multiplexer to peripheral B for TIOA6 and TIOB6
REG_PIOC_PDR |= PIO_PDR_P26 | PIO_PDR_P25; // Disable the GPIO on the corresponding pins
REG_TC2_CMR0 = TC_CMR_BCPC_SET | // Set TIOB on counter match with RC0
TC_CMR_ACPC_SET | // Set TIOA on counter match with RC0
TC_CMR_BCPB_CLEAR | // Clear TIOB on counter match with RB0
TC_CMR_ACPA_CLEAR | // Clear TIOA on counter match with RA0
TC_CMR_WAVE | // Enable wave mode
TC_CMR_WAVSEL_UP_RC | // Count up with automatic trigger on RC compare
TC_CMR_EEVT_XC0 | // Set event selection to XC0 to make TIOB an output
TC_CMR_TCCLKS_TIMER_CLOCK1; // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 48MHz)
REG_TC2_RC0 = 1400; // Load the RC0 register, 30 kHz PWM
REG_TC2_RA0 = 350; // Load the RB0 register
REG_TC2_RB0 = 700+350; // Load the RB0 register
// enable interrupts
TC2 -> TC_CHANNEL[0].TC_IER = TC_IER_CPAS // interrupt on RA compare match
| TC_IER_CPBS // interrupt on RB compare match
| TC_IER_CPCS; // interrupt on RC compare match
// enable interrupt vector
NVIC_EnableIRQ(TC6_IRQn);
REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Enable the timer TC6
}
void loop() {}
//TC6 interrupt handler
void TC6_Handler() {
// read interrupt status
uint32_t status = TC2 -> TC_CHANNEL[0].TC_SR;
if (status & TC_SR_CPAS) {
// RA compare match
} else if (status & TC_SR_CPBS) {
// RB compare match
} else if (status & TC_SR_CPCS) {
// RC compare match
TC2 -> TC_CHANNEL[0].TC_RC = 2000;
}
}
You can also program a PMW signal using the PWM controller on the Due. This way you can let the PDC DMA update the duty cycle values automatically. If needed I can look to make a quick sketch to test it out.