PWM on DUE using registers

Hello!

Let me first show you some code for the ATmega328, so you can see what I'm trying to accomplish:
The following code enables 16 bit PWM on timer 1 and then sets the compare registers such that it runs over all availale duty cycles.

void setup() { //some register magic to enable 16 bit PWM on timer 1
    DDRB |= _BV(PB1) | _BV(PB2);
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);
    ICR1 = 0xffff;
}

void loop() { // loop over all possible duty cycles and set the compare registers accordingly
  for(int i = 0; i<65535;i++)
  {
      OCR1A = i;
      OCR1B = 65535-i;
      delay(50);
  }
}

This code simply tells Timer 1 to count 16 bits and sets the compare registers so that the pwm runs up on one pin and down on the other.

Now, this is running just fine on my nano with its 16MHz clock. With 2^16=65536 steps this results in 244Hz base frequency.

The DUE(84MHz) should be able to do 1.2kHz with the same resolution, right? Configuring the output is a lot harder though. I tried all sorts of different things, but it just does not seem to work.

I already tried and changed some code available here and here; however as soon as I make any changes the whole thing does not work anymore :frowning: Also, my oscilloscope can only resolve ~10MHz, the high frequencies from kerry wongs page are not even visible.

It would be very nice to achieve some easy way to set the duty cycle of the fastest possible 16 bit PWM! It would be great if that would work on 5 pins independently.

So, after some experimenting I was able to achieve PWM with arbitrary resolution (maximum 2^15 Bit) and very high frequency. However once configured, the PWM parameters, especially the duty cycle cannot be changed.

Heres the code (adapted from kerry wongs page):

uint32_t maxDutyCount = 32768; //15 Bit
uint32_t clkAFreq = 42000000ul;
uint32_t pwmFreq = 42000000ul; 
 
void setupPWM(uint32_t duty,uint32_t  pwmPin) { //duty: the duty cycle in tics, at 15 bit 50% duty would be 16384; also, duty==0 means 100% duty cycle and duty==maxDutyCount means 0%
  pmc_enable_periph_clk(PWM_INTERFACE_ID);
  PWMC_ConfigureClocks(clkAFreq, 0, VARIANT_MCK);
 
  PIO_Configure(
    g_APinDescription[pwmPin].pPort,
    g_APinDescription[pwmPin].ulPinType,
    g_APinDescription[pwmPin].ulPin,
    g_APinDescription[pwmPin].ulPinConfiguration);
 
  uint32_t channel = g_APinDescription[pwmPin].ulPWMChannel;
  PWMC_ConfigureChannel(PWM_INTERFACE, channel , pwmFreq, 0, 0);
  PWMC_SetPeriod(PWM_INTERFACE, channel, maxDutyCount);
  PWMC_EnableChannel(PWM_INTERFACE, channel);
  PWMC_SetDutyCycle(PWM_INTERFACE, channel, duty);
 
  pmc_mck_set_prescaler(2);
}
 
void setup()
{
  setupPWM(8,12345); //enable pwm on pin 8 with corresponding duty cycle
}

void loop() 
{
}

I cannot wrap my head around the whole PWM on DUE yet, I don't understand how he was able to create a 126MHz square wave with a 84MHz core clock. I experimented with the prescalar, but had no success at all. This might also be the reason why the frequency is higher than it should be. At 12 Bit resolution the base frequency was 40kHz and not 20kHz as expected (84 000 000/4096=20.5).

Also the code above is not perfect yet. The setupPWM() function can be called once and the pwm will be running without any further processing. A second call to setupPWM() will not change the pwm signal. Maybe someone has an idea?

The Due's PWM controller has 8 independent channels.

The following example outputs 50Hz at 14-bit resolution on channel 0, pin DAC1 (PB16):

// Output 50Hz PWM at a resolution of 14 bits on pin DAC1 (pin: PB16, peripheral: 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(42);     // Set the PWM clock rate to 2MHz (84MHz/42) 
  REG_PWM_CMR0 = PWM_CMR_CALG | PWM_CMR_CPRE_CLKA;      // Enable dual slope PWM and set the clock source as CLKA
  REG_PWM_CPRD0 = 20000;                                // Set the PWM frequency 2MHz/(2 * 20000) = 50Hz 
  REG_PWM_CDTY0 = 1500;                                 // Set the PWM duty cycle to 1500 - centre the servo
  REG_PWM_ENA = PWM_ENA_CHID0;                          // Enable the PWM channel     
}

void loop() {}

The PWM channels can be multiplexed on to a number of different pins on the Due. Which PWM pins are available is specified by the mutliplexing tables in the SAM3X8E's datasheet. The SAM3X8E has four tables for each of its 32-bit ports A, B, C and D. Most pins having two peripheral functions: "Peripheral A" and "Peripheral B". It's possible for each PWM channel to control two complementary (opposite of each other) square wave outputs, labelled in the table as PWMLx (low) and PWMHx (high).

Going through the code:

It's first necessary to enable the PWM controller and multiplex the PWM controller's output to DAC1, which is pin PB16 (port B), using the REG_PMC_PCER1, REG_PIOB_ABSR and REG_PIOB_PDR registers.

The PWM controller's clock register (REG_PWM_CLK) allows you to either divide down the master clock (84MHz), either by using a prescaler, or clock divisor, or both. There are two clock divisor and prescaler registers: A and B, so you can generate up to two base frequencies for your 8 channels.

The channel mode register (REG_PWM_CMR0) connects the divided clock (2MHz) to channel 0 and selects the PWM mode of operation, in this case dual slope PWM, (center aligned).

The REG_PWM_CPRD0 and REG_PWM_CDTY0 determine the period (frequency) and duty cycle (phase) respectively for a given channel. It's also necessary to enable channel 0 with the REG_PWM_ENA register.

By the way, if you plan on changing the period or duty cycle during PWM operation, you'll need to use the REG_PWM_CPRDUPDx and REG_PWM_CDTYUPDx update registers for the given channel.

The equation for calculating the PWM frequency is also contained in the SAM3X8E's datasheet. For dual slope PWM:

PWM Frequency = MCLK / (2 * CPRD * DIVA)

PWM Frequency = 84MHz / (2 * 20000 * 42) = 50Hz

1 Like

Hi there Obba,

You may have a look to pwm_lib for doing what you want. I have mentioned it in other conversation:

http://forum.arduino.cc/index.php?topic=131323.msg2595654#msg2595654

Using this library the maximum frequency you can get is the one provided by the hardware. I see no problems even to get to 1 Mhz but in this case the resolution of the duty will be limited to 84 clock ticks, at 2 Mhz 42 ticks, etc. I have pwm_lib available at:

I hope it helps.

No me compila el basic_test de tu libreria pwm_lib, me sale esto:

me sale esto:

basic_test:93: error: variable or field 'change_duty' declared void
void change_duty(pwm_type& pwm_obj,uint32_t pwm_duty,uint32_t pwm_period)
^
basic_test:93: error: 'pwm_type' was not declared in this scope
basic_test:93: error: 'pwm_obj' was not declared in this scope
void change_duty(pwm_type& pwm_obj,uint32_t pwm_duty,uint32_t pwm_period)
^
basic_test:93: error: expected primary-expression before 'pwm_duty'
void change_duty(pwm_type& pwm_obj,uint32_t pwm_duty,uint32_t pwm_period)
^
basic_test:93: error: expected primary-expression before 'pwm_period'
void change_duty(pwm_type& pwm_obj,uint32_t pwm_duty,uint32_t pwm_period)
^
exit status 1
variable or field 'change_duty' declared void
/Users/brunomarti/Documents/Arduino/libraries/pwm_lib/examples/basic_test/basic_test.ino

que puede ser?

Hi there @ibruno,

Sorry I will answer in english, in spanish you can reach me with a personal message, if you prefer.

I am afraid that I need more information to be of any help.

At first sight, I suspect that the problem is the -std=gnu++11 flag. In any case, to be sure, attach the output of the whole compilation process to know what is happening.

Best.

A while back, while learning Arduino on the Due, I tested the complimentary PWM (PWMC) using 4 channels (8 signals). It worked great ... tested at 1.281 kHz PWM frequency with dead-time control and full 16-bit duty cycle control.

 //declare variables
 unsigned short duty0 = 15000;
 unsigned short duty1 = 30000;
 unsigned short duty2 = 45000;
 unsigned short duty3 = 60000;

void setup() {
  pwmc_setup();
}

void loop() {
 pwmc_duty(duty0, duty1, duty2, duty3); //set pwm duty cycle
}

void pwmc_setup()
{
  //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
  REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
  REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register

  REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
  REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

  REG_PWM_CMR0 = 0x10000; //Channe0 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR1 = 0x10000; //Channe1 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR2 = 0x10000; //Channe2 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR3 = 0x10000; //Channe3 Mode Register: Dead Time Enable DTE=1

  REG_PWM_DT0 = 0xA800A8; //Channe0 Dead Time Register (168=2us for outputs PWML0,PWMH0)
  REG_PWM_DT1 = 0xA800A8; //Channe1 Dead Time Register (168=2us for outputs PWML1,PWMH1)
  REG_PWM_DT2 = 0xA800A8; //Channe2 Dead Time Register (168=2us for outputs PWML2,PWMH2)
  REG_PWM_DT3 = 0xA800A8; //Channe3 Dead Time Register (168=2us for outputs PWML3,PWMH3)

  REG_PWM_CPRD0 = 65535; //Channe0 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD1 = 65535; //Channe1 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD2 = 65535; //Channe2 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD3 = 65535; //Channe3 Period Register (84mhz/65535=1.281khz=780.64us period)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
  REG_PWM_CDTY0 = duty0;
  REG_PWM_CDTY1 = duty1;
  REG_PWM_CDTY2 = duty2;
  REG_PWM_CDTY3 = duty3;
}

dlloyd:
A while back, while learning Arduino on the Due, I tested the complimentary PWM (PWMC) using 4 channels (8 signals). It worked great ... tested at 1.281 kHz PWM frequency with dead-time control and full 16-bit duty cycle control.

 //declare variables

unsigned short duty0 = 15000;
unsigned short duty1 = 30000;
unsigned short duty2 = 45000;
unsigned short duty3 = 60000;

void setup() {
  pwmc_setup();
}

void loop() {
pwmc_duty(duty0, duty1, duty2, duty3); //set pwm duty cycle
}

void pwmc_setup()
{
  //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
  REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
  REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register

REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
  REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

REG_PWM_CMR0 = 0x10000; //Channe0 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR1 = 0x10000; //Channe1 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR2 = 0x10000; //Channe2 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR3 = 0x10000; //Channe3 Mode Register: Dead Time Enable DTE=1

REG_PWM_DT0 = 0xA800A8; //Channe0 Dead Time Register (168=2us for outputs PWML0,PWMH0)
  REG_PWM_DT1 = 0xA800A8; //Channe1 Dead Time Register (168=2us for outputs PWML1,PWMH1)
  REG_PWM_DT2 = 0xA800A8; //Channe2 Dead Time Register (168=2us for outputs PWML2,PWMH2)
  REG_PWM_DT3 = 0xA800A8; //Channe3 Dead Time Register (168=2us for outputs PWML3,PWMH3)

REG_PWM_CPRD0 = 65535; //Channe0 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD1 = 65535; //Channe1 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD2 = 65535; //Channe2 Period Register (84mhz/65535=1.281khz=780.64us period)
  REG_PWM_CPRD3 = 65535; //Channe3 Period Register (84mhz/65535=1.281khz=780.64us period)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
  REG_PWM_CDTY0 = duty0;
  REG_PWM_CDTY1 = duty1;
  REG_PWM_CDTY2 = duty2;
  REG_PWM_CDTY3 = duty3;
}

Nice code :slight_smile:

I want to use this for a signal 1 Mhz..is this possible ?

is set REG_PWM_CPRD0 = 65535; to REG_PWM_CPRD0 = 83; but it doesn't work :confused:

I want to use this for a signal 1 Mhz..is this possible ?

Yes. The original dead time value was "killing it"

Unfortunately, programming the registers directly isn't very intuitive and requires referring to a 1,459 page "datasheet"

Updated for 1MHz complimentary PWM (PWMC):

// for 1MHz PWM, range of duty is 0 to 84
unsigned short duty0 = 21;  // pin 34 and inverted on pin 35
unsigned short duty1 = 42;  // pin 36 and inverted on pin 37
unsigned short duty2 = 63;  // pin 38 and inverted on pin 39
unsigned short duty3 = 77;  // pin 40 and inverted on pin 41

void setup() {
  pwmc_setup();
}

void loop() {
  pwmc_duty(duty0, duty1, duty2, duty3);
}

void pwmc_setup()
{
  //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
  REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
  REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
  REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
  REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

  // Use 0x100000 to enable dead time
  REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR2 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR3 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1

  // available range is same as duty and depends on PWM frequency (see datasheet)
  REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
  REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
  REG_PWM_DT2 = 0x000000; //Channe2 Dead Time Register for pins 38,39 (PWML2,PWMH2)
  REG_PWM_DT3 = 0x000000; //Channe3 Dead Time Register for pins 40,41 (PWML3,PWMH3)

  REG_PWM_CPRD0 = 84; //Channe0 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD1 = 84; //Channe1 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD2 = 84; //Channe2 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD3 = 84; //Channe3 Period Register (84mhz/84=1MHz)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
  REG_PWM_CDTY0 = duty0;
  REG_PWM_CDTY1 = duty1;
  REG_PWM_CDTY2 = duty2;
  REG_PWM_CDTY3 = duty3;
}

dlloyd:

// for 1MHz PWM, range of duty is 0 to 84

unsigned short duty0 = 21;  // pin 34 and inverted on pin 35
unsigned short duty1 = 42;  // pin 36 and inverted on pin 37
unsigned short duty2 = 63;  // pin 38 and inverted on pin 39
unsigned short duty3 = 77;  // pin 40 and inverted on pin 41

void setup() {
 pwmc_setup();
}

void loop() {
 pwmc_duty(duty0, duty1, duty2, duty3);
}

void pwmc_setup()
{
 //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
 REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
 REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
 REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
 REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

// Use 0x100000 to enable dead time
 REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR2 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR3 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1

// available range is same as duty and depends on PWM frequency (see datasheet)
 REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
 REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
 REG_PWM_DT2 = 0x000000; //Channe2 Dead Time Register for pins 38,39 (PWML2,PWMH2)
 REG_PWM_DT3 = 0x000000; //Channe3 Dead Time Register for pins 40,41 (PWML3,PWMH3)

REG_PWM_CPRD0 = 84; //Channe0 Period Register (84mhz/84=1MHz)
 REG_PWM_CPRD1 = 84; //Channe1 Period Register (84mhz/84=1MHz)
 REG_PWM_CPRD2 = 84; //Channe2 Period Register (84mhz/84=1MHz)
 REG_PWM_CPRD3 = 84; //Channe3 Period Register (84mhz/84=1MHz)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
 REG_PWM_CDTY0 = duty0;
 REG_PWM_CDTY1 = duty1;
 REG_PWM_CDTY2 = duty2;
 REG_PWM_CDTY3 = duty3;
}

Hi dlloyd,

thanks a lot for this code, it is very helpful. It seems audio out quality on Due could be better with this kind of fast PWM than with noisy DACs.

I've tried to understand this code by searching words like "PIOC, PIO, ABSR..." in datasheet but it is very difficult for beginners like me. In fact I do not understand your pin assignment method.

So I wonder how to assign this 1 Mhz PWM to others Pins: 12 and 13 for example, I'm not sure it is possible for all pins.

I only need 2 PWM out but do not understand how I can configure 2 pins only and not 8 ( I need a lot of free pins to connect buttons).

Thanks for your help.

I would suggest starting a new thread with some detailed information on what you're trying to accomplish.

Unfortunately, working with the registers directly isn't very user friendly, but sometimes its needed to take advantage of the hardware. One feature that the Due's PWM has that could be an advantage for the OP's application or yours is synchronous channel mode (haven't tried it).

thanks for this code, i am beginner on arduino due

how i can use PWM-signal for i2C on arduino due . what should i do ?

dlloyd:
Yes. The original dead time value was "killing it"

Unfortunately, programming the registers directly isn't very intuitive and requires referring to a 1,459 page "datasheet"

Updated for 1MHz complimentary PWM (PWMC):

// for 1MHz PWM, range of duty is 0 to 84

unsigned short duty0 = 21;  // pin 34 and inverted on pin 35
unsigned short duty1 = 42;  // pin 36 and inverted on pin 37
unsigned short duty2 = 63;  // pin 38 and inverted on pin 39
unsigned short duty3 = 77;  // pin 40 and inverted on pin 41

void setup() {
  pwmc_setup();
}

void loop() {
  pwmc_duty(duty0, duty1, duty2, duty3);
}

void pwmc_setup()
{
  //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
  REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
  REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
  REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
  REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

// Use 0x100000 to enable dead time
  REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR2 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
  REG_PWM_CMR3 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1

// available range is same as duty and depends on PWM frequency (see datasheet)
  REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
  REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
  REG_PWM_DT2 = 0x000000; //Channe2 Dead Time Register for pins 38,39 (PWML2,PWMH2)
  REG_PWM_DT3 = 0x000000; //Channe3 Dead Time Register for pins 40,41 (PWML3,PWMH3)

REG_PWM_CPRD0 = 84; //Channe0 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD1 = 84; //Channe1 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD2 = 84; //Channe2 Period Register (84mhz/84=1MHz)
  REG_PWM_CPRD3 = 84; //Channe3 Period Register (84mhz/84=1MHz)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
  REG_PWM_CDTY0 = duty0;
  REG_PWM_CDTY1 = duty1;
  REG_PWM_CDTY2 = duty2;
  REG_PWM_CDTY3 = duty3;
}

My use of PWM is to control a bi-color (red,green) TWO lead LED. Polarity reversal is needed as using PWM referenced to ground only triggers one to the two chips. The complementary output solves the problem without having to resort to an external signal inverter. Varying the PWM shifts the color from pure red through 'yellow' to pure green.
According to the datasheet, there are a total of eight PWM channels. There are 8 available outputs and 6 available complementary outputs. (PWMH4 is not connected on the DUE circuit board and PWMH7 is not implemented in the SAM3X8E chip.). The demo program runs all eight PWM channels full bore. All the outputs were checked out on the scope. I thoroughly commented the demo as thumbing throught the datasheet's ~80 page subsection on PWM gets old real quick.

PWM2B.ino (9.83 KB)

dlloyd:
A while back, while learning Arduino on the Due, I tested the complimentary PWM (PWMC) using 4 channels (8 signals). It worked great ... tested at 1.281 kHz PWM frequency with dead-time control and full 16-bit duty cycle control.

 //declare variables

unsigned short duty0 = 15000;
unsigned short duty1 = 30000;
unsigned short duty2 = 45000;
unsigned short duty3 = 60000;

void setup() {
 pwmc_setup();
}

void loop() {
pwmc_duty(duty0, duty1, duty2, duty3); //set pwm duty cycle
}

void pwmc_setup()
{
 //Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
 REG_PIOC_PDR = 0x3FC;  //B1111111100, PIO Disable Register
 REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register

REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
 REG_PWM_ENA = REG_PWM_SR | B1111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3)

REG_PWM_CMR0 = 0x10000; //Channe0 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR1 = 0x10000; //Channe1 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR2 = 0x10000; //Channe2 Mode Register: Dead Time Enable DTE=1
 REG_PWM_CMR3 = 0x10000; //Channe3 Mode Register: Dead Time Enable DTE=1

REG_PWM_DT0 = 0xA800A8; //Channe0 Dead Time Register (168=2us for outputs PWML0,PWMH0)
 REG_PWM_DT1 = 0xA800A8; //Channe1 Dead Time Register (168=2us for outputs PWML1,PWMH1)
 REG_PWM_DT2 = 0xA800A8; //Channe2 Dead Time Register (168=2us for outputs PWML2,PWMH2)
 REG_PWM_DT3 = 0xA800A8; //Channe3 Dead Time Register (168=2us for outputs PWML3,PWMH3)

REG_PWM_CPRD0 = 65535; //Channe0 Period Register (84mhz/65535=1.281khz=780.64us period)
 REG_PWM_CPRD1 = 65535; //Channe1 Period Register (84mhz/65535=1.281khz=780.64us period)
 REG_PWM_CPRD2 = 65535; //Channe2 Period Register (84mhz/65535=1.281khz=780.64us period)
 REG_PWM_CPRD3 = 65535; //Channe3 Period Register (84mhz/65535=1.281khz=780.64us period)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3) {
 REG_PWM_CDTY0 = duty0;
 REG_PWM_CDTY1 = duty1;
 REG_PWM_CDTY2 = duty2;
 REG_PWM_CDTY3 = duty3;
}

Hi, I wam trying to modify the above code so that I use 6 channels instead of 4. How should I do this? I I have tried to modify the REG_PIOC_PDR, REG_PIOC_ABSR, REG_PMC_PCER1.

Please help me out.
Thanks

Here is the code

//#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <PID_v1.h>
#include<math.h>
unsigned short duty0; // pin 34 and inverted on pin 35
unsigned short duty1; // pin 36 and inverted on pin 37
unsigned short duty2; // pin 38 and inverted on pin 39
unsigned short duty3; // pin 40 and inverted on pin 41
unsigned short duty4; // pin 09 and inverted on pin no comp
unsigned short duty5; // pin 08 and inverted on pin 44

const int sinePWM_P[335]PROGMEM= {0,79,158,237,316,394,473,551,630,708,785,863,940,1017,1093,1169,1245,1320,1395,1469,1543,1616,1689,1761,1832,1903,1973,2043,2111,2179,2246,2313,2378,2443,2507,2570,2632,2693,2753,2812,2870,2928,2984,3039,3093,3146,3198,3248,3298,3346,3393,3439,3484,3527,3570,3611,3650,3689,3726,3762,3796,3829,3861,3892,3921,3948,3974,3999,4023,4045,4065,4084,4102,4118,4133,4146,4158,4169,4178,4185,4191,4195,4198,4200,4200,4198,4195,4191,4185,4178,4169,4158,4146,4133,4118,4102,4084,4065,4045,4023,3999,3974,3948,3921,3892,3861,3829,3796,3762,3726,3689,3650,3611,3570,3527,3484,3439,3393,3346,3298,3248,3198,3146,3093,3039,2984,2928,2870,2812,2753,2693,2632,2570,2507,2443,2378,2313,2246,2179,2111,2043,1973,1903,1832,1761,1689,1616,1543,1469,1395,1320,1245,1169,1093,1017,940,863,785,708,630,551,473,394,316,237,158,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
const int sinePWM_N[335]PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,158,237,316,394,473,551,630,708,785,863,940,1017,1093,1169,1245,1320,1395,1469,1543,1616,1689,1761,1832,1903,1973,2043,2111,2179,2246,2313,2378,2443,2507,2570,2632,2693,2753,2812,2870,2928,2984,3039,3093,3146,3198,3248,3298,3346,3393,3439,3484,3527,3570,3611,3650,3689,3726,3762,3796,3829,3861,3892,3921,3948,3974,3999,4023,4045,4065,4084,4102,4118,4133,4146,4158,4169,4178,4185,4191,4195,4198,4200,4200,4198,4195,4191,4185,4178,4169,4158,4146,4133,4118,4102,4084,4065,4045,4023,3999,3974,3948,3921,3892,3861,3829,3796,3762,3726,3689,3650,3611,3570,3527,3484,3439,3393,3346,3298,3248,3198,3146,3093,3039,2984,2928,2870,2812,2753,2693,2632,2570,2507,2443,2378,2313,2246,2179,2111,2043,1973,1903,1832,1761,1689,1616,1543,1469,1395,1320,1245,1169,1093,1017,940,863,785,708,630,551,473,394,316,237,158,79,0};

int vq, i;
void setup() {
// put your setup code here, to run once:
pwmc_setup();
pinMode(22,OUTPUT);
}

void loop() {
// put your main code here, to run repeatedly:
pwmc_duty(duty0, duty1, duty2, duty3, duty4, duty5);
//delayMicroseconds(46);
i++;
if (i<168)
{
digitalWrite(22,HIGH);

}
else
{
digitalWrite(22,LOW);
if (i==333) i=0;
}

}
//VERIFY PIOC_PDR = 0x3FC
void pwmc_setup()
{
//Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
REG_PIOC_PDR = 0x3FC; //B1111111100, PIO Disable Register
REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
REG_PWM_ENA = REG_PWM_SR | B111111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3,4,5)

// Use 0x100000 to enable dead time
REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR2 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR3 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR4 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR5 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1

// available range is same as duty and depends on PWM frequency (see datasheet)
REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
REG_PWM_DT2 = 0x000000; //Channe2 Dead Time Register for pins 38,39 (PWML2,PWMH2)
REG_PWM_DT3 = 0x000000; //Channe3 Dead Time Register for pins 40,41 (PWML3,PWMH3)
REG_PWM_DT4 = 0x000000; //Channe4 Dead Time Register for pins 09,xx (PWML2,PWMH2)
REG_PWM_DT5 = 0x000000; //Channe5 Dead Time Register for pins 08,xx (PWML3,PWMH3)

REG_PWM_CPRD0 = 4200; //Channe0 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD1 = 4200; //Channe1 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD2 = 4200; //Channe2 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD3 = 4200; //Channe3 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD4 = 4200; //Channe2 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD5 = 4200; //Channe3 Period Register (84mhz/4200=20KHz)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3, unsigned short duty4, unsigned short duty5)
{
static int num01, num23, num45;

if(num01>=223) num23= num01 - 223;
else num23 = num01+112;

if(num01>=112) num45 = num01-112;
else num45 = num01 + 223;

if(++num01>=335) num01 = 0;
duty0 = sinePWM_P[num01];
duty1 = sinePWM_N[num01];
duty2 = sinePWM_P[num23];
duty3 = sinePWM_N[num23];
duty4 = sinePWM_P[num45];
duty5 = sinePWM_N[num45];

REG_PWM_CDTY0 = duty0;
REG_PWM_CDTY1 = duty1;
REG_PWM_CDTY2 = duty2;
REG_PWM_CDTY3 = duty3;
REG_PWM_CDTY4 = duty4;
REG_PWM_CDTY5 = duty5;
delayMicroseconds(47);
}

SalvaMalk:
Hi, I wam trying to modify the above code so that I use 6 channels instead of 4. How should I do this? I I have tried to modify the REG_PIOC_PDR, REG_PIOC_ABSR, REG_PMC_PCER1.

Please help me out.
Thanks

Here is the code

//#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <PID_v1.h>
#include<math.h>
unsigned short duty0; // pin 34 and inverted on pin 35
unsigned short duty1; // pin 36 and inverted on pin 37
unsigned short duty2; // pin 38 and inverted on pin 39
unsigned short duty3; // pin 40 and inverted on pin 41
unsigned short duty4; // pin 09 and inverted on pin no comp
unsigned short duty5; // pin 08 and inverted on pin 44

const int sinePWM_P[335]PROGMEM= {0,79,158,237,316,394,473,551,630,708,785,863,940,1017,1093,1169,1245,1320,1395,1469,1543,1616,1689,1761,1832,1903,1973,2043,2111,2179,2246,2313,2378,2443,2507,2570,2632,2693,2753,2812,2870,2928,2984,3039,3093,3146,3198,3248,3298,3346,3393,3439,3484,3527,3570,3611,3650,3689,3726,3762,3796,3829,3861,3892,3921,3948,3974,3999,4023,4045,4065,4084,4102,4118,4133,4146,4158,4169,4178,4185,4191,4195,4198,4200,4200,4198,4195,4191,4185,4178,4169,4158,4146,4133,4118,4102,4084,4065,4045,4023,3999,3974,3948,3921,3892,3861,3829,3796,3762,3726,3689,3650,3611,3570,3527,3484,3439,3393,3346,3298,3248,3198,3146,3093,3039,2984,2928,2870,2812,2753,2693,2632,2570,2507,2443,2378,2313,2246,2179,2111,2043,1973,1903,1832,1761,1689,1616,1543,1469,1395,1320,1245,1169,1093,1017,940,863,785,708,630,551,473,394,316,237,158,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
const int sinePWM_N[335]PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,158,237,316,394,473,551,630,708,785,863,940,1017,1093,1169,1245,1320,1395,1469,1543,1616,1689,1761,1832,1903,1973,2043,2111,2179,2246,2313,2378,2443,2507,2570,2632,2693,2753,2812,2870,2928,2984,3039,3093,3146,3198,3248,3298,3346,3393,3439,3484,3527,3570,3611,3650,3689,3726,3762,3796,3829,3861,3892,3921,3948,3974,3999,4023,4045,4065,4084,4102,4118,4133,4146,4158,4169,4178,4185,4191,4195,4198,4200,4200,4198,4195,4191,4185,4178,4169,4158,4146,4133,4118,4102,4084,4065,4045,4023,3999,3974,3948,3921,3892,3861,3829,3796,3762,3726,3689,3650,3611,3570,3527,3484,3439,3393,3346,3298,3248,3198,3146,3093,3039,2984,2928,2870,2812,2753,2693,2632,2570,2507,2443,2378,2313,2246,2179,2111,2043,1973,1903,1832,1761,1689,1616,1543,1469,1395,1320,1245,1169,1093,1017,940,863,785,708,630,551,473,394,316,237,158,79,0};

int vq, i;
void setup() {
// put your setup code here, to run once:
pwmc_setup();
pinMode(22,OUTPUT);
}

void loop() {
// put your main code here, to run repeatedly:
pwmc_duty(duty0, duty1, duty2, duty3, duty4, duty5);
//delayMicroseconds(46);
i++;
if (i<168)
{
digitalWrite(22,HIGH);

}
else
{
digitalWrite(22,LOW);
if (i==333) i=0;
}

}
//VERIFY PIOC_PDR = 0x3FC
void pwmc_setup()
{
//Configure PWM channels 0,1,2,3 (PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
REG_PIOC_PDR = 0x3FC; //B1111111100, PIO Disable Register
REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
REG_PWM_ENA = REG_PWM_SR | B111111; //PWM Enable Register | PWM Status Register (activate channels 0,1,2,3,4,5)

// Use 0x100000 to enable dead time
REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR2 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR3 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR4 = 0x00000; //Channe2 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR5 = 0x00000; //Channe3 Mode Register: Dead Time Enable DTE=1

// available range is same as duty and depends on PWM frequency (see datasheet)
REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
REG_PWM_DT2 = 0x000000; //Channe2 Dead Time Register for pins 38,39 (PWML2,PWMH2)
REG_PWM_DT3 = 0x000000; //Channe3 Dead Time Register for pins 40,41 (PWML3,PWMH3)
REG_PWM_DT4 = 0x000000; //Channe4 Dead Time Register for pins 09,xx (PWML2,PWMH2)
REG_PWM_DT5 = 0x000000; //Channe5 Dead Time Register for pins 08,xx (PWML3,PWMH3)

REG_PWM_CPRD0 = 4200; //Channe0 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD1 = 4200; //Channe1 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD2 = 4200; //Channe2 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD3 = 4200; //Channe3 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD4 = 4200; //Channe2 Period Register (84mhz/4200=20KHz)
REG_PWM_CPRD5 = 4200; //Channe3 Period Register (84mhz/4200=20KHz)
}

//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short duty0, unsigned short duty1, unsigned short duty2, unsigned short duty3, unsigned short duty4, unsigned short duty5)
{
static int num01, num23, num45;

if(num01>=223) num23= num01 - 223;
else num23 = num01+112;

if(num01>=112) num45 = num01-112;
else num45 = num01 + 223;

if(++num01>=335) num01 = 0;
duty0 = sinePWM_P[num01];
duty1 = sinePWM_N[num01];
duty2 = sinePWM_P[num23];
duty3 = sinePWM_N[num23];
duty4 = sinePWM_P[num45];
duty5 = sinePWM_N[num45];

REG_PWM_CDTY0 = duty0;
REG_PWM_CDTY1 = duty1;
REG_PWM_CDTY2 = duty2;
REG_PWM_CDTY3 = duty3;
REG_PWM_CDTY4 = duty4;
REG_PWM_CDTY5 = duty5;
delayMicroseconds(47);
}

Without looking too deeply into your code I see a few things right off that have to change:

  1. If you need two more channels with complementary outputs, PWM4 and PWM7 are out, that leaves PWM5 and PWM6. PWM5L is found on port C.22 (Pin D8), PWM5H is found on port C.19 (Pin D44), PWM6L is found on port C.23 (Pin D7), and PWM6H is found on port C.18 (Pin D45).
  2. REG_PIOC_PDR needs to have the additional channel bits set. 0x3FCu becomes 0xCC03FCul.
  3. REG_PIOC_ABSR also needs to have the additional channel bits set. All the added channels are listed as peripheral B. 0x3FCu becomes 0xCC03FCul.
  4. REG_PWM_ENA need to be tweaked to use the right channels. it becomes B01101111.
  5. Subsitute PWM channel 6 for PWM channel 4 in any other references.

The pin references are listed in my sketch attachment in the previous post.