Arduino Forum

Products => Arduino Due => Topic started by: jonnygainz on Jan 11, 2019, 04:02 pm

Title: Complementary PWM channels Output
Post by: jonnygainz on Jan 11, 2019, 04:02 pm
I'm using the following code to synchronize three channels and output a pair of complementary outputs per channel.

Code: [Select]
void setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID36; //Enable PWM (Power On)
 
  PWM->PWM_DIS = PWM_DIS_CHID0;       //Disable PWM on Channel 0
  //PWM->PWM_DIS = PWM_DIS_CHID1;

  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_PC3B_PWMH0 | PIO_PC5B_PWMH1 | PIO_PC7B_PWMH2; // 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_PC2B_PWML0 | PIO_PC4B_PWML1 | PIO_PC6B_PWML2; // Setting pins to Peripheral B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42); //Set PWM clock 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 | PWM_SCM_SYNC2; // Synchronizing of Channels 0, 1 and 2

  //PWM->PWM_CLK = PWM_CLK_PREA(1) | PWM_CLK_DIVA(42);
  //PWM->PWM_CH_NUM[1].PWM_CMR = PWM_CMR_CPRE_CLKA;

  REG_PWM_CPRD0 = 1000000; //Channel 0 Period f = 2MHz/(2*CPRD)
  REG_PWM_CDTY0 = 200000; //Channel 0 Duty Cycle x% = (CDTY/ CPRD)*100%
  REG_PWM_CPRD1 = 1000000;
  REG_PWM_CDTY1 = 1000000;
  REG_PWM_CPRD2 = 1000000;
  REG_PWM_CDTY2 = 0;
 
  PWM->PWM_ENA = PWM_ENA_CHID0; // Enable PWM on Channel 0
  //PWM->PWM_ENA = PWM_ENA_CHID1;

  //NVIC_EnableIRQ(TC0_IRQn); // enable TC0 interrupts
}

void loop() {
  // put your main code here, to run repeatedly:

}


When the duty cycle is set to 0%, 20%, 40%, 60%, 80% and 100% (0, 200000, 400000, 600000, 800000, 1000000) it outputs correctly. However any other value just gives me the output for 100% duty cycle. Can anyone explain this to me and offer some guidance on how to rectify this problem? 
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 12, 2019, 06:04 pm
Hi jonnygainz,

To generate synchronous, complementary PWM using method 1 (mode0): manual duty-cycle and trigger update, it's necessary to set the update unlock bit (PWM_SCUC_UPDULOCK) in the Sync Channels Update Control register (SCUC), to trigger once the duty-cycle registers have been changed:

Code: [Select]
PWM->PWM_SCUC = PWM_SCUC_UPDULOCK;      // Set the update unlock bit to trigger an update at the end of the next PWM period
Here's an example of using 3 synchronous, complementary channels on channel 0 (D34, D35), 1 (D36, D37) and 2 (D38, D39). The PWM is at 50Hz and switches between 25% and 75% duty-cycle every 1/2 second:

Code: [Select]
// Enable synchronous, complementary output, single-slope PWM at 50Hz on 3 channels (0 to 2), at 50% duty cycle
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 | PIO_ABSR_P5 | PIO_ABSR_P4 |        // Set the port C PWM pins to peripheral type B
                    PIO_ABSR_P3 | PIO_ABSR_P2;
  PIOC->PIO_PDR |= PIO_PDR_P7 | PIO_PDR_P6 | PIO_PDR_P5 | PIO_PDR_P4 |             // Set the port C PWM pins to outputs
                   PIO_PDR_P3 | PIO_PDR_P2;
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(84);                               // Set the PWM clock A rate to 1MHz (84MHz/84)
  PWM->PWM_SCM |= PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;                   // Set the PWM channels as synchronous                 
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;      // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CPRD = 19999;                 // Set the PWM frequency 1MHz/(19999 + 1) = 50Hz for all synchronous channels
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTY = 9999;                // Set the PWM duty cycle to 50%
  }
  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable all synchronous PWM channels
  PWM->PWM_SCUC = PWM_SCUC_UPDULOCK;      // Set the update unlock bit to trigger an update at the end of the next PWM period
}

void loop()
{
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (3 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTYUPD = 4999;             // Set the PWM duty cycle to 25%
  }
  PWM->PWM_SCUC = PWM_SCUC_UPDULOCK;      // Set the update unlock bit to trigger an update at the end of the next PWM period
  delay(500);
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (3 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTYUPD = 14999;            // Set the PWM duty cycle to 75%
  }
  PWM->PWM_SCUC = PWM_SCUC_UPDULOCK;      // Set the update unlock bit to trigger an update at the end of the next PWM period
  delay(500);
}

Also, the PWM controller's timers and duty-cycle registers (PWM_CDTY and PWM_CDTYUPD) are essentially only 16-bit, therefore it's not possible to load them with values over 65535 (2^16 - 1).
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 12, 2019, 08:47 pm
Thank you very much for the help, I'll let you know if I get through with what I'm doing.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 16, 2019, 12:56 pm
Is there a way to set the update unlock bit at the end of every period without using the delay function?  I'm working on a three phase inverter with variable frequency and voltage and the period i will be working with is too small for the delay function. I stayed away from mode 2 because i was having problems understanding how to configure the PDC DMA, can you explain to me how does it work? I'm reading through the datasheet and I'm having some difficulty understanding how it works.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 16, 2019, 01:18 pm
To automatically update the duty-cycle at the end of a timer period without having to set the update unlock bit, it's possible to use method 2 (mode1): manual duty-cycle update with automatic trigger.

The mode 1 bitfield is set in the Sync Channels Mode register:

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

Here's a similar example to the one above, but using mode 1 (and without the complementary outputs):

Code: [Select]
// Enable synchronous, complementary output, single-slope PWM at 50Hz on 3 channels (0 to 2), at 50% duty cycle
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_P6 | PIO_ABSR_P4 | PIO_ABSR_P2;                       // Set the port C PWM pins to peripheral type B               
  PIOC->PIO_PDR |= PIO_PDR_P6 | PIO_PDR_P4 | PIO_PDR_P2;                           // Set the port C PWM pins to outputs                 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(84);                               // Set the PWM clock A rate to 1MHz (84MHz/84)
  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_CMR = PWM_CMR_CPRE_CLKA;      // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CPRD = 19999;                 // Set the PWM frequency 1MHz/(19999 + 1) = 50Hz for all synchronous channels
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTY = 9999;                // Set the PWM duty cycle to 50%
  }
  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable all synchronous PWM channels
}

void loop()
{
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (3 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTYUPD = 4999;             // Set the PWM duty cycle to 25%
  }
  delay(500);
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (3 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTYUPD = 14999;            // Set the PWM duty cycle to 75%
  }
  delay(500);
}
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 16, 2019, 01:27 pm
I'm trying to update the duty cycle without using the delay function because in an inverter the time in which the duty cycle must be updated is very small and the delay function will not be able to accommodate that time. Also the frequency will have to be adjustable by an keypad or pot and hence cannot be a fixed value. Will the mode 2 help with this?
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 16, 2019, 01:30 pm
Regarding mode 2 operation using the PDC: automatic duty-cycle update and trigger, this mode is geared towards updating the duty-cycle each period (or for a specified number of periods) with a set of predefined duty-cycles held in memory.

Here's an example of mode 2 in operation, (based on ard_newbie's example: https://forum.arduino.cc/index.php?topic=530217.0 (https://forum.arduino.cc/index.php?topic=530217.0)):

Code: [Select]
// Enable synchronous, complementary output, single-slope PWM at 50Hz on 3 channels (0 to 2), at 50% duty cycle
uint16_t data[] = { 0, 4999, 14999, 4999, 4999, 14999, 9999, 4999, 14999, 14999, 4999, 14999, 19999, 4999, 14999 };

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_P6 | PIO_ABSR_P4 | PIO_ABSR_P2;                       // Set the port C PWM pins to peripheral type B               
  PIOC->PIO_PDR |= PIO_PDR_P6 | PIO_PDR_P4 | PIO_PDR_P2;                           // Set the port C PWM pins to outputs                 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(84);                               // Set the PWM clock A rate to 1MHz (84MHz/84)
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE2 |                                             // Automatically load the duty-cycle register with the PDC each timer cycle                   
                  PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;                   // Set the PWM channels as synchronous                 
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;      // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CPRD = 19999;                 // Set the PWM frequency 1MHz/(19999 + 1) = 50Hz for all synchronous channels
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTY = 9999;                // Set the PWM duty cycle to 50%
  }
  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_IER2 = PWM_IER2_WRDY;                       // Enable interrupt when Write Ready (WRDY) is set
 
  PWM->PWM_TPR = (uint32_t)data;                       // Set the address of the transmit data pointer
  PWM->PWM_TCR = 15;                                   // Set the length of the transmit data
  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 15;                                  // Set the next transmit counter
  PWM->PWM_PTCR |= PWM_PTCR_TXTEN;                     // Enable the Peripheral DMA Controller
  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable all synchronous PWM channels
}

void loop() {}

void PWM_Handler()
{
  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 15;                                  // Set the next transmit counter
  PWM->PWM_ISR2;                                       // Clear the interrupt status register 2
}

The duty-cycles are held in the data array and are stored { channel0, channel1, channel2, channel 0, channel 1, channel2, channel0, etc...

In this example the PWM interrupt handler simply reloads the next transmit data pointer (TNPR) and counter (TNCR) so that the duty-cycle pattern repeats indefinitely.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 16, 2019, 01:37 pm
Could you explain what this is in a little more detail for me? If it's not too much trouble?

Code: [Select]
  PWM->PWM_TPR = (uint32_t)data;                       // Set the address of the transmit data pointer
  PWM->PWM_TCR = 15;                                   // Set the length of the transmit data
  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 15;                                  // Set the next transmit counter
  PWM->PWM_PTCR |= PWM_PTCR_TXTEN;                     // Enable the Peripheral DMA Controller
  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable all synchronous PWM channels
}

void loop() {}

void PWM_Handler()
{
  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 15;                                  // Set the next transmit counter
  PWM->PWM_ISR2;                                       // Clear the interrupt status register 2
}
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 16, 2019, 01:47 pm
It's possible to change the period during operation using the period update register (PWM_CPRDUPD), followed by setting set the update unlock bit (PWM_SCUC_UPDULOCK) in the Sync Channels Update Control register (SCUC) :

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_CPRDUPD = 19999;
PWM->PWM_SCUC = PWM_SCUC_UPDULOCK;

If all the channels are synchronised, this will change the period for all the channels.

Quote
I'm trying to update the duty cycle without using the delay function because in an inverter the time in which the duty cycle must be updated is very small and the delay function will not be able to accommodate that time. Also the frequency will have to be adjustable by an keypad or pot and hence cannot be a fixed value. Will the mode 2 help with this?
Might I ask what you mean by using the delay() function?
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 16, 2019, 02:01 pm
See in #4 how you used the delay(500) to update the duty cycle I can't use that because after a while it will not be able to accommodate the times. I am making a three phase space vector modulated inverter and every period i will have to calculate new duty cycles to load into each channel. Depending on the supply frequency you want the period in which these duty cycle calues must be calculated will be extremely small. If you want to find out more detail about how this works you can take a look at this page:

https://hvdc.ca/webhelp/Master_Library_Models/HVDC_and_FACTS/Space_Vector_Modulation/SVM_Theory.htm
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 16, 2019, 02:09 pm
Quote
Could you explain what this is in a little more detail for me? If it's not too much trouble?
The PDC has a transmit data pointer (TDP), this is a register that contains the address or location of the first data item in the data array, in this example it's the address of the data array: data or &data[0]. The PDC also has a transmit counter (TCR), a 16-bit counter that initially contains the number of data items in the array, in our case 15.

Each PWM timer period, the PRC copies the data from the array into PWM timer's PWM_CDTYUPD register and decrements the counter by 1. When the counter reaches zero the PDC checks the next transmit data pointer (TNPR) and (TNCR) registers. If the TNCR is zero the transfer is terminated. If however the TNPR and TNCR contain new data, it's copied to the TCP and TCR registers and the TNPR and TNCR are set to zero.

To keep the PWM timer cycling around the array indefinitely, (should this be a requirement), it's necessary to use the PWM Controller's interrupt service routine (ISR): PWM_Handler() function, to reload the PDC's next registers oncemore.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 16, 2019, 02:14 pm
It's possible to just remove the delay() functions and call on the PWM timer to output whatever duty-cycle and period you require, whenever you need it. The PWM timer will output the new waveform at the beginning of the next cycle.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 16, 2019, 02:18 pm
I should say that there are two things I'm working on the first is generating three phase quasi-square waves using an inverter circuit and then implementing Space Vector Modulation to produce three phase sinusoidal waves from the quasi-square waves. For the quasi-square waves, only 6 circuit configurations are required in a specific order for a fixed period for duty cycles of either 0% or 100%. Space Vector Modulation however is much more complex than that.

Programming has never been my strong point, and working with this Due is a little difficult for me, but it is much more capable to do what i need it to do than the rest of the boards offered by arduino.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 21, 2019, 02:13 pm
@MartinL is there a way to do what you did using the data array "uint16_t data[]" but with a switch case or if statements using a counter? So if i want to update the duty cycle for the following code using  a switch case or if statements:

This is what I'm working with:
Code: [Select]
// Enable synchronous, complementary output, single-slope PWM at 5Hz on 3 channels (0 to 2), at varying duty cycle
uint16_t data[] = { 65535, 0, 0, 65535, 65535, 0, 0, 65535, 0, 0, 65535, 65535, 0, 0, 65535, 65535, 0, 65535};



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_P6 | PIO_ABSR_P4 | PIO_ABSR_P2;                       // Set the port C PWM pins to peripheral type B 
  PIOC->PIO_ABSR |= PIO_ABSR_P7 | PIO_ABSR_P5 | PIO_ABSR_P3;               
  PIOC->PIO_PDR |= PIO_PDR_P6 | PIO_PDR_P4 | PIO_PDR_P2;                           // Set the port C PWM pins to outputs
  PIOC->PIO_PDR |= PIO_PDR_P7 | PIO_PDR_P5 | PIO_PDR_P3;   
             
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(255);                               // Set the PWM clock A rate to 1MHz (84MHz/255)
  PWM->PWM_SCM |= PWM_SCM_UPDM_MODE2 |                                             // Automatically load the duty-cycle register with the PDC each timer cycle                   
                  PWM_SCM_SYNC2 | PWM_SCM_SYNC1 | PWM_SCM_SYNC0;                   // Set the PWM channels as synchronous                 
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKA;      // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[0].PWM_CPRD = 65535;                 // Set the PWM frequency 329411MHz/(65536) = 5Hz for all synchronous channels
  for (uint8_t i = 0; i < 3; i++)                      // Loop for each PWM channel (8 in total)
  {
    PWM->PWM_CH_NUM[i].PWM_CDTY = 0;                   // Set the PWM duty cycle to 50%
  }
  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_IER2 = PWM_IER2_WRDY;                       // Enable interrupt when Write Ready (WRDY) is set
 
  PWM->PWM_TPR = (uint32_t)data;                       // Set the address of the transmit data pointer
  PWM->PWM_TCR = 18;                                   // Set the length of the transmit data
  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 18;                                  // Set the next transmit counter
  PWM->PWM_PTCR |= PWM_PTCR_TXTEN;                     // Enable the Peripheral DMA Controller
  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable all synchronous PWM channels

}

void loop() {}

void PWM_Handler()
{

  PWM->PWM_TNPR = (uint32_t)data;                      // Set the next transmit data pointer
  PWM->PWM_TNCR = 18;                                  // Set the next transmit counter
  PWM->PWM_ISR2;                                       // Clear the interrupt status register 2
 
}


Instead of a data array i would like to use a switch case or if statements to change the duty cycle values. The duty cycle must change every period in the same pattern seen in the data array. Something like this:

Code: [Select]

int count = 0;

switch(count)
{
     case 1:
     //Update duty cycle
     break;
     
     case 2;
     //Update duty cycle
     break;
     //......
     case 6;
     //Update Duty cycle
     break;

}






Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 21, 2019, 03:11 pm
It depends what you mean by "update duty cycle" in your switch statement and whether the vectors are updated on a per pattern or per PWM period basis.

If you mean change the vector sequencing to a different pattern, then it's possible to provide the Transmit Next Data Pointer (PWM->PWM_TNPR) register with a new pointer to an array pattern, say data2[]. At end of the current pattern a new pattern will be loaded and output.

If however you mean change the vector on a period by period basis, then it's probably better to go back to synchronous mode 0 or 1 and set the duty-cycle update registers in the PWM Controller's interrupt service routine manually.

This requires the interrupt service routine to be called at the end of each PWM period, by enabling the counter event on channel 0 interrupt:

Code: [Select]
PWM->PWM_IER1 = PWM_IER1_CHID0;                        // Enable interrupt on PWM channel 0 triggered at end of PWM period
Then implementing the switch statement to change the duty-cycle update register for each channel (PWM->CH_NUM[X].PWM_CDYUPD) within the PWM_Handler() function.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 27, 2019, 11:43 am
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
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 27, 2019, 06:49 pm
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;
}
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Jan 29, 2019, 06:59 pm
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.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Jan 29, 2019, 08:04 pm
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
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 06, 2019, 06:44 pm
 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.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 06, 2019, 07:32 pm
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;
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 11, 2019, 04:01 pm
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.

Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 11, 2019, 05:12 pm
I just want to get a dead time of about 1 micro second
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 11, 2019, 06:08 pm
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.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 11, 2019, 10:58 pm
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 (https://forum.arduino.cc/index.php?topic=573751.0).
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 03:55 am
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.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 12, 2019, 09:54 am
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:

(https://forum.arduino.cc/index.php?action=dlattach;topic=590202.0;attach=294400)

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.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 04:06 pm
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.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 12, 2019, 04:56 pm
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);
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 05:04 pm
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
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 12, 2019, 05:18 pm
Quote
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.
I've now corrected it above, it's

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_DTUPD = PWM_DTUPD_DTHUPD(0) | PWM_DTUPD_DTLUPD(2);
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 12, 2019, 05:27 pm
Are your looking at the low side channel outputs, PWMLx, on your scope?
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 05:29 pm
I'm looking at both low side and high side for each channel in turn to compare.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 05:41 pm
I tried the code in #30 and still nothing. This is my code:

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;

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();
  /*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(200);
  //PWM->PWM_CH_NUM[1].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(200);
  //PWM->PWM_CH_NUM[2].PWM_DT = PWM_DT_DTH(0)| PWM_DT_DTL(200);
 
  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 ()
{
  //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_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            PWM->PWM_CH_NUM[0].PWM_CDTYUPD = p;
            PWM->PWM_CH_NUM[1].PWM_CDTYUPD = n;
            PWM->PWM_CH_NUM[2].PWM_CDTYUPD = n;
          break;
          case 1: //ppn = 110 => 012
            PWM->PWM_CH_NUM[0].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            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_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            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_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            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_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            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_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[1].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
            PWM->PWM_CH_NUM[2].PWM_DTUPD = PWM_DTUPD_DTHUPD(0)| PWM_DTUPD_DTLUPD(2);
           
            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;


    }
}
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 12, 2019, 08:16 pm
Could it be that the UPDULOCK bit must be set in order to allow the dead time registers to be updated?
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 13, 2019, 01:37 pm
Any luck? I've tried everything I can think of, and it's not working.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 28, 2019, 04:43 am
MartinL, If I would like to set the period of the pwm from a function, but the function takes input from a keypad and then uses the value inputted by the keypad to choose a case in a switch, how would be the best way to go about doing that?
Title: Re: Complementary PWM channels Output
Post by: MartinL on Feb 28, 2019, 09:51 am
Hi,

To change the period of the signals just requires you to change the period PER registers for channel 0:

Code: [Select]
PWM->PWM_CH_NUM[0].PWM_CPRD = CPRDV;
As your PWM signals span across a number of PWM cycles, I imagine it would be easiest to place to insert this line is in your update_Duty_Cycle() function, specifically in the one of the 6 output states. This also ensures that any changes happen at the beginning of your state output cycle.

To select differing periods your keypad could set a variable either containing the period or that can be used to select a preset period in the update_Duty_Cycle() function. As the update_Duty_Cycle() function is called from within the PWM_Handler(), but set by the keypad from outside it, it will be necessary to declare the variable as "volatile":

Code: [Select]
volatile unsigned long periodValue;
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 28, 2019, 10:26 am
So if I want to be able to set the period value before the program even starts with the first duty cycle value I should do something like at the top of my handler

Code: [Select]


if (x <1)
{
      setPeriod();
      x++
}

 

 And in this set period function I include the code that would allow the keypad to set the value of the period? Adding it to the top of the handler ensures that it is executed before any switching starts
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 28, 2019, 10:52 am
If it's done this way I'm guessing it will remain in the function as long as the person is setting the period value. But after that it'll never enter that loop again because x would be equal to 1. What you think?
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 28, 2019, 10:16 pm
Is there a way to set the period of the pwm before the program starts using they keypad? I don't want the pwm to start until I've set the period. See because it's an inverter I'm designing I need to input the frequency value before the PWM starts. It needs to be inputted from the keypad. I also need to display the supply frequency value during the operation of the inverter.

Is it possible to call a function in setup to set parameters for PWM like say period? I want to set parameters in the program using a keypad and LCD display where the LCD is prompting you to input a voltage, and based on this voltage value a frequency will be calculated for constant v/f control of an induction motor. this frequency value will then calculate a CPRD value and then load that into the CPRD register of the Due before the setup is exited. So basically I want to know if I set up the keypad and LCD in the setup before i enable the Channel counter and exit the step up, if i call a function that utilizes the keypad and LCD after they are initialized in the setup if I will be able to use the keypad and LCD.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Feb 28, 2019, 10:31 pm
Is it possible to enable the pwm on a channel outside of the setup() function?

Code: [Select]
PWM->PWM_ENA = PWM_ENA_CHID0;

In essence call this piece of code after the period values have been set in the function.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Mar 01, 2019, 09:33 am
Quote
Is it possible to enable the pwm on a channel outside of the setup() function?
Yes it is. However, if you disable the PWM Controller peripheral, (without switching the pins back to GPIO), they'll go high impedance and start to float. In might be better keep the PWM Controller enabled and to have a seventh "reset" state that sets the duty-cycle of all the outputs to 0.

Quote
Is it possible to call a function in setup to set parameters for PWM like say period?
Yes, it's possible to run any code in the setup() portion of the sketch, but it will only be run once. If your user only needs enter the code at the beginning, then the keypad input can be done at this stage. This means that you only need to set the PWM Controller's period (CPRD) register at the start. Also, in this instance there's no need to use a "volatile unsigned long" global variable or modify the update_Duty_Cycle() function.

Regarding the code pattern, I'd do something along these lines:

Code: [Select]
// ... Global variables

void setup() {
  // ...
  // ... Read keypad input
  switch(keypadInput)
  {
    case 0:
      PWM->PWM_CH_NUM[0].PWM_CPRD = 1000;      // Arbitary values
      break;
    case 1:
      PWM->PWM_CH_NUM[0].PWM_CPRD = 2000; 
      break;
    case 2:
      PWM->PWM_CH_NUM[0].PWM_CPRD = 3000;
      break;
    case 3:
      // ...
  }
  // ... continue with setup() ...
}

void loop() {
  // ... Main code loop 
}

void PWM_Handler() {
  // ...
  update_Duty_Cycle();
  // ...
}

void update_DutyCycle() {
  // ...
}
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Mar 01, 2019, 10:04 am
How would the read keypad part of the code look if I'm using "Keypad.h" because I tried to set it in the setup but it didn't work.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Mar 01, 2019, 10:43 am
I need the code to be written such that it won't leave the function unless it receives an input from the keypad to set the values.
Title: Re: Complementary PWM channels Output
Post by: MartinL on Mar 01, 2019, 01:42 pm
Quote
How would the read keypad part of the code look if I'm using "Keypad.h" because I tried to set it in the setup but it didn't work.
How the keypad code works is very much dependent on your keypad hardware.
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Mar 01, 2019, 01:47 pm
It's just a 4x4 keypad with numbers 0-9, letters A-D , * and #. The LCD display is a 1602 2 line display connected via I2C backpack on the 5V and SCL and CDA lines. And I'm using the Keypad library. Essentially what I need the function to do is wait for the input from the keypad and then set 4 global variables in the program based on which case I'm in. The case will have information such as inverter frequency, phase voltage value, modulation index and most important the period value. But this must happen in the setup
Title: Re: Complementary PWM channels Output
Post by: jonnygainz on Mar 01, 2019, 02:24 pm
The rows and columns of the keypad are connected through pins 2-9