Go Down

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

buckboostbill

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?

MartinL

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

buckboostbill

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.
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() {}


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

buckboostbill

I at least have 10bit ADC input now

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

dlloyd

Try this for 11-bit. Note that the duty cycle range is about 0-97.5%
Code: [Select]
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;
}

antodom

Hi there @buckboostbill,

Maybe you will find interesting to have a look to pwm_lib: https://github.com/antodom/pwm_lib

This library abstract the use of the eight hardware PWM channels available on Arduino DUE's Atmel ATSAM3X8E microcontroller. I think you can generate easily the 40 kHz PWM signals that you need using pwm_lib.

Have a look to the exampes that come with the library, and if in doubt just ask me.

I hope it helps.
------------
antodom

tcorkum

Try this for 11-bit. Note that the duty cycle range is about 0-97.5%
Code: [Select]
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;
}

Can I use this approach to get a frequency of 200KHz?

dlloyd

#22
Feb 22, 2017, 12:12 am Last Edit: Feb 22, 2017, 05:57 am by dlloyd
Sure. At 200kHz PWM, you'll get 420 steps for duty cycle.
Code: [Select]
REG_PWM_CPRD0 = 420;  //Set PWM frequency to 200kHz (84000000/200000 = 420)

mbahjiman

hi, i don't know whether i should start a new topic or just follow this, how can i produce 8 bursts of 40khz frequency, and does this have effects on other application, micros(), delay (), etc. Thank you

antodom

Hi there @mbahjiman,

Using pwm_lib (https://github.com/antodom/pwm_lib) you can easily generate eigth PWM independent outputs at 40 KHz. Have a look to the examples coming with the library.

In relation to micros() and delay() the library does not have any effect on them. Have a look to the README.md file coming with pwm_lib to know possible incompatibilies, the main one is with using analogWrite().
------------
antodom

mbahjiman

Thanks Antodom, I was able to generate the wave with desired frequency, but I still don't know how am I going to detect 8 outputs, is that the work of get_period()? if it is, can I use them in if conditional?

antodom

Hi there again @mbahjiman,

Have a look to example basic_test.ino which comes with pwm_lib. This example shows you how to generate two PWM signals. You just have to add the other six signals to this exampe to generate the eight signals you want. Take into account that to know which pins and signals you can use, you must utilize the enum values you can find in pwm_def.h (explained also in file README.md):

    enum class pwm_pin: uint32_t
    {
      // PWM pins listed on Table 38-2 for Atmel ATSAM3X8E
      // datasheet available in www.atmel.com
      // NOTE: only it is possible to use one pin for each
      // PWM channel
      PWMH0_PA8 , // PWM_CH0
      PWMH0_PB12, // PWM_CH0
      PWMH0_PC3 , // PWM_CH0
      PWML0_PA21, // PWM_CH0
      PWML0_PB16, // PWM_CH0
      PWML0_PC2 , // PWM_CH0
      
      PWMH1_PA19, // PWM_CH1
      PWMH1_PB13, // PWM_CH1
      PWMH1_PC5 , // PWM_CH1
      PWML1_PA12, // PWM_CH1
      PWML1_PB17, // PWM_CH1
      PWML1_PC4 , // PWM_CH1
      
      PWMH2_PA13, // PWM_CH2
      PWMH2_PB14, // PWM_CH2
      PWMH2_PC7 , // PWM_CH2
      PWML2_PA20, // PWM_CH2
      PWML2_PB18, // PWM_CH2
      PWML2_PC6 , // PWM_CH2
      
      PWMH3_PA9 , // PWM_CH3
      PWMH3_PB15, // PWM_CH3
      PWMH3_PC9 , // PWM_CH3
      PWML3_PA0 , // PWM_CH3
      PWML3_PB19, // PWM_CH3
      PWML3_PC8 , // PWM_CH3
      
      PWMH4_PC20, // PWM_CH4
      PWML4_PC21, // PWM_CH4
      
      PWMH5_PC19, // PWM_CH5
      PWML5_PC22, // PWM_CH5
      
      PWMH6_PC18, // PWM_CH6
      PWML6_PC23, // PWM_CH6
      
      PWML7_PC24  // PWM_CH7
    };

For some channels you can chose between several pins, except for channel 7. Each channel is independent, and remember that you can ONLY use ONE pin per channel. To define each pwm object you has to put as template argument the corresponding enum value for the output pin you want for each PWM signal.

I hope it helps.
------------
antodom

RustamAxm

Hi buckboostbill,

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;
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() {}


Can you explain it to me? I can use only 8 PWM pins. And can I add 2 pins, for example 2 pins D5 and DAC1?
In my code I add pins ( PIO_ABSR_P15 and PIO_ABSR_P25), but I don't understand how use 10 PWM pins, because in DUE we have only 8 PWM channels (PWM_ENA_CHIDx). 

MartinL

#28
Nov 15, 2017, 06:07 pm Last Edit: Nov 15, 2017, 06:13 pm by MartinL
Hi RustamAxm,

Each pin on the Arduino Due's SAM3X8E microcontroller can either be General Purpose Input Output (GPIO), or alternatively can be switched to one of two peripherals: either A or B. The peripherals being analog IO, timer IO, PWM, Serial, CAN, etc...

The different peripherals are allocated to a number of specified pins, defined in the PIO Controller Multiplexing tables in the "Peripherals" section of the SAM3X8E datasheet. There are four tables one for each of the microcontroller's ports: A, B, C and D.

Like you say, the PWM Controller peripheral has 8 channels. Each channel has both low and high side (non-inverting and inverting) outputs labelled: PWMLx and PWMHx, (where x is the channel number), that are listed in the table.

To get the PWM controller going it's first necessary to enable it (REG_PMC_PCER1), specify the peripheral either A or B (REG_PIOx_ABSR) for each pin, (in this case ports B and C and the low side PWMLx pins are selected) and finally set the corresponding pins as outputs (REG_PIOx_PDR).

To use the code it's easiest to lookup your required outputs in the PIO Controller Multiplexing tables and comment out or add channel outputs you require.

As the PWM controller has only 8 channels, further PWM outputs would require you to employ the TC timers. They're also PWM capable, but less fully featured.

Go Up