Arduino Due 490 Hz PWM

I'm currently working on controlling 8 thrusters through PWM outputs for a ROV project. I started off with the Arduino Mega 2560, but have now moved on to the Arduino Due as resolution is better and it's quite much faster. My problem however is that I can not figure out how to set 8 of my outputs to 490 Hz.. I've googled for hours, but the closest I'm getting is 490 Hz on pins 6,7,8,9 (pwm01.lib by Collin80), but I need 4 more....

The mega was quite easy, addressing TCCRxB timers....

Edit: Pin 4 and 10 are in use for a W5100 ethernet shield

type or paste code here

edit: nvm. I'll continue using the 2560.

490Hz is very low for motor control via PWM - why do you think you want 490Hz? For motor control 4kHz/8kHz/16kHz are commonly used values, higher frequencies for smaller motors typically (since they have lower inductances).

Can you provide links to the hardware (motors, motor drivers especially).

1 Like

Hi @rayvaa

Here's an example that outputs 490Hz PWM on all of the Due's 8 PWM controller channels:

// Enable dual slope, 11-bit resolution PWM at 490Hz 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
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                                               // Enable PWM controller
  PIOB->PIO_ABSR |= PIO_ABSR_P19 | PIO_ABSR_P18 | PIO_ABSR_P17 | PIO_ABSR_P16;     // Set the port B PWM pins to peripheral type B
  PIOC->PIO_ABSR |= PIO_ABSR_P24 | PIO_ABSR_P23 | PIO_ABSR_P22 | PIO_ABSR_P21;     // Set the port C PWM pins to peripheral type B
  PIOB->PIO_PDR |= PIO_PDR_P19 | PIO_PDR_P18 | PIO_PDR_P17 | PIO_PDR_P16;          // Set the port B PWM pins to outputs
  PIOC->PIO_PDR |= PIO_PDR_P24 | PIO_PDR_P23 | PIO_PDR_P22 | PIO_PDR_P21;          // Set the port C PWM pins to outputs
  PWM->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 = 2040;                                // Set the PWM frequency 2MHz/(2 * 2040) = 490Hz;
  } 

  for (uint8_t i = 0; i < PWMCH_NUM_NUMBER; i++)                       // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTY = 1000;                                // Set the PWM duty cycle to 1000 min throttle, 2000 max throttle
  }
  
  PWM->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; 
}

void loop() 
{
  PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 1200;              // Set the PWM duty cycle to low throttle on channel 0
  delay(1000);                                        // Wait for 1 second
  PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 1000;              // Set the PWM duty cycle to throttle off on channel 0   
  delay(1000);                                        // Wait for 1 second        
}

Note that in the loop the buffered CDTYUPD register is used in preference to the CDTY in the loop() function, this allows the waveforms to be updated at the end of the timer's cycle, thereby preventing glitches from appearing on the output whenever the duty-cycle is changed.

Just load the CDTYUPD timer registers with the microsecond value and the channel will output the corresponding pulse, for example if you load 1200, the pulse on the given channels will be 1200us in width. Range goes from 0 to 2039.

1 Like

I forgot to mention that I'm controlling ESCs. 490 is max as 100% DC is 2040 us. I'm using standard 1000-2000 ESC.

Not as straight forward as with the Mega I see, wow.. Thanks for the suggestion! I will experiment with this on my Due. For now the 2560 is installed and working, but 8 bit for the PWM is becoming very limiting in some parts of my code.

If 8-bit PWM is an issue, it's possible to use the Mega's 16-bit timers: 1, 3, 4 and 5 each with 3 channels A, B and C, to generate PWM for up to 12 motor outputs with 11-bit resolution at 490Hz:

// Output 490Hz dual slope PWM at 11-bit resolution on digital pins: 
// On the Arduino Mega there are 4 16 bit timers: 1, 3, 4 & 5, each with three outputs: A, B & C
 
// Store the addresses of the PWM timer compare (duty-cycle) registers
volatile uint16_t* pOCRxxReg[] = { &OCR1A, &OCR1B, &OCR1C, &OCR3A, &OCR3B, &OCR3C, &OCR4A, &OCR4B, &OCR4C, &OCR5A, &OCR5B, &OCR5C };

void setup() { 
  uint8_t pins[] = { 11, 12, 13, 5, 2, 3, 6, 7, 8, 46, 45, 44 };  // List the timers' Arduino pins
  for (uint8_t i = 0; i < 12; i++)                                // Iterate through each pin
  {
    pinMode(pins[i], OUTPUT);                                     // Set the pin to an output
  }
  
  // Initialise timers 1, 3, 4 and 5 for phase and frequency correct PWM
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1C1);         // Enable the PWM outputs OC1A, OC1B and OC1C on digital pins 11, 12 and 13
  TCCR1B = _BV(WGM13) | _BV(CS11);                          // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  TCCR3A = _BV(COM3A1) | _BV(COM3B1) | _BV(COM3C1);         // Enable the PWM output OC3A, OC3B and OC3C on digital pins 5, 2 and 3
  TCCR3B = _BV(WGM33) | _BV(CS31);                          // Set phase and frequency correct PWM and prescaler of 8 on timer 3
  TCCR4A = _BV(COM4A1) | _BV(COM4B1) | _BV(COM4C1);         // Enable the PWM outputs OC4A, OC4B and OC4C on digital pins 6, 7 and 8
  TCCR4B = _BV(WGM43) | _BV(CS41);                          // Set phase and frequency correct PWM and prescaler of 8 on timer 4
  TCCR5A = _BV(COM5A1) | _BV(COM5B1) | _BV(COM5C1);         // Enable the PWM outputs OC5A, OC5B and OC5C on digital pins 46, 45 and 44
  TCCR5B = _BV(WGM53) | _BV(CS51);                          // Set phase and frequency correct PWM and prescaler of 8 on timer 5
  ICR1 = 2040;                                              // Set the timer 1 frequency to 490Hz
  ICR3 = 2040;                                              // Set the timer 3 frequency to 490Hz
  ICR4 = 2040;                                              // Set the timer 4 frequency to 490Hz
  ICR5 = 2040;                                              // Set the timer 5 frequency to 490Hz
}

void loop() 
{
  for (uint8_t i = 0; i < 12; i++)                          // Iterate through the 12 motors
  {
    *(pOCRxxReg[i]) = 1200;                                 // Set the motors to low throttle
    delay(1000);                                            // Wait for 1 second
  }
  for (uint8_t i = 0; i < 12; i++)                          // Iterate through the 12 motors
  {
    *(pOCRxxReg[i]) = 1500;                                 // Set motors to half throttle
    delay(1000);                                            // Wait for 1 second
  } 
}
1 Like

Hello Martin

Didn't know I could use other than PWM pins 2-13. This is exactly what I need! Thanks a lot! 11 bit will hopefully allow for a smooth speed change even at low variations in RPM.

This is will increment the output in 1us steps, which the same resolution as most RC receivers.

You are not using the Servo library?

No, I guess it would have been easier, but I wrote my own code around analogWrite() for it.