Arduino Due PWM Frequency Reduction

Hi everybody,

I'll be direct... I have to command an ESC (Electronic Speed Controller) through PWM, but it wants a PWM frequency between 30-450Hz and, as far as I know, Due's default PWM frequency is much higher than that.

I just wanted to set that frequency for 6 of the 12 PWM capable pins (I have 6 ESCs) to use analogWrite() to command them, without messing up the board timing.

PD: I have never tried Direct Port Manipulation before, but I would be happy to learn (if necessary), thank you!

ESCs Datasheet (See last page): http://dl.djicdn.com/downloads/e310/en/E310_User_Manual_v1.0_en.pdf

Hi FSXPRO2000,

The following code will set-up 11-bit resolution, dual slope PWM at 400Hz, on 8 channels of the Due's PWM controller:

// Enable dual slope, 11-bit resolution PWM at 400Hz on 8 channels
void setup() {
  // PWM set-up on pins DAC1, A8, A9, A10, D9, D8, D7 and D6 for channels 0 through to 7 respectively
  REG_PMC_PCER1 |= PMC_PCER1_PID36;                                               // Enable PWM 
  REG_PIOB_ABSR |= PIO_ABSR_P19 | PIO_ABSR_P18 | PIO_ABSR_P17 | PIO_ABSR_P16;     // Set the port B PWM pins to peripheral type B
  REG_PIOC_ABSR |= PIO_ABSR_P24 | PIO_ABSR_P23 | PIO_ABSR_P22 | PIO_ABSR_P21;     // Set the port C PWM pins to peripheral type B
  REG_PIOB_PDR |= PIO_PDR_P19 | PIO_PDR_P18 | PIO_PDR_P17 | PIO_PDR_P16;          // Set the port B PWM pins to outputs
  REG_PIOC_PDR |= PIO_PDR_P24 | PIO_PDR_P23 | PIO_PDR_P22 | PIO_PDR_P21;          // Set the port C PWM pins to outputs
  REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42);                               // Set the PWM clock A rate to 2MHz (84MHz/42)
  
  for (uint8_t i = 0; i < PWMCH_NUM_NUMBER; i++)                       // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CMR = PWM_CMR_CALG | PWM_CMR_CPRE_CLKA;     // Enable dual slope PWM and set the clock source as CLKA
    PWM->PWM_CH_NUM[i].PWM_CPRD = 2500;                                // Set the PWM frequency 2MHz/(2 * 2500) = 400Hz;
  } 
 
  REG_PWM_ENA = PWM_ENA_CHID7 | PWM_ENA_CHID6 | PWM_ENA_CHID5 | PWM_ENA_CHID4 |    // Enable all PWM channels
                PWM_ENA_CHID3 | PWM_ENA_CHID2 | PWM_ENA_CHID1 | PWM_ENA_CHID0;
  for (uint8_t i = 0; i < PWMCH_NUM_NUMBER; i++)                      // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTYUPD = 1000;                            // Set the PWM duty cycle to 1000 min throttle, 2000 max throttle
  } 
}

void loop() {}

To control the ESC throttle just load the CDTYUPD (Channel Duty Update) register with 0 for off, then starting at 1000 for minimum throttle through to 2000 for maximum in the loop() section of your sketch. Using the update duty cycle register updates the timer at the beginning of its cycle, this prevents glitches from appearing on the output waveform. It's also possible to use the instance register form:

REG_PWM_CDTYUPD0 = 1000;    // Set the duty cycle update register on channel 0

This is the same as:

PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 1000;

Setting the period register (PWM_CPRD) to 20000, changes the PWM frequency to 50Hz, so that it also allows you to control analog servos. Again 1000 minimum, 2000 maximum, 1500 centred.

Warning: Please remove the propellers if testing this code with brushless motors.

Hi MartinL,

Thank you very much for your time. This morning I did some reading about register manipulation and even if I'am still unfamiliar with SAM3X architecture, I think I'm following you.

I have the 6 ESCs wired to digital pins 2 to 7 using connectors, so I would prefer not changing them. Is it possible to change PWM frequency specifically for those pins? Or maybe pins 8 to 13 (I have a spare connector I could use), (I also read that pins 5 and 6 behave a little different from the others, ending note here: analogWrite() - Arduino Reference).

In my ideal world I would like just to change PWM frequency for 6 digital pins and then use analogWrite() in my code normally, is it possible?

Thank you again!

Unfortunately, it's not possible to use the Arduino Due's PWM Controller output pins for digital pins in the D2-D13 range other than D6, D7, D8 and D9.

However, D2, D3, D4 and D5 are connected to the TC timer outputs. The TC timers are capable of PWM, albeit with more limited functionality.

The TC timers don't offer the benefit of buffered (update) registers in the PWM Controller, which means instead it'll be necessary to update in the timers in the overflow interrupt service routine (ISR), to prevent glitches appearing on your output waveforms.

Here's some code that outputs 400Hz, single slope PWM with TC6 (confusingly a.k.a TC2 Channel 0) on digital pins D5 and D6, (although it lacks the overflow ISR code):

// Output 50% duty cycle PWM at 400Hz 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 = 42MHz)

  REG_TC2_RA0 = 52500;                              // Load the RA0 register
  REG_TC2_RB0 = 52500;                              // Load the RB0 register
  REG_TC2_RC0 = 105000;                             // Load the RC0 register
  
  REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN;       // Enable the timer TC6
}

void loop() {}

Note that the compare and overflow registers (RA0, RB0 and RC0) have been multiplied up to account for the fact that the TC6 timer is running at 42MHz with single slope PWM, rather than 2MHz with dual slope, as in the previous PWM controller example.

I believe on the Arduino Due analogWrite() outputs at a fixed PWM frequency of 1kHz, although I have seen some suggestions about modifying the "variant.h" file, in order to change this.