Go Down

Topic: How do I make a PWM signal run once only. (Read 3061 times) previous topic - next topic

new_to_due

Hi

I have code running a PWM signal synchronously on 2 channels and for the time frames i require. However,  I need to use the signal to open an close switches so only want the signal to run once. At the moment the signal just continuously repeats.

The code is below. I've been testing it by entering a number on the serial monitor to jump to a function switching the PWM channels on. Ive tried implementing it and then disabling the channels but i get no signal at all. Same thing happens if I set all the duty cycles back 0.

Any help would be greatly appreciated!!  :)


Code: [Select]

int doSwitch = 0;
void setup() {
 
 /*************** PWM set-up on pins  D7 and D6 for channels 6 & 7 respectively*************************************/
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                                               // Enable PWM
 
 
  PIOC->PIO_ABSR |= PIO_ABSR_P24 | PIO_ABSR_P23;                                    // Set the port C PWM pins to peripheral type B
 

  PIOC->PIO_PDR |= PIO_PDR_P24 | PIO_PDR_P23;                                       // Set the port C PWM pins to outputs
 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(16);                                // Set the PWM clock A rate to 5.25MHz (84MHz/16)
 
  PWM->PWM_SCM |= PWM_SCM_SYNC7 | PWM_SCM_SYNC6 | 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 = 37500;                                              // Set the PWM frequency 5.25MHz/(37500) = 140Hz for all synchronous channels
 
 
Serial.begin(9600);
}

void loop() {
Serial.println("press a number");
delay(200);
if (Serial.available() > 0)
{
doSwitch = Serial.read();
}
if (doSwitch > 0){
  Serial.print("Switching");
  doSwitches();
  doSwitch = 0;
}

}
/*******************FUNCTIONS****************************************/

void doSwitches(){
 
  PWM->PWM_CH_NUM[0].PWM_CDTY = 315;                                               // Set the PWM duty cycle to 0.84%
  PWM->PWM_CH_NUM[6].PWM_CDTY = 315;
  PWM->PWM_CH_NUM[7].PWM_CDTY = 315;   
 
 
  PWM->PWM_CH_NUM[7].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;                   // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[7].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(630);                    // Set the low and high dead-time for 120us delay (140Hz => 120us period)
  PWM->PWM_CH_NUM[7].PWM_CDTY = 3150 + 630;                                       // Set the PWM duty cycle to 8.4%
  PWM->PWM_ENA = PWM_ENA_CHID7 | PWM_ENA_CHID6 | PWM_ENA_CHID0;                   // Enable all PWM channels10
  // PWM->PWM_DIS = PWM_DIS_CHID7 | PWM_DIS_CHID6 | PWM_DIS_CHID0;
   
}

MartinL

Here's a link to some code that uses the PWM controller's interrupt service routine to generate 8 pulses, however it could be easily modified to output a single pulse: http://forum.arduino.cc/index.php?topic=507194.0.

new_to_due

Hi Martin Thanks for the help!

I have the pulse running once but Ive lost the dead time for some reason I'm not sure why? Ive attached a scope pic of the original signal that I had/require and another showing the single pulse signal I'm getting now that is overlapped. My code is also attached. I'm sure it is just the order the code is written in but I cant seem to get it right. Any help would be appreciated.

Code: [Select]

int doSwitch = 0;
int counter = 0;
 
void setup() {
 
 /*************** PWM set-up on pins  D7 and D6 for channels 6 & 7 respectively*************************************/
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                                               // Enable PWM
 
  PIOC->PIO_ABSR |= PIO_ABSR_P24 | PIO_ABSR_P23;                                    // Set the port C PWM pins to peripheral type B
 
  PIOC->PIO_PDR |= PIO_PDR_P24 | PIO_PDR_P23;                                       // Set the port C PWM pins to outputs
 
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(16);                                // Set the PWM clock A rate to 5.25MHz (84MHz/16)
 
  PWM->PWM_SCM |= PWM_SCM_SYNC7 | PWM_SCM_SYNC6 | 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 = 37500;                                              // Set the PWM frequency 5.25MHz/(37500) = 140Hz for all synchronous channels
 
  PWM->PWM_CH_NUM[0].PWM_CDTY = 0;                                               // Set the PWM duty cycle to 0.84%
  PWM->PWM_CH_NUM[6].PWM_CDTY = 0;
  PWM->PWM_CH_NUM[7].PWM_CDTY = 0;   
 
  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)
  REG_PWM_IER1 = PWM_IER1_CHID0 ;                                                 // Enable interrupt on PWM channel 0 triggered at end of PWM period
 
  PWM->PWM_ENA = PWM_ENA_CHID7 | PWM_ENA_CHID6 | PWM_ENA_CHID0;                   // Enable all PWM

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println("press a number");
delay(200);
    if (Serial.available() < 2 )
      {
      doSwitch = Serial.read();
      }
      if (doSwitch > 0)
        {
          Serial.print("Stim");
          REG_PWM_IER1 = PWM_IER1_CHID0;    // Enable event interrupts
          doSwitch = 0;
        }
    }

/*******************FUNCTIONS****************************************/

void PWM_Handler() {
  if (REG_PWM_ISR1 & PWM_ISR1_CHID0 )    // Check if an update condition has occured
  {   
    if (counter == 0)
    {
      counter++;
      PWM->PWM_CH_NUM[0].PWM_CDTY = 315;                                               // Set the PWM duty cycle to 0.84%
      PWM->PWM_CH_NUM[6].PWM_CDTY = 315;
      PWM->PWM_CH_NUM[7].PWM_CDTY = 315;   

      PWM->PWM_CH_NUM[7].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;                   // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
      PWM->PWM_CH_NUM[7].PWM_DT = PWM_DT_DTH(0) | PWM_DT_DTL(630);                    // Set the low and high dead-time for 120us delay (140Hz => 120us period)
      PWM->PWM_CH_NUM[7].PWM_CDTY = 3150 + 630;                                       // Set the PWM duty cycle to 8.4%
    }
    else
    {
      counter = 0;                      // Reset the counter
      PWM->PWM_CH_NUM[0].PWM_CDTY = 0;                                               // Set the PWM duty cycle to 0.84%
      PWM->PWM_CH_NUM[6].PWM_CDTY = 0;
      PWM->PWM_CH_NUM[7].PWM_CDTY = 0;     
      REG_PWM_IDR1 = PWM_IDR1_CHID0;    // Disable interrupts     
    }
  }
}

MartinL

#3
Oct 17, 2018, 09:29 am Last Edit: Oct 17, 2018, 09:30 am by MartinL
Hi new_to_due,

I had a go at trying to get dead-time insertion together with synchronous PWM outputs and single pulse output, but it appears that doing so causes the Due to act strangely. I think that combining all three features is  straying outside the bounds of what I'd call PWM controller's normal operation.

It appears that dead-time insertion only works if the PWM clock is not divided down, which isn't helpful if you require low frequency output. Also, the output PWM waveforms only work if the channel 0 duty-cycle update register is loaded with zero.

If your intention is to use dead-time insertion for a H-bridge, it might be better to use two asynchronous channels' high (PWMHx) and low side (PWMLx) complementary PWM outputs and adjust their high (DTH) and low (DTL) dead-time insertion accordingly. I'm not familiar with the H-bridge, but I imagine this is how the Due's PWM channels with dead-time insertion are intended to be used.

ard_newbie


IMO PWM_SMMR (or TC_SMMR) is the register to be used for H-bridge control. You will find PWM waveforms outputs to provide a 2-bit gray count waveform on 2 outputs page 981 of Sam3x datasheet. Unfortunately, Figure 38-6 is wrong but you can find the correct figure (Figure 39_6) page 962 of Sam4S datasheet.

Whenever you have to code PWM waveforms, it's handy to draw the waveforms you need for a few periods, then look for the proper registers to program.

new_to_due

Hi ard _Newbie Thanks for the suggestion. Wondering if you have any example code in regards to this 


IMO PWM_SMMR (or TC_SMMR) is the register to be used for H-bridge control. You will find PWM waveforms outputs to provide a 2-bit gray count waveform on 2 outputs page 981 of Sam3x datasheet. Unfortunately, Figure 38-6 is wrong but you can find the correct figure (Figure 39_6) page 962 of Sam4S datasheet.

Whenever you have to code PWM waveforms, it's handy to draw the waveforms you need for a few periods, then look for the proper registers to program.
Im ok with and are used to AVR but Im having real difficulty implementing this ARM seem a lot more difficult to understand and implement. Any help in implementing your suggestion would be great.

 

ard_newbie


Begin by posting a drawing of the PWM outputs you need, one above the other.

new_to_due

#7
Oct 20, 2018, 12:23 pm Last Edit: Oct 20, 2018, 12:58 pm by new_to_due
Hi again ard_Newbie
Ive attached a sketch of the outputs I need for the PWM. Hopefully you can read them well enough if not let me know and Ill redo it.

As discussed in earlier posts I just need the waveforms to run once on command. I have tried a number of different ways  using interupts but it seems to always give some strange behaviour when trying to combine the interupts with the dead time functionality. It works sometimes and sometimes it doesnt. Bit frustrating!

ard_newbie


If you run your waveforms only once and then the 2 outputs are at a low level, what is the interest to program 2 PWM outputs ? You can obtain the 2 outputs with PIO_SODR and PIO_CODR instead.

new_to_due

Sorry Im not sure what you mean by that ard_Newbie. I probably wasnt clear in my explanation I need the waveforms to run once on command to control a Hbridge. The timing needs to be quite precise and accurate for the switching and I need them to run anytime a particular action happens. PWM is probably the best way to do this considering the precision and accuracy required?


Hi new_to_due,

I had a go at trying to get dead-time insertion together with synchronous PWM outputs and single pulse output, but it appears that doing so causes the Due to act strangely. I think that combining all three features is  straying outside the bounds of what I'd call PWM controller's normal operation.

It appears that dead-time insertion only works if the PWM clock is not divided down, which isn't helpful if you require low frequency output. Also, the output PWM waveforms only work if the channel 0 duty-cycle update register is loaded with zero.

If your intention is to use dead-time insertion for a H-bridge, it might be better to use two asynchronous channels' high (PWMHx) and low side (PWMLx) complementary PWM outputs and adjust their high (DTH) and low (DTL) dead-time insertion accordingly. I'm not familiar with the H-bridge, but I imagine this is how the Due's PWM channels with dead-time insertion are intended to be used.
Hey Martin I have tried your suggestion but cant seem to get it give me the outputs I require using different complementary channels. Again the dead time doesnt seem to be working for complementary channels. ie when I set the PWMHx channel to 100% duty cycle I cant seem to get a dead time in the channel, but perhaps I am using the registers incorrectly to do it it in this particular instance.

Code: [Select]

void setup () {

  // PWM Set-up on pins PC6 and PC9 (Arduino Pins 41(PWMH3)& Pins 39(PWMH2 )): see Datasheet chap. 38.5.1 page 973
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                    // PWM power ON
 
  PWM->PWM_DIS = PWM_DIS_CHID2 | PWM_DIS_CHID3;         // Disable PWM channel 2
 
  // Select Instance=PWM; Signal=PWML2 (channel 2); I/O Line=PC6 (P6, Arduino pin 38, see pinout diagram) ; Peripheral=B
  PIOC->PIO_PDR |= PIO_PDR_P6;                          // Set the pin to the peripheral PWM, not the GPIO

  PIOC->PIO_ABSR |= PIO_PC6B_PWML2;                     // Set PWM pin perhipheral type B
 
  // Select Instance=PWM; Signal=PWMH3 (channel 3); I/O Line=PC9 (P9, Arduino pin 41, see pinout diagram) ; Peripheral type =B
  PIOC->PIO_PDR |= PIO_PDR_P9;                          // Set the pin to the peripheral PWM, not the GPIO

  PIOC->PIO_ABSR |= PIO_PC9B_PWMH3;                     // Set PWM pin perhipheral type B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(16);    // Set the PWM clock rate to 2MHz (84MHz/42). Adjust DIVA for the resolution you are looking for

  PWM->PWM_CH_NUM[2].PWM_CMR = PWM_CMR_CPRE_CLKA;       // The period is left aligned, clock source as CLKA on channel 2
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_CPRE_CLKA;       // The period is left aligned, clock source as CLKA on channel 3

  PWM->PWM_CH_NUM[2].PWM_CPRD = 37500;                  // Channel 2 : Set the PWM frequency 2MHz/(2 * CPRD) = F ;
  PWM->PWM_CH_NUM[3].PWM_CPRD = 37500;                  // Channel 2 : Set the PWM frequency 2MHz/(2 * CPRD) = F ;
 
  PWM->PWM_CH_NUM[2].PWM_CDTY = 315;                    // Channel 2: Set the PWM duty cycle to x%= (CDTY/ CPRD)  * 100 % ;
 
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;        // Enable single slope PWM and set the clock source as CLKA for all synchronous channels
  PWM->PWM_CH_NUM[3].PWM_DT = PWM_DT_DTH(630) | PWM_DT_DTL(3780);      // Set the low and high dead-time to start at 120us to 720us (140Hz => 600us period)
  PWM->PWM_CH_NUM[3].PWM_CDTY = 37500;                                 // Set the PWM duty cycle to 100% for the high channel
 
  PWM->PWM_ENA = PWM_ENA_CHID2 | PWM_ENA_CHID3;
}

void loop() {
 
}

MartinL

#10
Oct 20, 2018, 04:27 pm Last Edit: Oct 20, 2018, 04:29 pm by MartinL
Hi new_to_due,

Here's an example of complementary PWM outputs on digital pins D40 (PWM3L) and D41 (PWM3H).

The two outputs are complementary, or in other words the inverse of each other at a frequency of 100kHz (10us period).

A delay can be added to both the low and high side PWM output with the dead-time low (DTL) and dead-time high (DTH) respectively.

In this example the dead-time is set to 209 (839/4), or 2.5us, on both the low and high side outputs:

Code: [Select]
// Enable single-slope PWM at 100kHz on complementary channel 3 with dead-time insertion
void setup() {
  // PWM set-up on digital pins D40 (PWML3) and D41 (PWMH3)
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                              // Enable PWM
  PIOC->PIO_ABSR |= PIO_ABSR_P9 | PIO_ABSR_P8;                    // Set the port C PWM pins to peripheral type B
  PIOC->PIO_PDR |=  PIO_PDR_P9 | PIO_PDR_P8;                      // Set the port C PWM pins to outputs
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);               // Set the PWM clock A rate to 84MHz (84MHz/1)
  PWM->PWM_CH_NUM[3].PWM_CPRD = 839;                              // Set the PWM frequency 84MHz/(839 + 1) = 100kHz
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;   // Enable single slope PWM and set the clock source as CLKA
  PWM->PWM_CH_NUM[3].PWM_DT = PWM_DT_DTH(209) | PWM_DT_DTL(209);  // Set the dead-time for a 2.5us delay on both outputs
  PWM->PWM_CH_NUM[3].PWM_CDTY = 419;                              // Set the PWM duty cycle to 50%
  PWM->PWM_ENA = PWM_ENA_CHID3;                                   // Enable all PWM channels             
}

void loop() {}


new_to_due

Ive tried applying the complementry waveforms and dead time but i cant seem to get the two waveforms Im trying to get. I think Im stuggling with the principles of dead time could you please explain where actually DTH and DTL are applying the delays in each waveform?

ard_newbie

Page 982:
Figure 38-7. Complementary Output Waveforms
E.g. with CPOL = 0, you see DTOH = PWMH output, and DTOL = PWML

DTH is the dead  time applied to PWMH whereas DTL is applied to PWML

As you can see, DTH < CDTY  and DTL < CPRD - CDTY (page 1051)

MartinL

#13
Oct 21, 2018, 07:29 pm Last Edit: Oct 21, 2018, 07:55 pm by MartinL
Hi new_to_due,

Ok, I had a go at solving your timing requirements.

You required a 60us pulse on one output, followed by a 600us pulse, 60us later on the other:



Here's the output I managed to generate on the Due. It matches your requirements with the exception of a tiny transient pulse on the high side output (PWMH3) at the beginning of the cycle:



The code uses two complementary outputs on a single PWM channel, employing the end of period interrupt service routine, dead-time insertion and output override. The pulses are triggered to occur every second:

Code: [Select]
// Enable single PWM pulses on complementary outputs on channel 3 with dead-time insertion and output override
void setup() {
  // PWM set-up on digital pins D40 (PWML3) and D41 (PWMH3)
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                              // Enable PWM
  PIOC->PIO_ABSR |= PIO_ABSR_P9 | PIO_ABSR_P8;                    // Set the port C PWM pins to peripheral type B
  PIOC->PIO_PDR |=  PIO_PDR_P9 | PIO_PDR_P8;                      // 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/1)
  PWM->PWM_CH_NUM[3].PWM_CPRD = 719;                              // Set the PWM frequency 1MHz/(729 + 1) = 1389Hz
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_DTE | PWM_CMR_CPRE_CLKA;   // Enable single slope PWM and set the clock source as CLKA
  PWM->PWM_CH_NUM[3].PWM_DT = PWM_DT_DTH(59) | PWM_DT_DTL(0);     // Set the dead-time for a 60us delay on PWMH3
  PWM->PWM_CH_NUM[3].PWM_CDTY = 59;                               // Set the PWM duty cycle for a 60us pulse on PWML3
  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_CHID3;                                 // Enable interrupt on PWM channel 3 triggered at end of PWM period
  PWM->PWM_ENA = PWM_ENA_CHID3;                                   // Enable all PWM channels             
}

void loop() {
  // Output PWM pulses every second
  PWM->PWM_IER1 = PWM_IER1_CHID3;                                 // Enable end of period PWM interrupt on channels 3
  delay(1000);                                                    // Wait 1 second 
}

void PWM_Handler() {
  static uint8_t counter = 0;                                     // Initialise the counter
 
  if (PWM->PWM_ISR1 & PWM_ISR1_CHID3)                             // Check if an update condition has occured
  {   
    if (counter == 0)                                             // Initiate the output pulses
    {
      counter++;     
      PWM->PWM_CH_NUM[3].PWM_CDTYUPD = 59;                        // Set the PWM
      PWM->PWM_OSCUPD = PWM_OSCUPD_OSCUPH3;                       // Clear the PWMH3 output override
    }
    else                                                          // Clear the output pulses on subsequent periods
    {
      counter = 0;                                                // Reset the counter
      PWM->PWM_CH_NUM[3].PWM_CDTYUPD = 0;                         // Set the PWM duty cycle to 0%
      PWM->PWM_OSSUPD = PWM_OSSUPD_OSSUPH3;                       // Override the PWMH3 output: set it to LOW (0v)
      PWM->PWM_IDR1 = PWM_IDR1_CHID3;                             // Disable update interrupts       
    }
  }
}

new_to_due

Thankyou guys. It is much appreciated!!

Go Up