DIY, tech, projects, stuff from Adam Fraser-Kruck

Arduino (AVR) PWM with duty cycle for IR

This post will give you some code to generate high frequency square waves for IR sensors as well as explain how the Fast PWM mode works for Arduino.

I needed to interface with some IR sensors when designing the high tech mini golf hole and had to generate a 38 kHz square wave to drive the infrared LED so that it could be seen by the TSOP4838 IR receivers.

The basic Arduino analogWrite function is limited to less than 1 kHz (depending on your board), so you need to directly access one of the other on board Timer/PWM modules. The Arduino Uno (ATmega328) has 3 timer/counters:

Timer/Counter0: 8 bit, already used by Arduino for timing functions like millis() and delay().

Timer/Counter1: 16 bit, which is more than I need for this, so I'd rather save it for something else.

Timer/Counter2: 8 bit, fits my needs perfectly.

Once you choose which timer you want to use, you would head off to the datasheet of the ATmega328 (which is the chip that the Arduino Uno actually uses) and figure out how to use the timer/counter/PWM module. I'm going to explain that for you now, but you should still make a point of at least skimming the datasheet at some point. Arduino is a great platform for getting you up and running quickly, but sooner or later, you will have to be able to understand the datasheet to access all of the really good stuff.

The above snippet should be all you need to get up and running. Read on if you want to understand the guts of how it works in the actual microcontroller.

What is going on in the hardware?

The following lines of code select how the Timer/Counter 2 module will run by specifying the values for TCCR2A and TCCR2B.

TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(WGM22);

The "_BV(bit)" macro simply allows you to set bits high by their position. I could have written TCCR2A = 0b00100011 or 0x23, but this loses the context of what I'm trying to do: set the bits high that correspond to COM2B1, WGM21, and WGM20.

Table 17-8 in the datasheet explains that bits WGM2:0 control the mode of Timer 2. I've selected "Fast PWM" mode to get max speed and simplicity. Note that the three WGM2:0 bits are actually split between registers TCCR2A and TCCR2B.

Table 17-6 of the datasheet shows that our PWM output pin will be OC2B and that it will be set high and cleared low (more on this below).

Figure 17-1 of the datasheet (see below) is where things start to get interesting. The Timer/Counter 2 module mainly consist of a timer register TCNT2, and two output compare registers OCR2A and OCR2B.

OCR2A controls the PWM frequency.

In our "Fast PWM" mode, TCNT2 counts up. When its value matches OCR2A (yellow & purple lines), the control logic understands that it is at the TOP and prepares to reset the timer to zero on the next clock pulse. OCR2A allows us to control the frequency of our PWM signal by determining how many clock pulses before restarting the timer. We don't use the output pin OC2A, because it is tied to match events from OCR2A and would only allow us to output a frequency with a fixed 50% duty cycle. Using output OC2B allows us to have control over frequency and duty cycle.

OCR2B controls the PWM duty cycle.

The output pin (OC2B) is set high when the timer reaches OCR2A and is reset back to zero. The output continues to stay high until the timer count matches OCR2B (yellow & blue lines) and the output pin is cleared.

Special case: you cannot set duty cycle to zero!

From this design, you can see that setting OCR2B = 0 will actually not result in a duty cycle of zero. Let's see what happens when OCR2B = 0. You have to remember that everything happens with the input clock. When TCNT2 = TOP, the control logic sets up 2 things to happen on the next clock pulse: (1) TCNT2 will reset back to zero and (2) the output pin will be set high. When the next clock pulse arrives, the output is set high, TCNT2 is reset to zero and now matches OCR2B. Now that OCR2B matches TCNT2, the control logic prepares to set the output pin low at the next clock pulse. This delay prevents the duty cycle from ever being 0.

Using the inverted output option allows you to be able to set the duty cycle to zero, but then prevents you from having 100% duty cycle. A bit of a crummy design if you ask me. I think "Phase Correct PWM Mode" allows setting duty cycle between 0 and 100%, but has the limitation of operating at half the speed of "Fast PWM Mode".

Special case: what if OCR2A = OCR2B?

Figure 17-1 isn't clear in this case. Assume OCR2A = OCR2B = 50. When the timer counts up from 49 to 50 it matches OCR2A and the control logic gets ready to reset the timer, and set the output pin high. However, the timer also matches OCR2B and the control logic gets ready to set the output pin low. This contradiction of setting the output low and high at the same time is covered in a footnote for Table 17-6: "A special case occurs when OCR2B equals TOP and COM2B1 is set. In this case, the Compare Match is ignored, but the set or clear is done at BOTTOM." The result of this special case means that when OCR2B is greater than or equal to OCR2A, the duty cycle will be 100%.