Electronic – arduino – How to reliably update duty cycle to PWM on arduino due with SAM3XE

arduinointerruptspwm

I have arduino due, which has Atmel SAM3XE MCU.

I want to generate PWM signal and I want to change duty cycle every cycle of PWM reliably so
i can generate output signal such as sine on 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() {}

Best Answer

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.