Arduino Due PWM

I’m using an Arduino Due to create 3 phase shifted PWM signals at 50Khz. I changed the frequency value in the variant.h file and successfully changed the frequency of all PWM signals to 50Khz. However, when evaluating the output waveforms on an Oscilloscope, there is a phase shift between pins 2,3. I don’t understand why there is a phase shift in the default PWM signals. Any help in understanding would be greatly appreciated.

void setup() {

// put your setup code here, to run once:
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
pinMode(5,OUTPUT);

}

void loop() {
analogWrite(2,100);
analogWrite(3,100); // lower number is higher duty cycle
analogWrite(4,100);
analogWrite(5,100);

}

Because they are on different counters. The counters (or timers) are not synchronised.

If you want 3 phases synchronised, meaning the phase difference never drifts and is under your control then the Due has some special hardware to do this for you. It’s intended to drive 3-phase brushless motors but it can be used for anything. It will require a lot of reading in the datasheet to get this mode working properly.

Ok, thanks. And to just double check that I understood you properly, the Due already has this capability built in ?

Yes, but unless you can find a library, it will not be easy.

The DUE has a synchro feature over its PWM peripheral and Timer Counter. You can easily synchro 3 PWM pulses at a 50 KHz frequency and e.g. phase shift PWM1 and PWM2 from PWM0 without any library.

1 Like

After playing around with the Due I’ve found that changing the the values in the Variant.H file creates a phase shift in the PWM signals. This method can be used on pin 3,4 and 13 with a value of approximately 45Khz to get a 120 degree phase shift between the 3 channels. This method can also we used to get achieve other phase shift values. For example at 65Khz there is a phase shift of approximately 180 degrees. This method can be applied to greater frequencies as well. To do this increase the frequency in increments of the phase shifted frequency. For example, if you want a 120 degree phase shifted signal that is greater than 45Khz, you can increase the frequency in increments of 45Khz (90khz,135Khz…) to keep the phase shift the same. This seems like it could be a potential solution for interleaving /phase shifting PWM signals that need a constant frequency and variable duty cycle. (These values need to be tweaked a bit as there is a small degree error in the phase shift, I was getting about a 120.6 degree phase shift) I’ll post screenshots on my Oscilloscope when I get the chance so that people can verify the phase shift

Page 985 of Sam3x datasheet you will find an explanation to update duty cycle for synchronous channels. There are 3 methods, the 3rd one (IMO the most interesting one) is the automatic update via a PDC DMA.

An example sketch with 3 PWM phase shifted and automatic duty cycle update via DMA (to easely see the result, select Menu>Tools>Serial Plotter and select 57600 for baud rate):

/*******************************************************************************************/
/*                Synchro channel PWML0, PWML1 and PWML2 with PDC DMA trigger on Compare               */
/*******************************************************************************************/


#define sinsize  (360)
#define Wavesize (360)
#define PERIOD_VALUE   (62500)        // ****** For a 1 Hz sinwave
#define NbCh      (3)                 // If Only channel 0 ---> Number of channels = 1, etc..

#define DUTY_BUFFER_LENGTH      (Wavesize * NbCh) // Half words

uint16_t Duty_Buffer[DUTY_BUFFER_LENGTH];

#define UpdatePeriod_Msk (0b1111)
#define UpdatePeriod    (UpdatePeriod_Msk & 0b0000) //Defines the time between each duty cycle update of the synchronous channels
//This time equals to (UpdatePeriod + 1) periods of the Reference channel 0

uint16_t Sin_Duty[sinsize];
uint16_t Wave_Duty[Wavesize];

void setup () {

  Serial.begin(57600);

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;       // PWM controller power ON

  // PWML0 on PC2, peripheral type B
  PIOC->PIO_PDR |= PIO_PDR_P2;      // Set PWM pin to peripheral
  PIOC->PIO_ABSR |= PIO_PC2B_PWML0; 


  // PWML1 on PC4 ; Peripheral=B
  PIOC->PIO_PDR |= PIO_PDR_P4;                       
  PIOC->PIO_ABSR |= PIO_PC4B_PWML1;               

  // PWML2 on PC6 ; Perpipheral type B
  PIOC->PIO_PDR |= PIO_PDR_P6;
  PIOC->PIO_ABSR |= PIO_PC6B_PWML2;


  // Set synchro channels list : Channel 0, channel 1 and channel 2
  PWM->PWM_DIS = PWM_DIS_CHID0 | PWM_DIS_CHID1 | PWM_DIS_CHID2;

  PWM->PWM_SCM  = PWM_SCM_SYNC0        // Add SYNCx accordingly, at least SYNC0, plus SYNC1 & SYNC2
                  | PWM_SCM_SYNC1
                  | PWM_SCM_SYNC2
                  | PWM_SCM_UPDM_MODE2;  //Automatic write of duty-cycle update registers by the PDC DMA

  // Set duty cycle update period
  PWM->PWM_SCUP = PWM_SCUP_UPR(UpdatePeriod);

  // Set the PWM Reference channel 0 i.e. : Clock/Frequency/Alignment
  PWM->PWM_CLK = PWM_CLK_PREA(0b0000) | PWM_CLK_DIVA(3);       // Set the PWM clock rate for 84 MHz/3
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;               // The period is left aligned, clock source as CLKA on channel 0
  PWM->PWM_CH_NUM[0].PWM_CPRD = PERIOD_VALUE;                   // Set the PWM frequency (84MHz/3)/PERIOD_VALUE Hz

  /****  Final frequency = MCK/DIVA/PRES/CPRD/(UPR + 1)/Wavesize    ****/

  // Set Interrupt events
  PWM->PWM_IER2 = PWM_IER2_WRDY;   //Write Ready for Synchronous Channels Update Interrupt Enable
  //synchro with ENDTX End of TX Buffer Interrupt Enable

  // Fill duty cycle buffer for channels 0, x, y ...
  // Duty_Buffer is a buffer of Half Words(H_W) composed of N lines whose structure model for each duty cycle update is :
  // [ H_W: First synchro channel 0 duty cycle **Mandatory** ]/[ H_W: Second synchro channel duty cycle ] ... and so on

  /*      PWML0 waveform between 0° and 120°    */
  for (int i = 0; i <= sinsize / 3; i++) {
    Sin_Duty[i] = (62500 * (sinf( i * 2 * PI / sinsize ) + 0));
  }
  /*      PWML0 waveform between 120° and 240°    */
  for (int i = sinsize / 3; i <= 2 * sinsize / 3; i++) {
    Sin_Duty[i] = Sin_Duty[240 - i];
  }

  /*      PWML0 waveform between 240° and 360°    */
  for (int i = 2 * sinsize / 3; i <= sinsize; i++) {
    Sin_Duty[i] = 0;
  }

  /*     Fill Duty_Buffer for all synchro channels  */
  for (uint32_t i = 0; i < sinsize; i++) {
    Duty_Buffer[i * NbCh + 0] = Sin_Duty[i];
    Duty_Buffer[i * NbCh + 1] = Sin_Duty[(i + sinsize / 3) % sinsize];
    Duty_Buffer[i * NbCh + 2] = Sin_Duty[(i + 2 * sinsize / 3) % sinsize];
  }

  PWM->PWM_ENA = PWM_ENA_CHID0;                  // Enable PWM for all channels, channel 0 Enable is sufficient

  PWM->PWM_TPR  = (uint32_t)Duty_Buffer;        // FIRST DMA buffer
  PWM->PWM_TCR  = DUTY_BUFFER_LENGTH;           // Number of Half words
  PWM->PWM_TNPR = (uint32_t)Duty_Buffer;        // Next DMA buffer
  PWM->PWM_TNCR = DUTY_BUFFER_LENGTH;
  PWM->PWM_PTCR = PWM_PTCR_TXTEN;               // Enable PDC Transmit channel request

  NVIC_EnableIRQ(PWM_IRQn);
}

void loop() {
  
  // For debugging only :
  // Check correct updates of CDTY via CDTYUPD by DMA
  // Menu > Tools > Serial Plotter -- 57600 baud
  printf(
    "%d,%d,%d\n",
    PWM->PWM_CH_NUM[0].PWM_CDTY,
    PWM->PWM_CH_NUM[1].PWM_CDTY,
    PWM->PWM_CH_NUM[2].PWM_CDTY
  );
 // If a PWM frequency update of PWM0 (and therefore PWM1 and PWM2)
 // Do it with PWM_CPRDUPD in loop()
}

void PWM_Handler() {  // move PDC DMA pointers to next buffer

  PWM->PWM_ISR2;      // Clear status register

  PWM->PWM_TNPR = (uint32_t)Duty_Buffer;
  PWM->PWM_TNCR = DUTY_BUFFER_LENGTH;

}

ard_newbie:
Page 985 of Sam3x datasheet you will find an explanation to update duty cycle for synchronous channels. There are 3 methods, the 3rd one (IMO the most interesting one) is the automatic update via a PDC DMA.

An example sketch with 3 PWM phase shifted and automatic duty cycle update via DMA (to easely see the result, select Menu>Tools>Serial Plotter and select 57600 for baud rate):

/*******************************************************************************************/

/*                Synchro channel PWML0, PWML1 and PWML2 with PDC DMA trigger on Compare               /
/
******************************************************************************************/

#define sinsize  (360)
#define Wavesize (360)
#define PERIOD_VALUE   (62500)        // ****** For a 1 Hz sinwave
#define NbCh      (3)                 // If Only channel 0 —> Number of channels = 1, etc…

#define DUTY_BUFFER_LENGTH      (Wavesize * NbCh) // Half words

uint16_t Duty_Buffer[DUTY_BUFFER_LENGTH];

#define UpdatePeriod_Msk (0b1111)
#define UpdatePeriod    (UpdatePeriod_Msk & 0b0000) //Defines the time between each duty cycle update of the synchronous channels
//This time equals to (UpdatePeriod + 1) periods of the Reference channel 0

uint16_t Sin_Duty[sinsize];
uint16_t Wave_Duty[Wavesize];

void setup () {

Serial.begin(57600);

PMC->PMC_PCER1 |= PMC_PCER1_PID36;       // PWM controller power ON

// PWML0 on PC2, peripheral type B
 PIOC->PIO_PDR |= PIO_PDR_P2;      // Set PWM pin to peripheral
 PIOC->PIO_ABSR |= PIO_PC2B_PWML0;

// PWML1 on PC4 ; Peripheral=B
 PIOC->PIO_PDR |= PIO_PDR_P4;                      
 PIOC->PIO_ABSR |= PIO_PC4B_PWML1;

// PWML2 on PC6 ; Perpipheral type B
 PIOC->PIO_PDR |= PIO_PDR_P6;
 PIOC->PIO_ABSR |= PIO_PC6B_PWML2;

// Set synchro channels list : Channel 0, channel 1 and channel 2
 PWM->PWM_DIS = PWM_DIS_CHID0 | PWM_DIS_CHID1 | PWM_DIS_CHID2;

PWM->PWM_SCM  = PWM_SCM_SYNC0        // Add SYNCx accordingly, at least SYNC0, plus SYNC1 & SYNC2
                 | PWM_SCM_SYNC1
                 | PWM_SCM_SYNC2
                 | PWM_SCM_UPDM_MODE2;  //Automatic write of duty-cycle update registers by the PDC DMA

// Set duty cycle update period
 PWM->PWM_SCUP = PWM_SCUP_UPR(UpdatePeriod);

// Set the PWM Reference channel 0 i.e. : Clock/Frequency/Alignment
 PWM->PWM_CLK = PWM_CLK_PREA(0b0000) | PWM_CLK_DIVA(3);       // Set the PWM clock rate for 84 MHz/3
 PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;               // The period is left aligned, clock source as CLKA on channel 0
 PWM->PWM_CH_NUM[0].PWM_CPRD = PERIOD_VALUE;                   // Set the PWM frequency (84MHz/3)/PERIOD_VALUE Hz

/****  Final frequency = MCK/DIVA/PRES/CPRD/(UPR + 1)/Wavesize    ****/

// Set Interrupt events
 PWM->PWM_IER2 = PWM_IER2_WRDY;   //Write Ready for Synchronous Channels Update Interrupt Enable
 //synchro with ENDTX End of TX Buffer Interrupt Enable

// Fill duty cycle buffer for channels 0, x, y …
 // Duty_Buffer is a buffer of Half Words(H_W) composed of N lines whose structure model for each duty cycle update is :
 // [ H_W: First synchro channel 0 duty cycle Mandatory ]/[ H_W: Second synchro channel duty cycle ] … and so on

/*      PWML0 waveform between 0° and 120°    /
 for (int i = 0; i <= sinsize / 3; i++) {
   Sin_Duty[i] = (62500 * (sinf( i * 2 * PI / sinsize ) + 0));
 }
 /
     PWML0 waveform between 120° and 240°    */
 for (int i = sinsize / 3; i <= 2 * sinsize / 3; i++) {
   Sin_Duty[i] = Sin_Duty[240 - i];
 }

/*      PWML0 waveform between 240° and 360°    */
 for (int i = 2 * sinsize / 3; i <= sinsize; i++) {
   Sin_Duty[i] = 0;
 }

/*     Fill Duty_Buffer for all synchro channels  */
 for (uint32_t i = 0; i < sinsize; i++) {
   Duty_Buffer[i * NbCh + 0] = Sin_Duty[i];
   Duty_Buffer[i * NbCh + 1] = Sin_Duty[(i + sinsize / 3) % sinsize];
   Duty_Buffer[i * NbCh + 2] = Sin_Duty[(i + 2 * sinsize / 3) % sinsize];
 }

PWM->PWM_ENA = PWM_ENA_CHID0;                  // Enable PWM for all channels, channel 0 Enable is sufficient

PWM->PWM_TPR  = (uint32_t)Duty_Buffer;        // FIRST DMA buffer
 PWM->PWM_TCR  = DUTY_BUFFER_LENGTH;           // Number of Half words
 PWM->PWM_TNPR = (uint32_t)Duty_Buffer;        // Next DMA buffer
 PWM->PWM_TNCR = DUTY_BUFFER_LENGTH;
 PWM->PWM_PTCR = PWM_PTCR_TXTEN;               // Enable PDC Transmit channel request

NVIC_EnableIRQ(PWM_IRQn);
}

void loop() {
 
 // For debugging only :
 // Check correct updates of CDTY via CDTYUPD by DMA
 // Menu > Tools > Serial Plotter – 57600 baud
 printf(
   “%d,%d,%d\n”,
   PWM->PWM_CH_NUM[0].PWM_CDTY,
   PWM->PWM_CH_NUM[1].PWM_CDTY,
   PWM->PWM_CH_NUM[2].PWM_CDTY
 );
// If a PWM frequency update of PWM0 (and therefore PWM1 and PWM2)
// Do it with PWM_CPRDUPD in loop()
}

void PWM_Handler() {  // move PDC DMA pointers to next buffer

PWM->PWM_ISR2;      // Clear status register

PWM->PWM_TNPR = (uint32_t)Duty_Buffer;
 PWM->PWM_TNCR = DUTY_BUFFER_LENGTH;

}

Can I somehow change this code to just have two 90-degree phase shifted signals? I’m sorry might be a noob question but I’ve been trying to do that since a long while cant seem to make it work.

alimzia:
Can I somehow change this code to just have two 90-degree phase shifted signals?

Yes you can.

Post the code you are actually using for 2 PWM signals.

/*
 * Working with a 90degree phase change
 * and synchronous, but duty cycle not variable
 */
 
 void setup() {
  pwmc_setup();
}

void loop() {
}

void pwmc_setup()
{
  //Configure PWM channels 0,1 (PWML0,PWMH0,PWML1,PWMH1), (port C.2,C.3,C.4,C.5), (pins P34,P35,P36,P37)
  REG_PIOC_PDR = 0x3C;                   //B111100, PIO Disable Register
  REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3C;  //B111100, Peripheral AB Select Register
  REG_PMC_PCER1 = REG_PMC_PCER1 | 16;    //Peripheral Clock Enable Register 1 (activate clock for PWM, id36)
  REG_PWM_ENA = REG_PWM_SR | B11;        //PWM Enable Register | PWM Status Register (activate channels 0,1)
  REG_PWM_SMMR = 0x10003;                //Stepper Motor Mode Register
  REG_PWM_CPRD0 = 840;                   //Channel0 Period Register (84mhz/4/3360=25kHz=4µs period)
  REG_PWM_CPRD1 = 840;                   //Channel1 Period Register (84mhz/4/3360=25kHz=4µs period)
  REG_PWM_CDTY0 = 420;                   //Channel0 Duty Cycle Register
  REG_PWM_CDTY1 = 420;                   //Channel1 Duty Cycle Register
}

I tried using this code but I can’t change the duty cycle here. This is the code with Stepper Motor Mode.

// 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 8 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_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)
 
}

void loop() {
  int duty;

  for (int x = 0; x < 25 ; x++)
  {
   duty = duty + 1000;
 
    PWM->PWM_CH_NUM[5].PWM_CDTYUPD = duty;
    PWM->PWM_CH_NUM[6].PWM_CDTYUPD = duty;                           // 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
  
  }
  duty = 0;
}

And then this is another way I tried doing it, using the synchronous channels mode. But how am I supposed to update the duty cycle again? Doesn’t work this way…

Note that Figure 38-6 page 981 of Sam3x datasheet is wrong. To see the right figure, read page 1192 of
Sam S70 datasheet. As you will notice, once channel 0 PWM_SMMR register is enabled, PWMH0 and PWMH1 will be 90 phase shifted automatically.

BTW, don’t use magic numbers. An example sketch to output 2-bit Gray counter with PWMH0 and PWMH1:

/***********************************************************/
/*     PWMH0-PWML0-PWMH1-PWML1   in 2 bit gray count       */
/***********************************************************/

void setup () {
  Serial.begin(250000);

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                   // PWM power ON

  // set PC2 = PWML0, PC3 = PWMH0, PC4 = PWML1 and PC5 = PWMH1
  PIOC->PIO_PDR = PIO_PDR_P2                          // Set the pin to the peripheral PWM, not the GPIO
                   | PIO_PDR_P3
                   | PIO_PDR_P4
                   | PIO_PDR_P5;
  PIOC->PIO_ABSR |= PIO_PC2B_PWML0                     // Set PWM pin perhipheral type B
                    | PIO_PC3B_PWMH0
                    | PIO_PC4B_PWML1
                    | PIO_PC5B_PWMH1;

  PWM->PWM_DIS = PWM_DIS_CHID0                         // Disable PWM channel 0 and 1
                 | PWM_DIS_CHID1;

  // 2-bit gray code enable
  PWM->PWM_SMMR = PWM_SMMR_GCEN0;                      // Duty cycle always equals to 50%
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_1024;    // The period is left aligned, clock source Mck/1024 on channel 0

  PWM->PWM_CH_NUM[0].PWM_CPRD = 41016;                   // Channel 0 : Set the PWM frequency
  PWM->PWM_CH_NUM[0].PWM_CDTY = 1;                    // 0<CDTY<CPRD , irrelevant but mandatory

  PWM->PWM_ENA = PWM_ENA_CHID0
                 | PWM_ENA_CHID1;
}
void loop() {

  static uint32_t Oldmillis;
  const uint32_t Timestamp = 10000;
  static boolean Down;

  // Reverse CW <--> CCW every 10 seconds
  if ((millis() - Oldmillis) > Timestamp) {
    Oldmillis = millis();
    if (!Down) {
      PWM->PWM_SMMR |= PWM_SMMR_DOWN0;
    }
    else {
      PWM->PWM_SMMR &= ~PWM_SMMR_DOWN0;
    }
    Down = !Down;
  }

}

Thanks for the reply.

Ill try the suggestions you have made. However, one thing I forgot to mention is that I need variable duty cycle. That is the issue, I cant change the duty cycle according to my need. It is supposed to be updated manually. example: Depending on the input voltage, the duty cycle of both the signals changes to the same proportion, and hence the average output voltage does too.

Can you tell me how to change duty cycle in this manner?

PS. Can we use CDTYUPDx for updating the duty cycle here?

I sum up:

1/ You have 2 PWM signals, 90 phase shifted ---> Right ?
2/ The "Main" PWM signal frequency is constant ---> Right ? what frequency ?
3/ The "Main" PWM duty cycle vary against an input voltage (I guess a potentiometer is connected to A0)---> Right ?

  1. Yes 2 PWM signals 90 degree phase shifted. Right.
  2. The main frequency, 1MHz would be sufficient. But, the higher the better.
  3. Correct. (Not a potentiometer exactly, but the same concept yes)

You could try this:

First step.

Test your code firstly with a single PWM, e.g. PWM channel 0. Set a constant frequency, e.g. 1 MHz, enable channel 0 with PWM_ENA, update the Duty Cycle in loop() with PWM_CDTYUPD against the 12-bit conversions read on A0 (0V (0)-> 0% ; 3.3V(4095) -> 100%).

Second step.

Select a second PWM channel, e.g. PWM channel 1, select the same frequency (1 MHz), but do not enable channel 1.
In the first part code, add an update of PWM channel 1 duty cycle in loop() equal to the one of channel 0. In the setup() of the first part code, add a comparison with PWM_CMPV for channel 0 on CPRD/4 (for a 90° phase shift), enable comparison with PWM_CMPM, select an interruption on comparison with PWM_ISR2, enable interruptions with NVIC_EnableIRQ(PWM_IRQn). An interruption will fire when PWM channel 0 reaches the fourth of its period.

The first time your code enters PWM interrupt handler, enable PWM channel 1 with PWM_ENA and disable PWM interruptions with NVIC_DisableIRQ(PWM_IRQn).

From now on, PWM channel 0 and channel 1 are 90° phase shifted with the same duty cycle.

Thanks a lot mate.

I am very close to making it work. I understand completely what your idea. The only part I don’t understand how to execute as of now is:
“add a comparison with PWM_CMPV for channel 0 on CPRD/4”

I know how to enable the compare parameters but how am I supposed to give the value (CPRD/4) to CMPV_CV?

Here is the code I have so far.

/***********************************************************/
/*PWM with 90-degree phase shift using CV compare interrupt*/      
/***********************************************************/

void setup () {
  Serial.begin(250000);

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                   // PWM power ON

  // set PC2 = PWML0 & PC4 = PWML1
  PIOC->PIO_PDR = PIO_PDR_P2                          // Set the pin to the peripheral PWM, not the GPIO
                   | PIO_PDR_P4;
                   
  PIOC->PIO_ABSR |= PIO_PC2B_PWML0                     // Set PWM pin perhipheral type B
                    | PIO_PC4B_PWML1;

  PWM->PWM_CMPV0 = PWM_CMPV0_CV; //This is where I have the doubt
  

  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;    // The period is left aligned, clock source Mck/2 on channel 0
  PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;

  PWM->PWM_CH_NUM[0].PWM_CPRD = 4200;                   // Channel 0 : Set the PWM frequency
  PWM->PWM_CH_NUM[0].PWM_CDTY = 1050;                   // Channel 0 : Set duty cycle
  PWM->PWM_CH_NUM[1].PWM_CPRD = 4200;                   // Channel 0 : Set the PWM frequency
  PWM->PWM_CH_NUM[1].PWM_CDTY = 1050;                   // Channel 0 : Set duty cycle

  PWM->PWM_ENA = PWM_ENA_CHID0;
                 //| PWM_ENA_CHID1;
  PWM->PWM_IER2 = PWM_IER2_CMPM0;
  
  NVIC_EnableIRQ(PWM_IRQn);
  
}
void loop() {
  int d = 0;
  for (int i = 0; i < 9; i++){
  PWM->PWM_CH_NUM[0].PWM_CDTYUPD = d;
  PWM->PWM_CH_NUM[1].PWM_CDTYUPD = d;
  d = d + 575;
  Serial.println(d);
  delay (1000);
  }
}

void PWM_Handler(){
  PWM->PWM_ISR2;
  PWM->PWM_ENA = PWM_ENA_CHID1;
  NVIC_DisableIRQ(PWM_IRQn);
}

BTW, I am just using a generic changing (increasing) duty cycle here for the sake of simplicity.

Sam3x datasheet, Page 1040 for PWM_CMPVx.

PWM->PWM_CMP[0].PWM_CMPV = PWM_CMPV_CV(1050); // 4200/4 = 1050

And enable comparison with PWM_CMPM.

You have all header files here:

https://android.googlesource.com/platform/external/arduino-ide/+/f876b2abdebd02acfa4ba21e607327be4f9668d4/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component

Ok I’ve done everything the way you suggested (I guess). Here is the code:

/***********************************************************/
/*PWM with 90-degree phase shift using CV compare interrupt*/      
/***********************************************************/

void setup () {
  Serial.begin(250000);

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                   // PWM power ON

  // set PC2 = PWML0 & PC4 = PWML1
  PIOC->PIO_PDR = PIO_PDR_P2                          // Set the pin to the peripheral PWM, not the GPIO
                   | PIO_PDR_P4;
                   
  PIOC->PIO_ABSR |= PIO_PC2B_PWML0                     // Set PWM pin perhipheral type B
                    | PIO_PC4B_PWML1;

  PWM->PWM_CMP[0].PWM_CMPV = PWM_CMPV_CV(1050); //Set comparison value
  PWM_CMPM_CEN;             //Comparison x enable
//UPDATE: this must be PWM->PWM_CMP[0].PWM_CMPM = PWM_CMPM_CEN;
  

  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;    // The period is left aligned, clock source Mck/2 on channel 0
  PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;

  PWM->PWM_CH_NUM[0].PWM_CPRD = 4200;                   // Channel 0 : Set the PWM frequency
  PWM->PWM_CH_NUM[0].PWM_CDTY = 1050;                   // Channel 0 : Set duty cycle
  PWM->PWM_CH_NUM[1].PWM_CPRD = 4200;                   // Channel 0 : Set the PWM frequency
  PWM->PWM_CH_NUM[1].PWM_CDTY = 1050;                   // Channel 0 : Set duty cycle

  PWM->PWM_ENA = PWM_ENA_CHID0;
                 //| PWM_ENA_CHID1;
  PWM->PWM_IER2 = PWM_IER2_CMPM0;
  
  NVIC_EnableIRQ(PWM_IRQn);
  
}
void loop() {
  int d = 0;
  for (int i = 0; i < 9; i++){
  PWM->PWM_CH_NUM[0].PWM_CDTYUPD = d;
  PWM->PWM_CH_NUM[1].PWM_CDTYUPD = d;
  d = d + 575;
  Serial.println(d);
  delay (1000);
  }
}

void PWM_Handler(){
  PWM->PWM_ISR2; //Tried with PWM->PWM_ISR2 = PWM_ISR2_CMPM0 as well
  Serial.println("interuppted");
  PWM->PWM_ENA = PWM_ENA_CHID1;
  NVIC_DisableIRQ(PWM_IRQn);
}

The problem is, I never see the output “interuppted” on the serial monitor. Therefore, the program is never being interuppted? And I am just getting the first PWM and there is nothing from the pin 36 (PC4). I’m sure I’m making a slight error somewhere but I dont see where exactly.
UPDATE: OK I found my mistake. I wasn’t enabling the CMPM_CEN properly. How silly. Still thought, the rest of the question remains.

Also, thanks a ton for the header files! That is really helpful.
However, I dont see how you get from the following:-

#define PWM_CMPV_CV_Pos 0
#define PWM_CMPV_CV_Msk (0xffffffu << PWM_CMPV_CV_Pos) /**< \brief (PWM_CMPV) Comparison x Value */
#define PWM_CMPV_CV(value) ((PWM_CMPV_CV_Msk & ((value) << PWM_CMPV_CV_Pos)))

To this: PWM->PWM_CMP[0].PWM_CMPV = PWM_CMPV_CV(1050);

Like how did you get this syntax. What is CMP[0] here? CMP is not mentioned anywhere in the header files.

PWM->PWM_CMP[0].PWM_CMPM = PWM_CMPM_CEN; !!!

You will find in the header files for PWM component:

typedef struct {
RwReg PWM_CMPV; /< \brief (PwmCmp Offset: 0x0) PWM Comparison 0 Value Register */
RwReg PWM_CMPVUPD; /
< \brief (PwmCmp Offset: 0x4) PWM Comparison 0 Value Update Register */
RwReg PWM_CMPM; /< \brief (PwmCmp Offset: 0x8) PWM Comparison 0 Mode Register */
RwReg PWM_CMPMUPD; /
< \brief (PwmCmp Offset: 0xC) PWM Comparison 0 Mode Update Register */
} PwmCmp;

typedef struct {
RwReg PWM_CLK; /< \brief (Pwm Offset: 0x00) PWM Clock Register */
WoReg PWM_ENA; /
< \brief (Pwm Offset: 0x04) PWM Enable Register */
………………
RoReg Reserved7[2];
PwmCmp PWM_CMP[PWMCMP_NUMBER]; /< \brief (Pwm Offset: 0x130) 0 … 7 */
RoReg Reserved8[20];
PwmCh_num PWM_CH_NUM[PWMCH_NUM_NUMBER]; /
< \brief (Pwm Offset: 0x200) ch_num = 0 … 7 */
} Pwm;

Hey man, Thank you so much for your reply! It has really helped me and I have moved much ahead with my project now.

I have however come across another problem related to PWM and interrupts.

I am trying to simulate an encoder, using two PWM outputs and using interrupts (two) to variate their duty cycle, and a third interrupt to realise the phase shift (As you explained above).
The encoder starts with the instructions from a CAN message.

The issue I’m facing is the following:

At the start of the program (fresh uploaded), everything works fine. (see attachment “Start”)

However, with any input after the, the phase change seems to go away. (see attachment “After Change”)

Here are a few snippets from the code:

void loop() {

  if (can.receiveCANMessage(msg, 2) == 1)
  {
    switch (msg[0].data[4]) {
      case 1:
        enc_freq = 420;
        break;
      case 2:
        enc_freq = 4200;
        break;
      default:
        enc_freq = 42000;
    }
    enable_encoder(msg[0].data[3],enc_freq);
  }
}

This code within the loop just takes care of the CANBUS communication. All PWM channels have already been enable previously in the setup().

void enable_encoder(byte x, int y)
{  
  if (x == 1)
  {
    PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_MCK_DIV_2;    // The period is left aligned, clock source Mck/2 on channel 0
    PWM->PWM_CH_NUM[0].PWM_CPRD = y;                   // Channel i : Set the PWM frequency
    PWM->PWM_CH_NUM[0].PWM_CDTY = y/2;                   // Channel i : Set duty cycle

    PWM->PWM_CMP[0].PWM_CMPV = PWM_CMPV_CV(y/2); //Set comparison value
    PWM->PWM_CMP[0].PWM_CMPM = PWM_CMPM_CEN;      //enable comparison x
    PWM->PWM_ENA = PWM_ENA_CHID0;

    PWM->PWM_IER2 = PWM_IER2_CMPM0;
    NVIC_EnableIRQ(PWM_IRQn);

    
    /**********************************************************************************************
     TC6 interrupt to change duty cycle on flank A of the encoder 1
   **********************************************************************************************/

  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)ID_TC6);

  TC2->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA0 on RC compare match

  TC2->TC_CHANNEL[0].TC_RC = y;

  TC2->TC_CHANNEL[0].TC_IER = TC_IER_CPCS       // Interrupt on RC compare match
                              | TC_IER_CPAS;    // Interrupt on RA compare match
  TC2->TC_CHANNEL[0].TC_IDR = ~TC_IER_CPCS;

  NVIC_EnableIRQ(TC6_IRQn);                     // Interrupt enable

  TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC0 counter and enable

  /**********************************************************************************************
     TC7 interrupt to change the duty cycle on flank B of the encoder 1
   **********************************************************************************************/

  REG_PMC_PCER1 |= PMC_PCER1_PID34; //Enable peripheral clock for TC7

  REG_PIOC_ABSR |= PIO_ABSR_P28;     // TIOA7, P28, i.e. pin 3 Driven by TC7
  REG_PIOC_PDR |= PIO_PDR_P28;       //Disable GPIO

  REG_TC2_CMR1 = TC_CMR_ACPC_SET |                  // Set TIOA on counter match with RC0
                 TC_CMR_ACPA_CLEAR |                // Clear TIOA on counter match with RA0
                 TC_CMR_WAVE |                      // Enable wave mode
                 TC_CMR_WAVSEL_UP_RC |              // Count up with automatic trigger on RC compare
                 TC_CMR_TCCLKS_TIMER_CLOCK1;        // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 42MHz)

  REG_TC2_RC1 = y;                               // Load the RC1 register
  TC2->TC_CHANNEL[1].TC_IER = TC_IER_CPCS;       // Interrupt on RC compare match
  NVIC_EnableIRQ(TC7_IRQn);                     // Interrupt enable
  }

This is the code for the initialisation of TIMERS and PWM interrupts, and this function gets called by the CAN instruction.

void PWM_Handler() {

  REG_TC2_CCR1 = TC_CCR_SWTRG | TC_CCR_CLKEN;       // Enable the timer TC7
  PWM->PWM_ISR2;
  //Serial.println("interuppted");
  NVIC_DisableIRQ(PWM_IRQn);
}

void TC6_Handler()
{
  TC6_dutyA();
  TC_GetStatus(TC2, 0);
}

void TC7_Handler()
{
  TC7_dutyB();
  TC_GetStatus(TC2, 1);
}

PWM handler just enables the interrupt of TIMER7 (this is responsible for the 90-deg shifted flank B)
TC6 & TC7 Handler shift the PWM duty cycles.

Furthermore, I have attached all complete code as well. (except the code required for the CAN-shield, as that is not relevant)

OOP_Timer_PWM_4.ino (2.4 KB)

OOP_timer_PWM_define.h (9.59 KB)