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!!
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;
}
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: generating 8 40KHz pulses - Arduino Due - Arduino Forum.
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.
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
}
}
}
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.
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.
Hi ard _Newbie Thanks for the suggestion. Wondering if you have any example code in regards to this
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.
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.
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!
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.
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?
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.
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.
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() {
}
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:
// 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() {}
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?
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:
// 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
}
}
}
Here's a simplified version the produces the same pulses, but interleaves 2 synchronous PWM channels, by simply changing their duty-cycle and period every other cycle:
// Interleave two PWM pulses using different duty-cycles and periods on two synchronous PWM channels: 0 and 1
void setup() {
// PWM set-up on pins DAC1 and A8 for channels 0 and 1 respectively
PMC->PMC_PCER1 |= PMC_PCER1_PID36; // Enable PWM
PIOB->PIO_ABSR |= PIO_ABSR_P17 | PIO_ABSR_P16; // Set the port B PWM pins to peripheral type B
PIOB->PIO_PDR |= PIO_PDR_P17 | PIO_PDR_P16; // Set the port B 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_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 = 119; // Set the PWM frequency 84MHz/(839 + 1) = 100kHz for all synchronous channels
PWM->PWM_CH_NUM[0].PWM_CDTY = 0; // Set the PWM duty cycle to 0%
PWM->PWM_CH_NUM[1].PWM_CDTY = 0; // Set the PWM duty cycle to 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)
PWM->PWM_ENA = PWM_ENA_CHID1 | PWM_ENA_CHID0; // Enable PWM channels 0 and 1
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() {
// Output PWM pulses every second
PWM->PWM_IER1 = PWM_IER1_CHID0; // Enable end of period PWM interrupt on channels 0 and 1
delay(1000); // Wait 1 second
}
void PWM_Handler() {
static uint8_t counter = 0; // Initialise the counter
if (PWM->PWM_ISR1 & PWM_ISR1_CHID0) // Check if an update condition has occured
{
if (counter == 0) // Initiate the output pulses
{
counter++; // Increment the counter
PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 59; // Set the first PWM duty-cycle for 60us pulse and period of 120us
PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 0;
PWM->PWM_CH_NUM[0].PWM_CPRDUPD = 119;
}
else if (counter == 1)
{
counter++; // Increment the counter
PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 0; // Set the second PWM duty-cycle for 600us pulse and period of 7030us
PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 599;
PWM->PWM_CH_NUM[0].PWM_CPRDUPD = 7029;
}
else
{
counter = 0; // Reset the counter
PWM->PWM_CH_NUM[0].PWM_CDTYUPD = 0; // Reset the duty-cycle to 0 and set the period
PWM->PWM_CH_NUM[1].PWM_CDTYUPD = 0;
PWM->PWM_CH_NUM[0].PWM_CPRDUPD = 119;
PWM->PWM_IDR1 = PWM_IDR1_CHID0; // Disable update interrupts
}
PWM->PWM_SCUC = PWM_SCUC_UPDULOCK; // Set the update unlock bit to trigger an update at the end of the next PWM period
}
}
It's somewhat simpler than using the dead-time insertion and complementary outputs method that I suggested above.
There is also another way to output these 2 waveforms, that's with 2 synchro channels and an automatic update of Duty Cycles with the PDC DMA.
140 Hz = 84 MHz / 84(DIVA)/1(CPRD)/7142(Size of Buffers). Declare 2 Buffers: Wave_Duty0 for PWMH0 and Wave_Duty1 for PWMH1 of 7142 Half Words. Fill these 2 Buffers according to the wave forms you want. Enable PDC DMA. That should work.