Go Down

Topic: Due PWM frequency (Read 16286 times) previous topic - next topic

buckboostbill

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

MartinL

#1
Feb 04, 2017, 06:23 pm Last Edit: Feb 04, 2017, 06:23 pm by MartinL
Hi buckboostbill,

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

Code: [Select]
// 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.

buckboostbill

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

buckboostbill

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??

MartinL

#4
Feb 04, 2017, 07:56 pm Last Edit: Feb 04, 2017, 08:07 pm by MartinL
Hi buckboostbill,

Quote
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:

Code: [Select]
REG_PWM_CDTYUPD0 = 1050;
Quote
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:

Code: [Select]
// 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() {}

buckboostbill

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.

buckboostbill

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

buckboostbill

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?

buckboostbill

#8
Feb 06, 2017, 07:08 am Last Edit: Feb 06, 2017, 07:14 am by buckboostbill
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
}

MartinL

#9
Feb 06, 2017, 12:57 pm Last Edit: Feb 06, 2017, 07:38 pm by MartinL
Quote
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:

Code: [Select]
REG_PWM_CMR0 = PWM_CMR_CPRE_MCK_DIV_2;
...or alternatively:

Code: [Select]
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:

Code: [Select]
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:

Code: [Select]
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.


buckboostbill

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..

MartinL

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

buckboostbill

Code: [Select]
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?

MartinL

#13
Feb 07, 2017, 10:15 am Last Edit: Feb 07, 2017, 10:27 am by MartinL
Quote
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:

Code: [Select]
REG_PMC_PCER1 |= PMC_PCER1_PID36;
2. 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):

Code: [Select]
REG_PIOB_ABSR |= PIO_ABSR_P16;
3. 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):

Code: [Select]
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.

buckboostbill


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


Code: [Select]
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

}

Go Up