Arduino Push-Pull PWM

Push-Pull Drive: OC1A top, OC1B bottom
Push-Pull PWM Drive: OC1A top, OC1B bottom

Most of the time you need just a single PWM output, but when you’re driving a transformer (or some such) and need twice the primary voltage, you can use a pair of PWM outputs in push-pull mode to get twice the output voltage.

A single PWM output varies between 0 and +5 V (well, Vcc: adjust as needed), so the peak-to-peak value is 5 V. Drive a transformer from two out-of-phase PWM outputs, such that one is high while the other is low, and the transformer sees a voltage of 5 V one way and 5 V the other, for a net 10 V peak-to-peak excursion.

A 50% duty cycle will keep DC out of the primary winding, but a blocking capacitor is always a good idea with software-controlled hardware. A primary winding with one PWM output stuck high and the other stuck low is a short circuit that won’t do your output drivers or power supply any good at all.

An Arduino (ATMega168 and relatives) can do this without any additional circuitry if you meddle with the default PWM firmware setup. You must use a related pair of PWM outputs that share an internal timer; I used PWM 9 and 10.

#define PIN_PRI_A   9    // OCR1A - high-active primary drive
#define PIN_PRI_B   10   // OCR1B - low-active primary drive

I set push-pull mode as a compile-time option, but you can change it on the fly.

#define PUSH_PULL   true // false = OCR1A only, true = OCR1A + OCR1B

The timer tick rate depends on what you’re trying accomplish. I needed something between 10 & 50 kHz, so I set the prescaler to 1 to get decent resolution: 62.5 ns.

// Times in microseconds, converted to timer ticks
//  ticks depend on 16 MHz clock, so ticks = 62.5 ns
//  prescaler can divide that, but we're running fast enough to not need it

#define TIMER1_PRESCALE   1     // clock prescaler value
#define TCCR1B_CS20       0x01  // CS2:0 bits = prescaler selection

Phase-Frequency Correct mode (WGM1 = 8) runs at half-speed (it counts both up and down in each cycle), so the number of ticks is half what you’d expect. You could use Fast PWM mode or anything else, as long as you get the counts right; see page 133 of the Fine Manual.

// Phase-freq Correct timer mode -> runs at half the usual rate

#define PERIOD_US   60
#define PERIOD_TICK (microsecondsToClockCycles(PERIOD_US / 2) / TIMER1_PRESCALE)

The phase of the PWM outputs comes from the Compare Output Mode register settings. Normally the output pin goes high when the PWM count resets to zero and goes low when it passes the duty cycle setting, but you can flip that around. The key bits are COM1A and COM1B in TCCR1A, as documented on page 131.

As always, it’s easiest to let the Arduino firmware do its usual setup, then mercilessly bash the timer configuration registers…

// Configure Timer 1 for Freq-Phase Correct PWM
//   Timer 1 + output on OC1A, chip pin 15, Arduino PWM9
//   Timer 1 - output on OC1B, chip pin 16, Arduino PWM10

 analogWrite(PIN_PRI_A,128);    // let Arduino setup do its thing
 analogWrite(PIN_PRI_B,128);

 TCCR1B = 0x00;                 // stop Timer1 clock for register updates

// Clear OC1A on match, P-F Corr PWM Mode: lower WGM1x = 00
 TCCR1A = 0x80 | 0x00;

// If push-pull drive, set OC1B on match
#if PUSH_PULL
 TCCR1A |= 0x30;
#endif

 ICR1 = PERIOD_TICKS;           // PWM period
 OCR1A = PERIOD_TICKS / 2;      // ON duration = drive pulse width = 50% duty cycle
 OCR1B = PERIOD_TICKS / 2;      //  ditto - use separate load due to temp buffer reg
 TCNT1 = OCR1A - 1;             // force immediate OCR1x compare on next tick

// upper WGM1x = 10, Clock Sel = prescaler, start Timer 1 running
 TCCR1B = 0x10 | TCCR1B_CS20;

And now you’ll see PWM9 and PWM10 running in opposition!

Memo to Self: remember that “true” does not equal “TRUE” in the Arduino realm.