Due PWM frequency

I have a buck converter that needs 40kHz pwm. I have read almost every forum and article on the subject and I am still stuck. Everyone seems to have a different recipe with certain drawbacks or wants to push the limits just to see how fast of a fixed duty square wave they can generate.

This is a Due so I want to be able to retain the 10 bit pwm resolution or even use the 12 bit.

Most of the posts or articles are old and was wondering if there was a reasonable more accepted way of doing this?

Thanks

Hi buckboostbill,

The code below uses register manipulation to create a 40kHz, 50% duty cycle PWM signal on pin DAC1 (PWML0):

// Output a 40kHz PWM waveform at a resolution of 11-bits on pin DAC1 (PWML0)
void setup() {
  // PWM Set-up on pin: DAC1
  REG_PMC_PCER1 |= PMC_PCER1_PID36;                     // Enable PWM 
  REG_PIOB_ABSR |= PIO_ABSR_P16;                        // Set PWM pin perhipheral type A or B, in this case B
  REG_PIOB_PDR |= PIO_PDR_P16;                          // Set PWM pin to an output
  REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);      // Set the PWM clock rate to 84MHz (84MHz/1) 
  REG_PWM_CMR0 = PWM_CMR_CPRE_CLKA;                     // Enable single slope PWM and set the clock source as CLKA
  REG_PWM_CPRD0 = 2100;                                  // Set the PWM frequency 84MHz/40kHz = 2100 
  REG_PWM_CDTY0 = 1050;                                  // Set the PWM duty cycle 50% (2100/2=1050)
  REG_PWM_ENA = PWM_ENA_CHID0;                          // Enable the PWM channel     
}

void loop() {}

This will give you 11-bit resolution, which is the best that can be achieved by the 84MHz Due at this (40kHz) frequency.

Thanks but a buck converter needs to be controlled with an analog input with variable duty cycle

It also needs to be on the pwm channels. I need several outputs. If it is really this hard to do this I guess I could settle with one of the standard values and design my magnetics around that. I believe 32kHz is an option??

Hi buckboostbill,

Thanks but a buck converter needs to be controlled with an analog input with variable duty cycle.

What do you mean by analog input, if you require the Due to produce a PWM output?

The duty cycle can be changed by loading the buffered duty cycle update register REG_PWM_CDTYUPD0 with a value between 0 and 2100, (0 to 100% duty cycle). Using the buffered register outputs the signal on the next timer cycle, preventing changes from causing glitches on your outputs:

REG_PWM_CDTYUPD0 = 1050;

It also needs to be on the pwm channels. I need several outputs.

This is one of the PWM channels, channel 0. It might be on the DAC1 pin, but the pins on the SAM3X8E microcontroller have multiple functions.

The PWM controller used on the Due has 8 channels. The following code outputs a 40kHz signal on all 8 channels. I've commented out the code that'll allow you to also synchronize these channels:

// Enable single slope, 11-bit resolution PWM at 40kHz 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(1);                                // Set the PWM clock A rate to 84MHz (84MHz/1)
  //REG_PWM_SCM |= PWM_SCM_SYNC7 | PWM_SCM_SYNC6 | PWM_SCM_SYNC5 | PWM_SCM_SYNC4 |  // Set the PWM channels as synchronous
  //               PWM_SCM_SYNC3 | PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;  
  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_CPRE_CLKA;                  // Enable single slope PWM and set the clock source as CLKA
    PWM->PWM_CH_NUM[i].PWM_CPRD = 2100;                               // Set the PWM period register 84MHz/(40kHz)=2100;
  } 
  //REG_PWM_ENA = PWM_ENA_CHID0;           // Enable the PWM channels, (only need to set channel 0 for synchronous mode)
  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 = 1050;                            // Set the PWM duty cycle to 50% (2100/2=1050) on all channels
  } 
  //REG_PWM_SCUC = PWM_SCUC_UPDULOCK;      // Set the update unlock bit to trigger an update at the end of the next PWM period
}

void loop() {}

Thanks appreciate it.. I will try this right now. You can basically just pretend I'm controlling duty cycle from 0 to 100 with a pot using analog input A0,3.3, and gnd.

A buck converter will have a voltage divider from its output creating a 0-3.3 voltage range. When there are load variations the pulses needed to rapidly change duty cycle in order to maintain a constant output.

const int analogInPin = A0; // Analog input pin that the potentiometer is attached to
const int analogOutPin = 9; // Analog output pin that the LED is attached to

int sensorValue = 0; // value read from the pot
int outputValue = 0; // value output to the PWM (analog out)

void setup() {

}

void loop() {
// read the analog in value:
sensorValue = analogRead(analogInPin);
// map it to the range of the analog out:
outputValue = map(sensorValue, 0, 1023, 0, 255);
// change the analog out value:
analogWrite(analogOutPin, outputValue);

}

This is what I am using now and it works beautifully at 1kHz..just want to change the frequency

MartinL that did work and i have a perfect 40kHz output according to my scope. Now how do I control the duty cycle with a pot?

This works perfectly on my uno. I know this is a different timer. Any way to use simple prescaler change like I did in this example for an individual channel?

Thanks

int PWMpin = 9; // PWM connected to digital pin 9
int Feedback = 0; // feedback connected to pin 0
int val = 0; // variable to store the read value

void setup()

{
TCCR1B = TCCR1B & B11111000 | B00000001;
pinMode(PWMpin, OUTPUT); // sets the pin as output
}

void loop()

{
val = analogRead(Feedback); // read the input pin
analogWrite(PWMpin, val / 4); // analogRead values go from 0 to 1023, analogWrite values from 0 to 255
}

Buckconverterowncode.ino (462 Bytes)

Any way to use simple prescaler change like I did in this example for an individual channel?

Yes, on the Due it's possible to change the prescaler for each timer channel using the PWM Channel Mode Register (REG_PWM_CMRx).

This divides down the Due's master clock running at 84MHz:

To divide down by 2 on channel 0:

REG_PWM_CMR0 = PWM_CMR_CPRE_MCK_DIV_2;

...or alternatively:

PWM->PWM_CH_NUM[i].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;

... does the same thing, but is useful if you're iterating throught a number of channels.

It's possible to divide down by 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, just by changing the divisor.

If you require more refinement to your clock frequency, it's possible instead to divide down the master clock (84MHz) using one of two clock sources: CLKA or CLKB, like I did with CLKA in the example above:

REG_PWM_CMR0 = PWM_CMR_CPRE_CLKA;

You can then optionally set a prescaler and divisor (or both) for either CLKA or CLKB in the PWM Clock Register (REG_PWM_CLK), for example:

REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42);    // Set the PWM clock prescaler and divisor (84MHz/42)

If DIVA or DIVB are 0 CLKA and CLKB are turned off respectively.
If DIVA or DIVB are 1 the respective clocks are prescaled by PREA or PREB.
If DIVA or DIVB are between 2-255 the CLKA and CLKB are divided by PREA or PREB then divided by DIVA or DIVB.

The prescalers PREA and PREB are 4-bit values that range from 0 to 15 and correspond to a prescaler of 1 through to 1024. For example PWM_CLK_PREA(5) will divide the master clock (84MHz) by 32.

Thank you! Exactly what I wanted.

I've been referring to the pwm p. 970 in the atmel manual and understand the function. I just don't understand how to turn it into code. Where is the file that we are manipulating? If I could see what is in there by default I feel like I could figure things out..

The PWM register definitions for the Due are stored in two Atmel files.

One file resides in the "instance" directory and includes the definitions for the peripherals registers themselves. The other can be found in the neighbouring "component" directory and details the registers' individual bit/bitfield structures together with their associated definitions.

These can currently be found, (at least on my Windows machine) at:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\sam3xa\include\instance\pwm.h

and

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\sam3xa\include\component\pwm.h

int PWMpin = 9;      // PWM connected to digital pin 9
int Feedback = A0;   // feedback connected to pin 0
int val = 0;         // variable to store the read value



void setup()
{

  REG_PWM_CMR0 = PWM_CMR_CPRE_MCK_DIV_2;
  
  pinMode(PWMpin, OUTPUT);   // sets the pin as output
  
}

void loop()

{

  val = analogRead(Feedback);   // read the input pin
  analogWrite(PWMpin, val / 4);  // analogRead values go from 0 to 1023, analogWrite values from 0 to 255

}

So I just inserted the code as is and messed around with both my PWMpin (PWM2-13) and atmel CMR0-CMR7 and the frequency remained 1kHz.

What arduino PWM pin is REG_PWM_CMR0?

What arduino PWM pin is REG_PWM_CMR0?

The REG_PWM_CMR0 register is just the Channel Mode Register for channel 0. However, the designation what channel is assigned to what pin is determined by the PIO Controller Multiplexing tables in the SAM3X8E's datasheet. In the old datasheet it was in section 11 and in the new one it's in section 9: "Peripheral Signal Multiplexing on I/O lines".

There are 4 PIO Controller Multiplexing tables, one for each port: A, B, C and D. Furthermore, each pin can be specified as either GPIO, peripheral A, peripheral B or an extra function, for example analog IO. Channel 0 of the PWM controller can be found on peripherals pins PWML0 or its inverse PWMH0. Channel 1 on PWML1 and so on... To enable the PWM channel 0 as an output, it's necessary to:

  1. First enable the peripheral's clock using the Peripheral Clock Enable Register (PCER), in the case of the PWM controller, peripheral ID 36:
REG_PMC_PCER1 |= PMC_PCER1_PID36;
  1. Set the given port pin to peripheral A or peripheral B using the Peripheral AB Select Register (ABSR), (the register's detailed in the Parallel IO Controller (GPIO) section of the datasheet):
REG_PIOB_ABSR |= PIO_ABSR_P16;
  1. Finally disable the GPIO for the given port pin using the Port Disable Register (PDR), (again this register's detailed in the Parallel IO Controller section):
REG_PIOB_PDR |= PIO_PDR_P16;

In my example I've chosen the DAC1 pin, but the PWML0 output is also assigned to peripheral B on pin D34, aka Port C, pin2 or PC2.

I guess you can't teach a man to fish.. just feed me I'm just not getting it. I still can't get this to work. OAgain I'm going to use 5 of these outputs, 4 fixed duty cycle for an H-bridge power converter and the main one I'm concerned with now which is an earlier buck stage in which I need to control the pulse widths with "a pot".

I was hoping to still use the arduino language like I did with the uno. I would prefer using the pwm pins. This is what I tried. Can you alter this to work?

int PWMpin = DAC1; // PWM connected to digital dac1
int Feedback = A0; // feedback connected to pin 0
int val = 0; // variable to store the read value

int PWMpin = DAC1;      // PWM connected to digital dac1
int Feedback = A0;   // feedback connected to pin 0
int val = 0;         // variable to store the read value



void setup()
{
  REG_PMC_PCER1 |= PMC_PCER1_PID36;
  REG_PIOB_ABSR |= PIO_ABSR_P16;
  REG_PIOB_PDR |= PIO_PDR_P16;
  REG_PWM_CMR0 = PWM_CMR_CPRE_MCK_DIV_2;
  pinMode(PWMpin, OUTPUT);   // sets the pin as output
  
}

void loop()

{

  val = analogRead(Feedback);   // read the input pin
  analogWrite(PWMpin, val / 4);  // analogRead values go from 0 to 1023, analogWrite values from 0 to 255

}

This may help you understand where I am coming from..

I don't get why you would have to start from the ground up like enabling pwm. Isn't that done when you select an output with analogWrite?

I don't get why you would have to start from the ground up like enabling pwm. Isn't that done when you select an output with analogWrite?

The issue with analogWrite() is that it only gives you a fixed PWM frequency and an 8-bit resolution. It doesn't really harness the full potential of the SAM3X8E's PWM controller.

Understood. I will need to change my approach. I will code with the atmel language.

After your last few posts I now fully understand this. Works great.

// Output a 40kHz PWM waveform at a resolution of 11-bits on pin DAC1 (PWML0)
void setup() {
  // PWM Set-up on pin: DAC1
  REG_PMC_PCER1 |= PMC_PCER1_PID36;                     // Enable PWM 
  REG_PIOB_ABSR |= PIO_ABSR_P16;                        // Set PWM pin perhipheral type A or B, in this case B
  REG_PIOB_PDR |= PIO_PDR_P16;                          // Set PWM pin to an output
  REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);      // Set the PWM clock rate to 84MHz (84MHz/1) 
  REG_PWM_CMR0 = PWM_CMR_CPRE_CLKA;                     // Enable single slope PWM and set the clock source as CLKA
  REG_PWM_CPRD0 = 2100;                                  // Set the PWM frequency 84MHz/40kHz = 2100 
  REG_PWM_CDTY0 = 1050;                                  // Set the PWM duty cycle 50% (2100/2=1050)
  REG_PWM_ENA = PWM_ENA_CHID0;                          // Enable the PWM channel     
}

void loop() {}

0-2100 resolution is much better suited to my project.

I now need to figure out how to make REG_PWM_CDTY0 = the value from an ADC input with 0-2100 resolution instead of a fixed 1050..

I at least have 10bit ADC input now

int Feedback = A0;   // feedback connected to pin 0
int val = 0;         // variable to store the read value

void setup() {
  // PWM Set-up on pin: DAC1
   
  REG_PMC_PCER1 |= PMC_PCER1_PID36;                     // Enable PWM 
  REG_PIOB_ABSR |= PIO_ABSR_P16;                        // Set PWM pin perhipheral type A or B, in this case B
  REG_PIOB_PDR |= PIO_PDR_P16;                          // Set PWM pin to an output
  REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);      // Set the PWM clock rate to 84MHz (84MHz/1) 
  REG_PWM_CMR0 = PWM_CMR_CPRE_CLKA;                     // Enable single slope PWM and set the clock source as CLKA
  REG_PWM_CPRD0 = 2100;                                  // Set the PWM frequency 84MHz/40kHz = 2100                                   // Set the PWM duty cycle 50% (2100/2=1050)
  REG_PWM_ENA = PWM_ENA_CHID0;                          // Enable the PWM channel     
}

void loop()
{
val = analogRead(Feedback);
REG_PWM_CDTY0 = val * 2;
}

Try this for 11-bit. Note that the duty cycle range is about 0-97.5%

int Feedback = A0;   // feedback connected to pin 0
int val = 0;         // variable to store the read value

void setup() {
  // PWM Set-up on pin: DAC1

  REG_PMC_PCER1 |= PMC_PCER1_PID36;                     // Enable PWM
  REG_PIOB_ABSR |= PIO_ABSR_P16;                        // Set PWM pin perhipheral type A or B, in this case B
  REG_PIOB_PDR |= PIO_PDR_P16;                          // Set PWM pin to an output
  REG_PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);      // Set the PWM clock rate to 84MHz (84MHz/1)
  REG_PWM_CMR0 = PWM_CMR_CPRE_CLKA;                     // Enable single slope PWM and set the clock source as CLKA
  REG_PWM_CPRD0 = 2100;                                 // Set the PWM frequency 84MHz/40kHz = 2100                                   // Set the PWM duty cycle 50% (2100/2=1050)
  REG_PWM_ENA = PWM_ENA_CHID0;                          // Enable the PWM channel

  analogReadResolution(11);                             // 11-bit, 0-2048 
}

void loop()
{
  val = analogRead(Feedback);
  REG_PWM_CDTY0 = val;
}