Go Down

Topic: Complementary PWM channels Output (Read 1 time) previous topic - next topic

jonnygainz

How should I write the ISR routine if i want to use the Switch Case to change the duty cycle? I tried doing it but it doesn't work.

Code: [Select]
byte count = 0;

void setup()
{
  Serial.begin(57600);
 
  PMC->PMC_PCER1 |= PMC_PCER1_PID36; //Enable PWM (Power On)
 
  PWM->PWM_DIS = PWM_DIS_CHID0;       //Disable PWM on Channel 0

  PIOC->PIO_PDR |= PIO_PDR_P3 | PIO_PDR_P5; // Setting pins 3,5, (DUE Pins 35, 37, 39) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_PC3B_PWMH0 | PIO_PC5B_PWMH1; // Setting pins to Peripheral B

  PIOC->PIO_PDR |= PIO_PDR_P2 | PIO_PDR_P4; // Setting pins 2,4, (DUE Pins 34, 36, 38) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_PC2B_PWML0 | PIO_PC4B_PWML1; // Setting pins to Peripheral B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42); //Set PWM clocke rate to 2MHz (84MHz/42)
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA; // Period is left aligned,clock source is CLKA on Channel 0

  REG_PWM_SCM |= PWM_SCM_SYNC0 | PWM_SCM_SYNC1; // Synchronizing of Channels 0, 1 and 2

  REG_PWM_SCM |= PWM_SCM_UPDM_MODE1;  // Manual Write of duty-cycle automatic trigger of the update

  PWM->PWM_SCUP = PWM_SCUP_UPR(0); // Defining update period (UPR + 1)

  PWM->PWM_IER2 = PWM_IER2_WRDY; // Enable Interrupt when WRDY flag is set

  //PWM_SCM_PTRM == 0;; // The WRDY flag in PWM_ISR2 and the PDC transfer request are set to 1 as soon as the update period is elapsed.


  REG_PWM_CPRD0 = 1000000; //Channel 0 Period f = 2MHz/(2*CPRD)
  //REG_PWM_CDTY0 = 0; //Channel 0 Duty Cycle x% = (CDTY/ CPRD)*100%
  REG_PWM_CPRD1 = 1000000;
  //REG_PWM_CDTY1 = 1000000;

  PWM->PWM_ENA = PWM_ENA_CHID0; // Enable PWM on Channel 0

  update_Duty_Cycle();

  //NVIC_EnableIRQ(PWM_IRQn); // enable PWM interrupts


}

void loop()
{
  // put your main code here, to run repeatedly:
   // Serial.print("count = ");
   // Serial.print(count);
  //  Serial.print(" ");
}


void PWM_Interrupt_Handler()
{
  PWM->PWM_ISR2;      // Read ISR2 and clear status register
  //update_Duty_Cycle();
 
}

void update_Duty_Cycle()
{
  if(count > 3){
    count = 0;}
    switch(count)
    {
      case 0:
      REG_PWM_CDTYUPD0 = 1000000;
      REG_PWM_CDTYUPD1 = 0;
      break;
      case 1:
      REG_PWM_CDTYUPD1 = 1000000;
      break;
      case 2:
      REG_PWM_CDTYUPD0 = 0;
      break;
      case 3:
      REG_PWM_CDTYUPD1 = 0;
      break;
      default:
      break;
    }
    //SquareWave(count);
    //count++;
}

/*
void SquareWave(byte Sector)
{
    switch(Sector)
    {
      case 0:
      REG_PWM_CDTYUPD0 = 1000000;
      REG_PWM_CDTYUPD1 = 0;
      break;
      case 1:
      REG_PWM_CDTYUPD1 = 1000000;
      break;
      case 2:
      REG_PWM_CDTYUPD0 = 0;
      break;
      case 3:
      REG_PWM_CDTYUPD1 = 0;
      break;
      default:
      break;
    }
}
*/


This is what I tried to do, I know some of the configuration is wrong but this is essentially what I want to do

MartinL

#16
Jan 27, 2019, 06:49 pm Last Edit: Jan 27, 2019, 07:02 pm by MartinL
Here's a working example based on your code, using the two complementary channels: 0 (D34, D35) and 1 (D36, D37):

Code: [Select]
void setup()
{
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                  // Enable PWM (Power On)

  PIOC->PIO_PDR |= PIO_PDR_P3 | PIO_PDR_P5;           // Setting pins 3,5, (DUE Pins 35, 37, 39) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_ABSR_P3 | PIO_ABSR_P5;        // Setting pins to Peripheral B

  PIOC->PIO_PDR |= PIO_PDR_P2 | PIO_PDR_P4;           // Setting pins 2,4, (DUE Pins 34, 36, 38) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_ABSR_P2 | PIO_ABSR_P4;        // Setting pins to Peripheral B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42);  // Set PWM clocke rate to 2MHz (84MHz/42)
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;     // Period is left aligned,clock source is CLKA on Channel 0

  PWM->PWM_SCM |= PWM_SCM_SYNC0 | PWM_SCM_SYNC1;      // Synchronizing of Channels 0, 1 and 2
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE1;                 // Manual Write of duty-cycle automatic trigger of the update
  //PWM->PWM_SCUP = PWM_SCUP_UPR(0);                    // Defining update period (UPR + 1)

  NVIC_SetPriority(PWM_IRQn, 0);                      // Set the Nested Vector Interrupt Controller (NVIC) priority for the PWM controller to 0 (highest)
  NVIC_EnableIRQ(PWM_IRQn);                           // Connect PWM Controller to Nested Vector Interrupt Controller (NVIC)

  PWM->PWM_IER1 = PWM_IER1_CHID0;                     // Enable interrupt on PWM channel 0 triggered at end of PWM period

  PWM->PWM_CH_NUM[0].PWM_CPRD = 10000;                // Channel 0 Period f = 2MHz/(2*CPRD)
  PWM->PWM_CH_NUM[0].PWM_CDTY = 0;                    // Channel 0 duty-cycle at 0%
  PWM->PWM_CH_NUM[1].PWM_CDTY = 0;                    // Channel 1 duty-cycle at 0%

  PWM->PWM_ENA = PWM_ENA_CHID0;                       // Enable synchronous PWM on Channel 0
}

void loop(){}

void PWM_Handler()                       // PWM Interrupt Service Routine (ISR)
{
  if (PWM->PWM_ISR1 & PWM_ISR1_CHID0)    // Check if an update condition has occured
  {       
    update_Duty_Cycle();                 // Update the duty cycles
  }
}

void update_Duty_Cycle()
{
  static byte count = 0;
 
  switch(count)
  {
    case 0:
      PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 10000;
      PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 0;
    break;
    case 1:
      PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 10000;
    break;
    case 2:
      PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 0;
    break;
    case 3:
      PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 0;
    break;
    default:
    break;
  }
  count = (count + 1) % 4;
}

jonnygainz

Thank you very much, you have been very helpful to me thus far, I would like to also find out how to add dead times to the code in #16.

MartinL

#18
Jan 29, 2019, 08:04 pm Last Edit: Jan 29, 2019, 08:08 pm by MartinL
To insert dead-times there's the dead-time (PWM->PWM_CH_NUM[X].PWM_DT) and the dead-time update (PWM->PWM_CH_NUM[X].PWM_DTUPD) registers.

Like the duty-cycle and period registers, the DT register takes effect immediately at the PWM output, while changes to the DTUPD register occur on the next timer cycle.

The DT and DTUPD are both 32-bit registers and contain two 16-bit bitfields. The upper 16-bits are for the PWMLx output and are named DTL/DTLUPD, while the lower 16-bits are for the PWMHx output and are named DTH/DTHUPD. Although each bitfield contains 16-bits, only the lower 12-bits in each bitfield are significant (valid), a value between 0 and 4095.

To insert dead-time on channel 0's PWML0 output for example:

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(167);    // Set the PWML0 output dead-time

jonnygainz

 How do you enable the dead time? I see in the datasheet you need to use a dead time enable bit to get the dead-time working.

MartinL

#20
Feb 06, 2019, 07:32 pm Last Edit: Feb 06, 2019, 07:32 pm by MartinL
Sorry, I noticed that I omitted the dead-time enable bit in my description above. You just need to set the DTE bit in the Due's Channel Mode Register (CMRx) for the given PWM channel, for example for channel 0:

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;

jonnygainz

#21
Feb 11, 2019, 04:01 pm Last Edit: Feb 12, 2019, 12:42 pm by jonnygainz Reason: Removing code for my final year project
How does the dead time operate for synchronous channels? I'm putting in dead times and it significantly reducing my period value. When using in synchronous channels, do you have to just enable it on channel 0 and use channel 0's clock? or you have to enable for each channel and use the clock for each channel? my period value is supposed to be 0.002777778, but i'm getting 0.001933333 when I insert dead times. Even with '0' in the DTH and DTL I'm still getting 0.001933333.


jonnygainz

I just want to get a dead time of about 1 micro second

jonnygainz

When the DTE bit is disabled, I'm getting the correct period 0.002777778s, but when DTE is enabled, regardless of what I put in the DTH and DTL registers, I'm still getting a period of 0.0019333333s. Both complementary outputs look exactly the same on the oscilloscope also, one isn't slightly bigger and the other and both start and end on the exact same time intervals.

MartinL

Dead-time insertion doesn't actually change the period of the complementary waveforms, it simply delays the waveforms' edge with respect to the beginning of the timer cycle.

If the duty-cycle (and period) of the waveform remains unchanged then inserting a delay will shorten the pulse width. To maintain the pulse width it's necessary to add the delay time to the duty-cycle register, in order to extend it oncemore, however this can only be achieved so long as the duty-cycle does not exceed the period.

An example of using dead-time insertion is given in post number #10 here: https://forum.arduino.cc/index.php?topic=573751.0.

jonnygainz

This example shows how it works for 1 channel, how does dead time work for three synchronized channels? like how I have CH0, CH1 and CH2 synchronized.

MartinL

#26
Feb 12, 2019, 09:54 am Last Edit: Feb 12, 2019, 10:19 am by MartinL
Quote
This example shows how it works for 1 channel, how does dead time work for three synchronized channels? like how I have CH0, CH1 and CH2 synchronized.
You just have to set the dead time enable bit for each channel:

Code: [Select]
PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_DTE;                         // Enable channel 1 dead-time insertion
Here's an example with 3 pairs of complementary and synchronous PWM outputs at 100kHz:

Code: [Select]
// Enable synchronous, complementary output, single-slope PWM at 100kHz on 3 channels (0 to 2), at 75% duty cycle
// with various dead-times for each channel
void setup() {
  // Set-up 6 PWM outputs on 3 complementary channels on on pins D34, D35, D36, D37, D38, and D39 for channels 0 through to 2 respectively
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                                // Enable PWM   
  PIOC->PIO_ABSR |= PIO_ABSR_P7 | PIO_ABSR_P6 |                     // Set the port C PWM pins to peripheral type B
                    PIO_ABSR_P5 | PIO_ABSR_P4 |
                    PIO_ABSR_P3 | PIO_ABSR_P2;
  PIOC->PIO_PDR |= PIO_PDR_P7 | PIO_PDR_P6 |                        // Set the port C PWM pins to outputs
                   PIO_PDR_P5 | PIO_PDR_P4 |
                   PIO_PDR_P3 | PIO_PDR_P2;                 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);                 // Set the PWM clock A rate to 84MHz (84MHz/1)
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE1 |                              // Automatically update the duty-cycle register each timer cycle                   
                  PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;    // Set the PWM channels as synchronous                 
  PWM->PWM_CH_NUM[0].PWM_CPRD = 839;                                // Set the PWM frequency to 100kHz (84MHz/(839 + 1)) for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;     // Enable channel 0 dead-time insertion and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_DTE;                         // Enable channel 1 dead-time insertion
  PWM->PWM_CH_NUM[2].PWM_CMR = PWM_CMR_DTE;                         // Enable channel 2 dead-time insertion
  PWM->PWM_CH_NUM[0].PWM_CDTY = 630;                                // Set channel 0 PWM duty cycle to 75%
  PWM->PWM_CH_NUM[1].PWM_CDTY = 630;                                // Set channel 1 PWM duty cycle to 75%
  PWM->PWM_CH_NUM[2].PWM_CDTY = 630;                                // Set channel 2 PWM duty cycle to 75%
  PWM->PWM_CH_NUM[0].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(0);        // Set channel dead-time for a 0us delay on output PWML0
  PWM->PWM_CH_NUM[1].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(105);      // Set channel dead-time for a 1.25us delay on output PWML1
  PWM->PWM_CH_NUM[2].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(210);      // Set channel dead-time for a 2.5us delay on output PWML2
  PWM->PWM_ENA = PWM_ENA_CHID0;                                     // Enable all synchronous PWM channels
}

void loop() {}

Here's the output top to bottom of channels PWML0, PWML1, PWML2 and PWMH0 respectively:



PWML0 (yellow) and its complement PWMH0 (dark blue) have no dead-time insertion, PWML1 (light blue) has 1.25us of dead-time insertion, while PWML2 (pink) has 2.5us.

jonnygainz

How do I update the dead time every period, because I tried what you showed me in #26 but the dead times aren't being inserted. I'm still just getting both complementary wave-forms being exactly the same.

Code: [Select]
/* This code generates Quasi-Square Waves at 60Hz*/
static byte stateCount = 0;
static float CPRDV = 0;
static float inverterFrequency = 60;
int divA = 42;      // Set DIVA

void setup()
{
  Serial.begin(57600);
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                  // Enable PWM (Power On)

  PIOC->PIO_PDR |= PIO_PDR_P3 | PIO_PDR_P5 | PIO_PDR_P7;           // Setting pins 3,5,7 (DUE Pins 35, 37, 39) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_ABSR_P3 | PIO_ABSR_P5 | PIO_ABSR_P7;       // Setting pins to Peripheral B

  PIOC->PIO_PDR |= PIO_PDR_P2 | PIO_PDR_P4 | PIO_PDR_P6;           // Setting pins 2,4,6 (DUE Pins 34, 36, 38) to PWM Peripheral, not GPIO
  PIOC->PIO_ABSR |= PIO_ABSR_P2 | PIO_ABSR_P4 | PIO_ABSR_P6;       // Setting pins to Peripheral B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(divA);  // Set PWM clocke rate to 2MHz (84MHz/42)
  PWM->PWM_SCM |= PWM_SCM_SYNC0 | PWM_SCM_SYNC1 | PWM_SCM_SYNC2;      // Synchronizing of Channels 0, 1 and 2
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE1;                 // Manual Write of duty-cycle automatic trigger of the update
 
  NVIC_SetPriority(PWM_IRQn, 0);                      // Set the Nested Vector Interrupt Controller (NVIC) priority for the PWM controller to 0 (highest)
  NVIC_EnableIRQ(PWM_IRQn);                           // Connect PWM Controller to Nested Vector Interrupt Controller (NVIC)

  PWM->PWM_IER1 = PWM_IER1_CHID0;                     // Enable interrupt on PWM channel 0 triggered at end of PWM period

  calcCPRDV();  // CPRD is calculated
  /*Serial.print("CPRDV = ");
  Serial.print(CPRDV);
  Serial.print("\n");*/
  PWM->PWM_CH_NUM[0].PWM_CPRD = CPRDV;                // Channel 0 Period f = 2MHz/(CPRD)= 100Hz = 0.01s |CPRD = 2MHz/f
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA | PWM_CMR_DTE;     // Period is left aligned,clock source is CLKA on Channel 0
  PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_DTE;
  PWM->PWM_CH_NUM[2].PWM_CMR = PWM_CMR_DTE;

  PWM->PWM_CH_NUM[0].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(2);   
  PWM->PWM_CH_NUM[1].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(2); // Dead time of 1us [DT = (1/2778)*5555 = 2
  PWM->PWM_CH_NUM[2].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(2);
 
  PWM->PWM_ENA = PWM_ENA_CHID0;                       // Enable synchronous PWM on Channel 0
}

void loop()
{
 
}

void PWM_Handler()                       // PWM Interrupt Service Routine (ISR)
{
  if (PWM->PWM_ISR1 & PWM_ISR1_CHID0)    // Check if an update condition has occured
  {       
    update_Duty_Cycle();                 // Update the duty cycles
  }
}

void calcCPRDV () // Used to calculate the CPRD value
{
  //inverterFrequency = 60;
  static float numOfVectors = 6;
  static float adMCKFreq = 84000000;
  static float timerFreq = (adMCKFreq/divA); //2MHz
  static float period = 1/inverterFrequency;
  static float Ts = period/numOfVectors;
  static float switchingFreq = 1/Ts;
  CPRDV = (timerFreq/(1*switchingFreq));
  /*Serial.print("Inverter Frequency = "); Serial.print(inverterFrequency);Serial.print("\n");
  Serial.print("Num of Vectors = "); Serial.print(numOfVectors); Serial.print("\n");
  Serial.print("Timer Freq = "); Serial.print(timerFreq); Serial.print("\n");
  Serial.print("Period = "); Serial.print(period, 8); Serial.print("\n");
  Serial.print("SSV Period = "); Serial.print(Ts, 8); Serial.print("\n");
  Serial.print("Switching Freq = "); Serial.print(switchingFreq); Serial.print("\n");*/

}

void update_Duty_Cycle()
{
 
 
    if(stateCount < 6)
    {
          PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 0;
          PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 0;
          PWM->PWM_CH_NUM[2].PWM_CDTYUPD = 0;
          stateCount++;
          /*Serial.print("Counter = ");
          Serial.print(stateCount); Serial.print("\n");*/
    }
    else
    {
 
      static byte count = 0;
      static float p = CPRDV;
      static float n = 0;
      //Serial.print("Sector = "); Serial.print(count); Serial.print("\n");
      switch(count)
        {
          case 0: //pnn = 100 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = n;
           
            PWM->PWM_CH_NUM[0].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(20);
            PWM->PWM_CH_NUM[1].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(20);
            PWM->PWM_CH_NUM[2].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(20);
          break;
          case 1: //ppn = 110 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = n;
          break;
          case 2: //npn = 010 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = n;
          break;
          case 3: //npp = 011 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = p;
          break;
          case 4: //nnp = 001 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = p;
          break;
          case 5: //pnp = 101 => 012
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = p;
          break;
          default:
          break;
        }
        count = (count + 1) % 6;


    }
}


I just want a deadtime of 1us, my CPRD value is 5555 and my period is 2778us, therefore DT=(1/2778)*55555 = 1.999 = 2. When i look at it on an oscilloscope I'm not seeing the dead time. So I'm thinking that I need to update the dead time every period for them to show up.

MartinL

#28
Feb 12, 2019, 04:56 pm Last Edit: Feb 12, 2019, 05:17 pm by MartinL
Have you zoomed into (with the time base) and compared the leading edges of the synchronised PWM waveforms on your scope? A 1us (1/2MHz * 2) dead-time is hardly anything in comparison to the 2778us period.

During operation it's also possible to use the dead-time insertion update registers. These update the dead-time at the beginning of the next timer cycle, in a similar fashion to the duty-cycle update registers:

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_DTUPD = PWM_DTUPD_DTHUPD(0) | PWM_DTUPD_DTLUPD(2);

jonnygainz

#29
Feb 12, 2019, 05:04 pm Last Edit: Feb 12, 2019, 05:10 pm by jonnygainz
When I try using the code in #28, the IDE tells me "PWM_DT_DTHUPD" and "PWM_DT_DTLUPD" was not declared in this scope.

An yes I zoomed in on the oscilloscope to see if I was getting the dead times, I went as far as 5us and still wasn't seeing the dead times

Go Up