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.

31 thoughts on “Arduino Push-Pull PWM

  1. I am having so much fun with my new Uno board, but a lack of documentation is holding me back. Is there a reference for all the Atmel control registers etc. that are exposed to the Arduino IDE? For example, I’m playing with the remote control that came with my kit, and I see a reference to TCCR1A. I’ve got the Atmega168 datasheet and it’s very enlightening, but where do I find the definitive reference on how much of the guts of the microcontroller is hanging out there in userspace?

    1. the definitive reference on how much of the guts of the microcontroller is hanging out there in userspace

      As nearly as I can tell, the gcc-avr headers define all those constants, so you can use them as you see fit. Just avoid clobbering Timer 0 and the serial configuration: everything else is available.

      However, don’t get too clever with the source code, as the Arduino compile chain pretty much assumes you’re not doing exotic language tricks.

      It’s a great way to make stuff work, that’s for sure!

      1. Yikes, I’ve been tiptoeing around the Arduino compile chain not really knowing how far I could step out of bounds but that was a very informative illustration of how delicate things are. It still strikes me as bizarre that I can write C++ and target it on something that looks like it came from the 1980’s (shame no new and delete but at least there’s still malloc and free, although their utility on a device with 2K of SRAM is questionable.) I have ventured into the headers and the fog is clearing somewhat. The main stumbling block is realising you CAN do something, not HOW to do it. Even so there’s a lot faster feedback into blinky lights and whizzy stepper motors than back in the dark ages of ABEL and CUPL and – saints preserve us – PALASM (yes, I’m old.)

        1. something that looks like it came from the 1980′s

          Microcontrollers are where fully depreciated fab lines go to die and embedded systems are where PC-wannabe architectures go to die…

          That said, it’s amazing what you can stuff into a silicon splinter these days.

          I trust you’ve read my preprocessor horror story. My rule of thumb: any Arduino project can have one clever hack that goes outside the normal bounds, but more than that will land you in a world of hurt.

  2. Hi, i used your code and it worked prefectly, very good job.
    I don’t have so much experience about timers but in my project with 328p i would use the timer2 instead of timer1 as you do.
    What i must modify in your code for using the timer2?
    Which are the pwm exists with timer2?

    1. Which are the pwm exists with timer2?

      You need a copy of either my pinout chart or the Official Version taped over your workbench: Timer2 drives the PWM3 and PWM11 outputs.

      What i must modify in your code for using the timer2?

      Basically, everything that refers to Timer1 changes to Timer2! Remember that Timer2 is only 8 bits wide, so you don’t have quite the same resolution as with the 16 bit Timer1.

      The key idea is that you set up OC2A and OC2B to behave oppositely when the timer matches the comparison registers (OCR2x).

      Timer2 uses a separate clock prescaler, so select the clock input using the CS2x bits. That post may be helpful in picking the frequency.

      You should read The Fine Manual’s Chapter 18: “18. 8-bit Timer/Counter2 with PWM and Asynchronous Operation” to understand what’s going on; the block diagram will be helpful. Timer2 is only 8 bits wide, so the timing constants will be different.

      Enjoy!

      1. Thanks for the reply.
        I think i’m able to set the right frequency for timer2.

        But what about “The key idea is that you set up OC2A and OC2B to behave oppositely when the timer matches the comparison registers (OCR2x).” ?

        I readed many times discussions in forums and manual but is hard for me understand the setting up of the timer for that mode.
        Could you write few code lines for help me?

        Thanks in advance

        1. Could you write few code lines for help me?

          I avoid doing that, simply because if you don’t understand what’s going on, I get roped into a remote-control debugging session that’s frustrating for both of us: it’s entirely possible that my code won’t work, so I shouldn’t expect you to figure that out!

          In fact, this is one of those cases where I’m completely wrong: you can’t get push-pull PWM bits out of Timer2 and control the PWM frequency, too.

          The problem is that Timer2 doesn’t have an Input Compare Register (ICR) that can set the timer period, in addition to the Output Compare Registers (OCR) that determine when the output bits flip within that period. As a result, you can’t specify the overall timer period and also have both output bits flip to control the PWM.

          However, if the overall period of Timer2 (256 ticks from the clock prescaler input) will suit your purposes, then (I think!) what you do is:

          • Set Timer2 to Normal Mode with WGM2:0 = 0 (counts upward, wraps from 0xFF to 0x00)
          • Set OCR2A and OCR2B to the PWM fraction you want (50% PWM = 128)
          • Set OC2A (output A) to go high on match: COM2Ax = 11
          • Set OC2B (output B) to go low on match: COM2Bx = 10
          • Set Prescaler2 to generate whatever period you need with CS2x, which will start the timer

          Then it’s a matter of fiddling around until it does exactly what you want… and at the end of the whole process, you’ll actually understand what’s going on, too, which is what you really want!

  3. Ok.
    I don’t need variable frequency (i need the maximum frequency, with prescaler set to 1), but i need variable duty cycle from 10% to 48% for each output.
    The initial setup of timer 2 you wrote will fit my request?
    Varing OCR2x i vary the duty as i want, but the outputs have opposite phase?

    Thanks

    1. I think it’ll work, but I’ve been wrong before… [grin]

      With the prescaler set to 1, Timer2 ticks at 16 MHz and the PWM frequency will be 16 MHz/256 = 62.5 kHz.

      You control the PWM width by setting the OCR2A and OCR2B registers; they must be equal to ensure the outputs switch at the same time. The COM2Ax / COM2Bx bits determine how the output bits switch when the compare happens, so you need one Set and one Clear.

      The PWM resolution is 0.4%, because Timer2 has only 8 bits. Set the OCR2x registers between 10% = 26 and 48% = 123; it should Just Work.

      Or, at least, you’ll be closer to having it work…

      1. I appreciate very much your help, thanks a lot.
        After the test i will write you back :)

        1. Hey Ed,
          i tried the code and it worked well, but i setted timer2 in CTC mode, not normal mode. The max frequency of each output is 31250 Hz, it’s ok for me.
          Thank you very much for the advice.
          Greetings from Italy

          1. timer2 in CTC mode

            It’s not clear that does quite what you want, because OCR2A sets the upper limit for the timer. That means you can’t also use OCR2A to set the PWM width: the timer resets immediately after the end of the PWM pulse!

            I think the 31 kHz output means that the PWM output is happening only on every other timer count cycle. Put that thing on an oscilloscope and make sure you can actually vary the PWM width: if it’s always 50% and only the period varies, then you know what’s happening…

  4. I setted timer2 in normal mode and didn’t work. Then i setted to CTC mode and i used OCR2A and OCR2B for varying the width and i verified it with my oscilloscope; it worked fine.
    May be i wrong the setting of the timer in normal mode: what is the register i must set to 1 for have normal mode?

    1. setting of the timer in normal mode

      This is why I didn’t want to write your code: then I must do remote debugging of my errors…

      Closer reading of The Fine Manual says you want Fast PWM Mode 3, not Normal mode, because then the OC2x outputs get reset automatically when the counter wraps from 0xFF to 0x00:

      • TCCR2A = binary 10 11 00 11 = 0xE3
      • TCCR2B = binary 00 00 0 001 = 0x01

      But, frankly, without actually fiddling with the code and watching the oscilloscope myself, I have my doubts…

  5. this is my code:

    void setup()
    {

    pinMode(3, OUTPUT);
    digitalWrite(3, LOW);

    pinMode(11, OUTPUT);
    digitalWrite(11, LOW);

    TCCR2A = 0;
    TCCR2B = 0;

    TCCR2A = _BV(WGM22) | _BV(WGM20) | _BV(COM2A1) | _BV(COM2A0) | _BV(COM2B1);
    TCCR2B = _BV(CS20); // prescaler = 1

    OCR2A = 254; // D11 254->min duty, 1->max duty
    OCR2B = 1; // D3 1->min duty, 254->max duty

    }

    What do you think?

    1. What do you think?

      Some things stand out…

      • No need to use digitalWrite; the timer setup will overrule those
      • No need to set TCCR2A or B to zero; the next assignments overrule those
      • WGM22 is in TCRR2B, so TCRR2A gets the wrong bit set
      • WGM = binary 101 is phase correct PWM, not fast PWM as I suggested
      • OCR2A must always equal OCR2B to make their outputs switch at the same TCNT2 value

      It probably has other gotchas, but I’m not in front of the oscilloscope…

  6. Yes i made a mistake with WGM22,
    Today i try to set the fast pwm as you suggested and then i give you a feedback.
    For now, thank you

    1. Hi,
      i try the fast pwm with top at 0xFF, so it double the frequency reaching 62kHz but putting in OCR2A and in OCR2B the same value i obtain 2 equal waves overlapped, and the duty cycles are dependents from the value in the registers each other.
      Instead in my first method i obtain 2 pwm with opposite phase but with duty cycles independent from each others: that is what i want.

      1. that is what i want.

        Ah, I misunderstood the goal; I thought you needed push-pull PWM with equal-but-opposite polarity.

        Sounds like you’ve managed to find a workable solution, but now you can see why I avoid remote debugging as much as I can… [grin]

        Pulse on!

        1. If you wanna drive a push pull transformer with center tap primary you need 2 pwm with opposite phase with the same duty cycle; so you can vary the duty in the same way from a minimum of 2% or more to a maximum of 48%.
          Thanks for all the help!

  7. Hi Ed,
    i’m here again :)
    I need a little help for a pulse generation in retard of a specified time from the rising edge of an input signal.
    I’m using the attachInterrupt for calling a ISR for start the timer1 with prescale=8.
    I wanna the output 9 attached to timer1 goes high automatically when the timer reaches the value stored in OCR1A (CTC mode), the time of retard that i setted before.
    My actual code works in the opposite way: When the edge is detected the output goes high and after the time setted in OCR1A goes low; so no retard, just pulse with duty variable.
    Could you help me?
    Thanks a lot

    1. pulse generation

      If I understand you correctly, you want:

      • A specific delay from an external event
      • A specific pulse width after the delay
      • One pulse per external event

      I think you can do that with a single timer, but it requires two interrupt handlers:

      • External event triggers “Handler A” to start the timer and possibly set the output bit
      • Timer triggers “Handler B” at end of pulse to stop the timer and reset for next external event

      You (almost certainly) want phase-and-frequency-correct PWM mode, not CTC mode, for this function, so that you can set the delay and width. The timers normally run continuously, so you must manually preset, start, and stop the timers to get the result you want.

      The trick is to preload the TCNT register with the delay value relative to the TOP value in ICR. When the external event starts the timer (through Handler A), it counts upward during the initial delay time. When it hits TOP, the COM configuration sets the output bit and the timer counts downward toward BOTTOM (0x0000) during the pulse width. You’ve set the TOP value in ICR so that the delay from TOP to BOTTOM determines the pulse width. When TCNT hits BOTTOM, it clears the output bit and triggers Handler B, which stops the timer and prepares everything for the next external pulse.

      I haven’t worked out the details, but that seems to do what (I think) you need; it will definitely require drawing timing diagrams with the register and configuration settings to get the proper values!

      Now, fair warning: the last time you presented a problem, we did a bunch of back-and-forth debugging to get your code working. I will not do that again: if you want finished code or dedicated consulting and problem-solving, I’ll be very happy to charge you for that work. [grin]

      1. Your explanation is very good.
        The only one thing isn’t so clear is to preload the TCNT. Have i to load the desired delay value in TCNT, or in ICR?

        1. Have i to load the desired delay value in TCNT, or in ICR?

          The value in ICR sets TOP, which will determine the pulse width while the timer counts from TOP to BOTTOM.

          The initial value in TCNT sets the delay while the timer counts from that value to TOP. So you must set TCNT relative to the value of TOP; basically, you subtract the initial delay from TOP to get TCNT. I suspect there will be a problem if that difference lies below TOP, because the counter must pass TOP on its way through 0x0000 to TOP again… but I haven’t drawn the diagrams.

          If that poses a problem, then you must either use two timers or use Timer 1 (probably in CTC mode) to generate both delays, with manual bit twiddling in three places: initial setup, end of delay, and end of pulse.

  8. this is my code:

    void setup() {
    setTimer1(); //set the timer 1
    attachInterrupt(0, SensorINPUT, FALLING);
    pinMode(9,OUTPUT);
    }

    void loop() {
    }

    // initialize Timer1
    void setTimer1() {
    SREG &= ~(1 << SREG_I); // disable global interrupts
    // set compare match register to desired timer count:
    // turn on CTC mode:
    TCCR1A = 0;
    TCCR1B = (1 << WGM12);
    TCCR1B &= ~((1 << WGM10) | (1 << WGM11));
    SREG |= (1 << SREG_I);// enable global interrupts:
    }

    void SensorINPUT() {
    TCCR1B &= ~(1 << CS11); //stop the timer
    TCNT1 = 0; //reset the timer counter
    OCR1A=1000; //set OCR1A (500us of retard)
    TCCR1A = ((1<<COM1A1) | (1<<COM1A0)); //set pin 9 high on compare
    TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
    TCCR1B |= (1 << CS11); //restart the timer
    }

    //Interrupt Service Routine called on OCR1A COMPARE MATCH
    ISR(TIMER1_COMPA_vect) {
    TCCR1B &= ~(1 << CS11); //stop the timer
    TCCR1A=0;
    TIMSK1 &= ~(1 << OCIE1A); //disable interrupt
    }

    I wait for you, thanks.

  9. So, the value in the OCR1A is the delay from external interrupt, right?
    Because i know that TCNT1 is the current counter value.
    I never use this mode, which is the name of the “handle B” called by the the timer? ISR(TIMER1…..

    1. You must re-read what I’ve already written and start drawing diagrams based on how the hardware operates.

      Remember, I haven’t worked out all the details and gotchas; those are for you to explore…

  10. Hi, i appreciate your help very much, but what i need is a pulse generation of a fixed width (100us) in retard from the falling/rising edge of my input signal.
    For that i’m looking for the fast pwm or phase correct pwm, but the issue i’m currently get is that the output pin 9 attached to timer1 is going high when i start the timer1 and not after the required delay.
    I’m fighting with the datasheet and timing diagrams but i don’t see the end.
    I tried two solutions (fast and phase correct) but didn’t work.

    1. the output pin 9 attached to timer1 is going high when i start the timer1

      The interrupt handler for the external trigger should do nothing more than start the clock, because you must configure the timer with OC1A low before the trigger. Obviously, you’re not doing that, so I think your setup is incorrect.

      Do the WGM bits select the correct operating mode?

      Do the COM bits specify that OC1A goes high on match when upcounting?

      Have you preset the proper TOP value in the proper register for the mode you’re using?

      Do you preload TCNT1 with the initial delay value, but that value is less than the TOP value (in ICR1 or OCR1A), so that TCNT1 incorrectly triggers a comparison during the delay period? I think that will happen if the initial delay is longer than the pulse width.

      Are you starting the timer by setting the CS1 bits to something other than 0? You must set up the timer configuration with the clock stopped, then start the clock when the external trigger event occurs, stop the clock after the pulse ends, then reset the timer for the next external trigger with the clock stopped.

      I don’t want to sound obstructionist, but you’re asking for a lot of assistance. Yes, it takes time and effort to work through all the modes and bits and interactions until you understand them, but, as the saying goes, time is money.

      Stringing these comments out on an unrelated blog post is not productive, except to show others how not to get help. I won’t respond to further requests here; if you need more assistance getting your project working, contact me by email and we’ll work out a contract.

Comments are closed.