AVR PWM with Timer1 and Timer2: LED Dimming and Servo Motor Control

Pulse width modulation (PWM) switches a pin on and off quickly and varies the fraction of time it stays on, called the duty cycle. An LED driven this way appears dimmer or brighter, a DC motor driver delivers a variable average voltage, and a hobby servo reads the pulse width as a position command.

The AVR timers can produce PWM entirely in hardware, so the CPU only writes a new duty value when it wants to change the output. In this tutorial you will use Timer2 to fade an LED and the 16-bit Timer1 to drive an SG90 servo with the precise 50 Hz signal it needs.

PWM Modes and Formulas

In fast PWM mode the timer counts from 0 up to TOP and starts again. In non-inverting mode the output pin is set at 0 and cleared when the counter equals the compare register, so a larger compare value means a wider pulse. In phase correct PWM the timer counts up and then down, which halves the frequency but gives symmetrical pulses preferred for motor control.

Fast PWM frequency          f = f_clk / (N x (1 + TOP))
Phase correct frequency     f = f_clk / (2 x N x TOP)
Duty cycle (non-inverting)  D = (OCRnx + 1) / (TOP + 1)

N = prescaler (1, 8, 64, 256, 1024 for Timer1;
               Timer2 also offers 32 and 128)
OutputTimer registerATmega328P pinUno pin
OC0A / OC0BOCR0A / OCR0BPD6 / PD5D6 / D5
OC1A / OC1BOCR1A / OCR1BPB1 / PB2D9 / D10
OC2A / OC2BOCR2A / OCR2BPB3 / PD3D11 / D3

The PWM signal only reaches the pin if that pin's DDR bit is set as an output. Forgetting this is the most common reason for a dead PWM pin.

Part 1: LED Fading with Timer2

Connect an LED with a 330 Ω resistor from PB3 (OC2A, Uno D11) to ground. Timer2 is set to fast PWM with TOP = 255 (mode 3) and a prescaler of 64. Note that Timer2 has its own prescaler table: CS22 alone selects divide by 64, unlike Timer0 and Timer1 where the same bit pattern means divide by 256.

f = 16 000 000 / (64 x 256) = 976.5625 Hz  (no visible flicker)
LED fade on OC2A (PB3) using Timer2 fast PWM
#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

static void timer2_pwm_init(void)
{
    DDRB |= (1 << DDB3);                    /* OC2A pin as output */

    /* Fast PWM, TOP = 0xFF: WGM21 = 1, WGM20 = 1
       Non-inverting on OC2A: COM2A1 = 1 */
    TCCR2A = (1 << COM2A1) | (1 << WGM21) | (1 << WGM20);

    /* Timer2 prescaler 64: CS22 = 1 (Timer2 table) */
    TCCR2B = (1 << CS22);

    OCR2A = 0;                              /* start with LED off */
}

int main(void)
{
    timer2_pwm_init();

    while (1) {
        for (uint16_t d = 0; d <= 255; d++) {   /* fade in */
            OCR2A = (uint8_t)d;
            _delay_ms(4);
        }
        for (int16_t d = 255; d >= 0; d--) {    /* fade out */
            OCR2A = (uint8_t)d;
            _delay_ms(4);
        }
    }
}

With non-inverting fast PWM, OCR2A = 0 still produces a very narrow spike once per cycle, so the LED is not fully dark. If you need a true zero, disconnect the output by clearing COM2A1 and drive the pin low, or use inverting mode where 255 gives full off.

Part 2: Servo Control with Timer1

A standard hobby servo such as the SG90 expects a pulse every 20 ms (50 Hz). A pulse of about 1 ms moves it to one end, 1.5 ms to the centre, and about 2 ms to the other end. Many SG90 units accept a slightly wider range, roughly 0.5 ms to 2.4 ms, but test the limits gently because forcing a servo past its stop can strip the gears.

An 8-bit timer cannot give both a 20 ms period and fine resolution, so Timer1 is used in mode 14: fast PWM with ICR1 as TOP. With a prescaler of 8 the timer ticks at 2 MHz, which is 0.5 µs per count.

Timer clock  = 16 MHz / 8 = 2 MHz  (0.5 us per count)
ICR1 (TOP)   = 2 000 000 / 50 - 1 = 39999   -> 20 ms period
OCR1A        = pulse_us x 2 - 1
  1000 us  -> OCR1A = 1999
  1500 us  -> OCR1A = 2999
  2000 us  -> OCR1A = 3999

Wire the servo signal (orange) to PB1 (OC1A, Uno D9), red to a separate 5 V supply able to deliver a few hundred milliamps, and brown to ground. The servo ground and the microcontroller ground must be connected together.

SG90 servo sweep using Timer1 mode 14 (ICR1 = TOP) on OC1A
#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

static void timer1_servo_init(void)
{
    DDRB |= (1 << DDB1);                     /* OC1A = PB1 output */

    ICR1 = 39999;                            /* 20 ms period */
    OCR1A = 2999;                            /* 1.5 ms, centre */

    /* Mode 14: WGM13 WGM12 WGM11 = 1, WGM10 = 0
       Non-inverting OC1A: COM1A1 = 1 */
    TCCR1A = (1 << COM1A1) | (1 << WGM11);
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);  /* prescaler 8 */
}

/* Set pulse width in microseconds, clamped to a safe range */
static void servo_write_us(uint16_t us)
{
    if (us < 1000) us = 1000;
    if (us > 2000) us = 2000;
    OCR1A = us * 2 - 1;      /* 16-bit write handled by the compiler */
}

int main(void)
{
    timer1_servo_init();

    while (1) {
        for (uint16_t us = 1000; us <= 2000; us += 10) {
            servo_write_us(us);
            _delay_ms(15);
        }
        for (uint16_t us = 2000; us >= 1000; us -= 10) {
            servo_write_us(us);
            _delay_ms(15);
        }
    }
}

Timer1 registers such as ICR1 and OCR1A are 16 bits wide but accessed through an 8-bit bus with a shared temporary register. avr-gcc writes the high byte first automatically, so a plain assignment is correct. If an ISR also writes 16-bit Timer1 registers, protect main-loop writes with an atomic block.

Troubleshooting

  • No output on the pin: the DDR bit is not set, or the COM bits are zero.
  • Wrong frequency on Timer2: you used the Timer0 prescaler bit pattern. Check the Timer2 clock select table.
  • Servo jitters or the board resets when it moves: the servo is powered from the same weak supply as the AVR. Use a separate 5 V source with a common ground and a large capacitor across it.
  • Servo buzzes at one end: the pulse is beyond its mechanical limit. Narrow the range in servo_write_us().

Summary

Hardware PWM needs three things: a PWM waveform mode in the WGM bits, a compare output mode in the COM bits, and the pin set as output. Timer2 fast PWM at about 977 Hz is ideal for LED dimming, while Timer1 mode 14 with ICR1 = 39999 and a prescaler of 8 gives a 50 Hz servo signal with 0.5 µs resolution. Duty cycle changes are a single register write, so the CPU stays free for other work.