Complementary PWM Signals at 50 kHz with dead time

I'm new to using Arduino and am trying to develop two complementary PWM signals at 50 kHz with dead time.

I found this: two 200kHz complementary PWM Signals - #9 by dlloyd but am very unsure of how the code actually works and how to adjust the duty cycle & dead time. I've spent time looking at the control registers and recognize that the duty cycle is adjusted using the OCR1A bit, but how the number ties into the calculation of the duty cycle is confusing me.

I'm wondering if anyone has any suggestions for a software implementation that takes a slightly easier approach?

For precise control over the hardware timer in a demanding application like yours, you really have no choice but to carefully study the timer section of the processor data sheet, and learn the function of every bit in the control registers. No high level library is complete enough to handle every imaginable application.

For more specific advice it is extremely important that you identify the processor you plan to use.

Sorry, I should've mentioned that I'm using an Arduino mega 2560

What is the technical spec, the needs for synchronised outputs? For what will those matched signals be used?
Exactly the same time never exists. Just get better time measuring device and You will see the difference. Newbies often call for exactness without knowing the real needs.

When you say two complementary PWM signals, do you mean as in controlling a half-bridge or a full bridge? I'm not sure that the AVR (at least not the Mega series) has the timer complexity to manage a full bridge with deadtime all in hardware.

// Generating Two 180° Out of Phase 50 kHz Square 
// Waves with dead time on Timer1 of an 
// Arduino UNO (Pins 9 and 10)
// Written January 23rd, 2023 by John Wasser

// frequency =  16MHz / (2 * prescaleFactor *  (TOP + 1))
// Correction, because Phase Correct PWM is used: 
// frequency =  16MHz / (2 * prescaleFactor *  TOP)
const unsigned TOP = 160;
// frequency =  16MHz / (2 * 1 *  160)
// frequency =  16MHz / 320
// frequency = 50,000

void setup()
{
  Serial.begin(115200);
  delay(200);

  digitalWrite(9, LOW);
  pinMode(9, OUTPUT);
  digitalWrite(10, LOW);
  pinMode(10, OUTPUT);

  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TIMSK1 = 0;  // Timer/Counter1 Interrupt Mask Register

  // Set Timer/Counter1 to Waveform Generation Mode 8:
  // Phase and Frequency correct PWM with TOP set by ICR1
  TCCR1B |= _BV(WGM13);  // WGM=8
  TCCR1A |= _BV(COM1A1);  // Normal PWM on Pin 9
  TCCR1A |= _BV(COM1B1) | _BV(COM1B0); // Inverted PWM on Pin 10

  ICR1 = TOP;
  // Difference between OCR1A and OCR1B is Dead Time
  OCR1A = (TOP / 2) - 1;
  OCR1B = (TOP / 2) + 1;

  // Start timer by setting the clock-select bits to non-zero
  TCCR1B |= _BV(CS10); // prescale = 1
}

void loop() {}
3 Likes

The answer may be:

Then an Uno already can provide the non-overlapping signals in hardware.

@johnwasser presented the code that works on most AVR controllers (not sure of ATtiny).

@rachber, welcome to the forum. The Timers of a Arduino Uno can do many things. It should not be hard to convert the code for your Mega board, but I did not try that.

The TOP should be 160.

I tried your code in a Wokwi simulation: A PWM signal with dead time - Wokwi ESP32, STM32, Arduino Simulator


The simulation stores a file (from the simulated Logic Analyzer) after the simulation is stopped.
PulseView is needed to see the signals of the Wokwi Logic Analyzer. You don't have to install sigrok.

[UPDATE 2] I have edited this post, because the value needs to be 160 after all, see post #17 and #18.

I had the hope the OP would tell that instead of our guessing.

Is it logical to write value on an IO line without setting up the direction first?

Is there any benefit of choosing Mode-8 instead of Mode-14?

The output state is stored even if the pin is not configured for output.

Fast PWM mode (14) resets the outputs only on counter overflow, i.e. at the same time. Mode 8 toggles the outputs while counting up and down, at different times.

Sorry it's taken me so long to get back to everyone. Thank you for the suggestions/ideas/code. I've spent more time diving into the timers and have a better since on how to manipulate them to adjust the frequency. Thanks again for the help

Because the Fast PWM is a single slope conversion, the counter (say: TCNT1) starts counting from BOTTOM when it matches with the TOP (the ICR1 in Mode-14) value. Thus for a realistic frequency/duty cycle determined by ICR1/OCR1A, the TCNT1 never reaches to its Final Count (Fig-1) to undergo overflow/rollover.


Figure-1:

Or restart at bottom. Glad to see that you understood what I meant.

@johnwasser Can you help me out ?

When using your sketch from post #6, then I get a frequency of 50.3kHz.
To get exactly 50.000kHz, the TOP needs to be 160.
Can you confirm that ?

Test conditions:
A Uno clone with a accurate 16MHz crystal (I used it for a clock without RTC in the past). Room temperature. LHT00SU1 Logic Analyzer with 24MHz sampling rate because the 48MHz sampling rate turns out to be not accurate. Confirmed with a normal CTC mode that the result is as expected (https://wokwi.com/projects/355043826935526401). Multiple times tested in Wokwi and real life.

Yes, the correct value is 160.
It because Phase Correct PWM is used.

In Fast PWM mode the period is TOP +1 cycle,
pwmfast

but in Phase Correct is only TOP cycles.

(Screenshots from the atmega328 dataseet, chapter 20.13)

2 Likes

In post #6, @johnwasser has chosen Mode-8 waveform which is "Phase and Frequency Correct" PWM signal though he has mistakenly used the following incorrect equation for frequency.

The correct equation for frequency of dual slope (Phase Correct, Phase and Frequency Correct) PWM waveform particularly Mode-8 can be easily calculated from Fig-1:


Figure-1:

Period = time to travel path: A - C and C - E
==> T = 2 x time to travel path A - C
==> T = 2 x (1/clkTC1) x ICR1  //1/clkTC1 = time for 1 count

frequency, f = 1/T:
==> f = clkTC1/(2 x ICR1)
==> f = (clkSYS/N)/(2 x ICR1)
==> f = clkSYS/(2 x N x ICR1)
For clkSYS = 16 MHz, N = 1, f = 50 kHz:
ICR1 (the TOP) = 160
1 Like

Thank you both :heart:

Thanks! I've added a correction to Reply #6 in case someone else finds the code and doesn't read the following comments.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.