Arduino Due 10 bit muti-channel center aligned PWM at 2000 Hz

Hi fellows,

I'm new to ARM. I'm working on Arduino Due from last 2 weeks. I got a medium experience on programming arduino and is limited to C & arduino.

I need to set up 10 bit, 6 PWM channels synchronized, center aligned at 2000 Hz, whose duty ratios would be changed in time of interrupt after each cycle (see SYNC PWM.jpg please in the attachment).
in the attachment it is 3 channels. but my requirement is 6.

Any library available for this task? I did search a lot but got nothing.

Can anyone help please?

Thanks in advance.

SYNC PWM.jpg

1 Like

That sounds suspiciously like you've been tasked with creating a three phase inverter. 2KHz is kind of slow but who's counting? As your luck would have it, I recently published the start of an open source inverter built around the SAM3X processor in the Due! It indeed does do 6 PWM sync'd center aligned. But, it isn't quite 10 bit (very, very close) nor 2KHz (instead 10KHz). It does, however, trigger an ADC reading and interrupt at the peak of the count (center of PWM) just like you want. So, it is left as an exercise for you to download the source code and figure out how to change the parameters to suit your needs. Luckily, it is full of comments explaining how it works so that shouldn't be a problem.

The code has 6 PWM outputs but they're three pairs of complementary outputs (one high, one low). I assume this is why you need 6 PWM outputs. If not you could instead enable 6 high sides or 6 low sides. Dead time is added in the code and if you're doing complementary outputs you do want that. If not then you can remove the dead time.

Hi souravg009,

The following code sets up synchronous, centre aligned PWM on 8 channels with 14-bit resolution, at 2kHz, 50% duty cycle:

// Enable synchronous, centre-aligned, 14-bit resolution PWM at 2kHz 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;          // Disable the port B PWM pins as GPIO
  REG_PIOC_PDR |= PIO_PDR_P24 | PIO_PDR_P23 | PIO_PDR_P22 | PIO_PDR_P21;          // Disable the port C PWM pins as GPIO
  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_CALG | PWM_CMR_CPRE_CLKA;    // Enable centre aligned PWM and set the clock source as CLKA
    PWM->PWM_CH_NUM[i].PWM_CPRD = 21000;                              // Set the PWM period register 84MHz/(2*2kHz)=21000;
  } 
  REG_PWM_ENA = PWM_ENA_CHID0;           // Enable the PWM channels, (only need to set channel 0 for synchronous mode)
  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 = 10500;                           // Set the PWM duty cycle to 50% (21000/2=10500)
  } 
  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() {}

I offer my heartfelt thanks to Collin80 & MartinL for your mindblowing support.

Yes Collin80, I am building a 3-phase inverter as a part of my coursework. which is a part of the whole project. and your code is like a poetry (not exaggerating). :slight_smile:

Thanks MartinL, you have provided a very useful & precise code to work with. :slight_smile:

Both of you helped me a lot to learn. I'll try to be a help for forum and you guys. I'll surely work on the code and keep you updated.

Thanks again.

Hi Collin80,
As far as I went through your code and learned, you have used a comparison unit to trigger ADC.

//Use comparison unit 0 for Event line 0 which the ADC hardware is looking for triggers on
  PWMC_ConfigureEventLineMode(PWM_INTERFACE, 0, PWM_ELMR_CSEL0);

But, for some more reason i need to activate "PWM ISR" to set a flag to start next cycle of calculation after updating the duty cycle values in the PWM channels.
Can you help with that please?

Thanks

I have been searching for the correct methods for last 2 days, I went through datasheet & other resources in the internet and here is what I've come up with.
This is based on the code Collin80 provided me. Thanks again Collin.
This code should create 10 bit, Synchronous, 3 Complimentary, center aligned PWM and updates the value at the Interrupt on the Max value of the counter.
Anyone please tell me if I'm right? Is anything else needed to get it complete?
I don't have an oscilloscope so I cant validate the correctness of the code.

/* DUE PWM
CHANNEL:				3
COMPLIMENTARY OUTPUTS: 	3 HIGH + 3 LOW
SYNC:				ALL
FREQUENCY:			2 KHz
MAX_PWM_DUTY:			1023 (10 BIT)
PWM_TARGET_DEADTIME	600 nS
*/

#include <Arduino.h>
#include "pwm.h"
#include "config.h"
void setup_pwm()
{
  //pinMode(42, OUTPUT);  

  pmc_enable_periph_clk (PWM_INTERFACE_ID) ;  // turn on clocking to PWM unit

  PWMC_DisableChannel(PWM_INTERFACE, 0);
  PWMC_DisableChannel(PWM_INTERFACE, 1);
  PWMC_DisableChannel(PWM_INTERFACE, 2); 
  
    // Configure (PC2) through (PC7) to be PWM now(for complimentary PWM output. i.e. PC2 = low, PC3= high). 
	// This causes all of them to be PWM channels 0, 1, 2
	//	C7	C6	C5	C4	C3	C2	C1	C0
	//	1	1	 1	 1	 1   1   0   0	=>	0b11111100 = 0xFC
  PIOC->PIO_PDR = 0xFC;  	// 0b11111100 disable PIO control for all of them
  PIOC->PIO_IDR = 0xFC;   	// 0b11111100 disable PIO interrupts for all of them
  PIOC->PIO_ABSR |= 0xFC;  	// 0b11111100 switch to B peripheral
  
  //assuming 2KHz and 1023 that would be a clock of 4.092 MHz (Center aligned is twice speed)
  PWMC_ConfigureClocks((unsigned int)PWM_FREQ * MAX_PWM_DUTY * 2, 0, VARIANT_MCK );
  
  //find the number of ticks necessary to ensure that dead time is at least as long as requested.
  int deadTicks = ((VARIANT_MCK * 1000ul) / ((unsigned int)PWM_FREQ * MAX_PWM_DUTY) / (unsigned int)PWM_TARGET_DEADTIME) + 1;

  deadTicks = 40;

  //                   PWM, Chan, Clock, Left/Center, Polarity, CountEvent, DeadEnable, DeadHighInv, DeadLowInv
  // CHANNEL 0
  PWMC_ConfigureChannelExt(PWM_INTERFACE, 0, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);
  PWMC_SetPeriod (PWM_INTERFACE, 0, 1023) ;  	// period = 525 ticks (10Khz), every tick is 1/5.25 micro seconds
  PWMC_SetDutyCycle (PWM_INTERFACE, 0, 0); 		//set duty cycle of channel 0 to 0 (duty is out of the period above so min is 0 max is 525)
  PWMC_SetDeadTime(PWM_INTERFACE, 0, deadTicks, deadTicks) ; //set a bit of dead time around all transitions
  // CHANNEL 1
  PWMC_ConfigureChannelExt (PWM, 1, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);
  PWMC_SetPeriod (PWM, 1, 1023) ; 
  PWMC_SetDutyCycle (PWM, 1, 0); //set duty cycle of channel 0 to 0 (duty is out of the period above
  PWMC_SetDeadTime(PWM, 1, deadTicks, deadTicks) ; //set some dead time
  // CHANNEL 2
  PWMC_ConfigureChannelExt (PWM, 2, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);
  PWMC_SetPeriod (PWM, 2, 1023) ;
  PWMC_SetDutyCycle (PWM, 2, 0); //set duty cycle of channel 0 to 0 (duty is out of the period above
  PWMC_SetDeadTime(PWM, 2, deadTicks, deadTicks) ; //set some dead time
  
  // SYNCHRONISM OF PWM CHANNELS
  PWMC_ConfigureSyncChannel(PWM_INTERFACE, 7, 0, 0,0); //make channels 0, 1, 2 be synchronous ie 0b0000...0111 
  
  PWMC_SetSyncChannelUpdatePeriod(PWM_INTERFACE, 1);	//PWMC_SetSyncChannelUpdatePeriod(PWM_INTERFACE, PWM_Period)
  PWMC_ConfigureChannelExt(PWM_INTERFACE, 0, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);
  PWMC_ConfigureChannelExt(PWM_INTERFACE, 1, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);
  PWMC_ConfigureChannelExt(PWM_INTERFACE, 2, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0, 0, PWM_CMR_DTE, 0, 0);  
  
  // Start of Interrupts declaration
  //Enable of the Interrupts (writing CHIDx in PWM_IER1 register, and writing WRDYE, CMPMx in PWM_IER2 register)
  PWM_INTERFACE->PWM_IER1 = 0x01; 			//0b0000 0000 0000 0000 0000 0000 0000 0001--enable CHID0 on channel 0
  PWM_INTERFACE->PWM_IDR1 = 0xFFFFFFFE; 	//0b1111 1111 1111 1111 1111 1111 1111 1110--enable CHID0 on channel 0
  PWM_INTERFACE->PWM_IER2 = 0x101; 		//0b0000 0000 0000 0000 0000 0001 0000 0001--enable WRDY => 0 & CMPM0 => 8 on channel 0
  PWM_INTERFACE->PWM_IDR2 = 0xFFFFFEFE; 	//0b1111 1111 1111 1111 1111 1110 1111 1110--enable WRDY => 0 & CMPM0 => 8 on channel 0

  // Setup NVIC interrupt
  NVIC_DisableIRQ(PWM_IRQn); 		
  NVIC_ClearPendingIRQ(PWM_IRQn);
  NVIC_SetPriority(PWM_IRQn, 0);
  NVIC_EnableIRQ((IRQn_Type)36); 	
  
  /*
  // Enable 
  PWMC_EnableChannelIt(PWM_INTERFACE, 0);
  */
  
  PWMC_EnableChannel (PWM_INTERFACE, 0) ;   // enable
  PWMC_EnableChannel (PWM_INTERFACE, 1) ;   // enable
  PWMC_EnableChannel (PWM_INTERFACE, 2) ;   // enable
}

void loop() {
}

void PWM_Handler(void) // PWM interrupt handler Occurs @ INTERRUPT as shown in 'SYNC PWM.jpg'
{
  volatile long dummy = PWM_INTERFACE->PWM_ISR1; 	// clear interrupt flag
  dummy = PWM_INTERFACE->PWM_ISR2; 				// clear interrupt flag
  
  flag = TRUE;                                                             // Calculation start flag for my program
  PWMC_SetDutyCycle (PWM_INTERFACE, 0, a);
  PWMC_SetDutyCycle (PWM_INTERFACE, 1, b);
  PWMC_SetDutyCycle (PWM_INTERFACE, 2, c);
  
  PWMC_SetSyncChannelUpdateUnlock(PWM_INTERFACE); //enable setting of all those duties all at once
}

Though I do not understand utility of

PWMC_EnableChannelIt(PWM_INTERFACE, channel);

hence I kept it under comment and changed it to work with channel 0 if necessary.
can anyone please describe its functionality?

Thanks in advance.
Sourav