Timers, PWM and Timing
Prescaler and compare math, periodic interrupts, PWM frequency and resolution, servo pulses and timing without blocking.
What a hardware timer is
A timer is a counter that the chip’s clock increments on its own, whatever the program is doing. It can raise an interrupt when it overflows or reaches a compare value, toggle a pin automatically, capture the count when an input changes, and generate PWM. Because it runs in hardware, a timer keeps exact time without any CPU load, which is why periodic work, PWM and precise measurement are built on timers, not on loops and delay().
Prescaler and compare math
The timer’s clock is the system clock divided by a prescaler (1, 8, 64, 256, 1024 on an AVR). The time of one count is the tick, and the period of an interrupt is the compare value plus one, times the tick.
tick = prescaler ÷ fclk period = (compare + 1) × tick compare = period ÷ tick − 1
Worked example: a 1 Hz interrupt on a 16-bit timer
At 16 MHz we need 1 s. Try each prescaler: counts needed = 1 s ÷ tick.
| Prescaler | Tick | Counts for 1 s | Fits in 16 bits? |
|---|---|---|---|
| 1 | 0.0625 µs | 16 000 000 | no (over 65 536) |
| 8 | 0.5000 µs | 2 000 000 | no (over 65 536) |
| 64 | 4.0000 µs | 250 000 | no (over 65 536) |
| 256 | 16.0000 µs | 62 500 | yes |
| 1024 | 64.0000 µs | 15 625 | yes |
The smallest prescaler that fits is 256 (the best resolution), giving a compare value of 62 499 counting from 0 (62 500 − 1 = 62 499). With a prescaler of 1 024 the tick of 64 µs would also work but with coarser steps.
// ATmega328P Timer1: interrupt once per second (CTC mode, prescaler 256)
cli();
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS12); // CTC mode, prescaler 256
OCR1A = 62499; // 16e6 / 256 - 1
TIMSK1 = (1 << OCIE1A); // enable the compare-match interrupt
sei();
ISR(TIMER1_COMPA_vect) { seconds++; } // keep it short
Worked example: an exact 1 kHz tick, and a note that is a little flat
- 1 kHz tick. With the 8-bit Timer0 and a prescaler of 64: tick = 64 ÷ 16 MHz = 4 µs, and 1 ms = 250 ticks, so the compare value is 250 − 1 = 249, an exact division (16 000 000 ÷ 64 000 = 250). This is the interrupt that drives millis() and the debounce routine of GPIO, Interrupts and Debouncing.
- A tone. To toggle a speaker pin at 440 Hz (the note A4) with a prescaler of 8, the pin must toggle every half period: count = 16 000 000 ÷ (2 × 8 × 440) = 2272.73, which rounds to 2 273, giving 439.95 Hz: an error of -0.21 cents (a hundred cents make a semitone; most people notice about 5 to 10 cents). Rounding error is largest for high frequencies where the count is small.
Timer modes: overflow, CTC, capture, PWM
| Mode | What it does | Use |
|---|---|---|
| Normal / overflow | Counts to the maximum (255 or 65 535), overflows to 0 and raises an interrupt | A simple periodic tick; a free-running time base |
| CTC (clear on compare match) | Counts to the compare value, then restarts | Exact periods and frequencies |
| Input capture | Copies the counter to a register when an input pin changes | Measuring pulse width, frequency and time between events with tick resolution; no ISR jitter |
| Output compare | Toggles, sets or clears a pin when the counter matches | Generating exact waveforms and delays by hardware |
| PWM (fast, phase-correct) | Counts and sets the pin high and low at compare values | PWM for LEDs, motors, servos and DACs |
| Quadrature decoder / external clock | Counts edges from an encoder or pin | Encoders and pulse counting (see GPIO, Interrupts and Debouncing) |
Capture is the accurate way to measure. To measure the period of a signal, capture the timer at two successive edges and subtract: the unsigned subtraction survives one counter overflow. The resolution is one tick, so a 4 µs tick measures a 1 kHz signal’s period (1 000 µs) to ±0.4 %.
PWM: frequency, duty and resolution
Pulse-width modulation switches a pin on and off at a fixed frequency and varies the fraction of time it is on, the duty cycle. A load that responds slowly (an LED to the eye, a motor to its inertia, an RC filter) sees the average voltage.
Vaverage = duty × Vsupply fPWM = fclk ÷ (prescaler × counts per period)
Worked example: frequency against resolution
- The Arduino default. 8-bit fast PWM (256 counts), prescaler 64 at 16 MHz: f = 16 000 000 ÷ (64 × 256) = 976.6 Hz (the familiar “about 980 Hz” of pins 5 and 6).
- A higher frequency for a motor. A 20 kHz PWM, above hearing, needs 16 000 000 ÷ 20 000 = 800 counts per period at prescaler 1: that is only log2(800) = 9.6 bits, so about 800 duty steps (0.125 % each).
- More resolution, lower frequency. 10 bits (1 024 counts) with a prescaler of 8 gives 1 953 Hz; a full 16 bits at prescaler 1 gives 244.1 Hz. Frequency and resolution trade against each other for a given clock: bits = log2(fclk ÷ fPWM).
- Phase-correct PWM counts up and down, halving the frequency (16 MHz ÷ (64 × 510) = 490 Hz here) but placing the pulses symmetrically, which is better for motors and for two PWMs that must not switch together.
- Pick the frequency for the load: well above 100 Hz for LEDs (to avoid flicker, and 1 kHz or more for cameras); 1 to 20 kHz for small motors (20 kHz is silent); tens to hundreds of kHz for switching power stages.
- Change the duty cycle by writing the compare register, not by stopping and restarting the timer, so there are no glitches. Most timers latch the new value at the end of the period.
- Dead time between the on-times of two switches in a half-bridge prevents shoot-through; use a timer with a dead-time generator for that.
- PWM is not an analog output. For a smooth voltage add an RC filter (below) or use a DAC.
- Motors and heaters need a driver stage, and a flyback path for inductive loads (see Driving Motors and Actuators).
Servo and ESC pulses
A hobby servo is not controlled by duty cycle but by pulse width: a pulse of 1 to 2 ms repeated every 20 ms (50 Hz), with 1.5 ms at the center. The same signal drives most ESCs (motor controllers).
Worked example: servo resolution
- A 16-bit timer with a prescaler of 8 at 16 MHz ticks every 0.5 µs. The 20 ms period is 40 000 counts, and the 1 ms and 2 ms pulses are 2 000 and 4 000 counts.
- Across 1 000 µs of pulse the servo travels about 90° (a typical servo; many go further), so one count is 90 ÷ 2 000 = 0.045°, far finer than the servo can resolve (its dead-band is about 1 to 5 µs, 0.1° to 0.5°).
- The Arduino
Servolibrary uses the same idea on a timer; with 8-bit timers the resolution would be coarser, so use a 16-bit timer or a PWM peripheral for precision. - Power: a servo draws hundreds of milliamps to amps when it moves; power it from its own supply with the grounds joined, not from the MCU’s regulator.
From PWM to an analog voltage
A simple RC low-pass turns a PWM into a slowly varying voltage. The cut-off frequency fc = 1 ÷ (2π R C) must be well below the PWM frequency to give a small ripple, and well above the rate at which you need the output to change.
Worked example: a PWM digital-to-analog converter
With R = 10 kΩ and C = 1.0 µF, τ = R C = 10 ms and fc = 15.9 Hz. A 20 kHz PWM at 50 % duty (5 V supply) is 1 257 times higher than fc; the filtered output is 2.5 V with a peak-to-peak ripple of about V × D (1 − D) ÷ (fPWM R C) = 5 × 0.5 × 0.5 ÷ (20 000 × 0.01) = 6.3 mV. The output takes about 5 τ = 50 ms to settle after a change of duty. A second stage reduces ripple more; a buffer amplifier stops the load from changing the divider.
Timing in software without blocking
delay() stops the program, so nothing else runs while it waits. Instead of waiting, compare a running millisecond counter against the time of the last action. Unsigned subtraction handles the counter roll-over automatically: millis() − last ≥ interval stays correct when the 32-bit counter wraps, which happens after 232 ms = 49.7 days.
const unsigned long BLINK_MS = 500, READ_MS = 100;
unsigned long lastBlink = 0, lastRead = 0;
void loop() {
unsigned long now = millis();
if (now - lastBlink >= BLINK_MS) { lastBlink += BLINK_MS; toggle_led(); } // += keeps long-term accuracy
if (now - lastRead >= READ_MS) { lastRead += READ_MS; read_sensor(); }
}
- Add the interval, do not reset to now:
last += intervalkeeps the average period exact even when a pass is late;last = nowaccumulates the lateness. - Time stamps: micros() overflows every 71.6 minutes (232 µs): use it only for short intervals.
- Short delays of a few microseconds (waiting for a sensor, a bit-banged protocol) are fine as busy waits in a driver function; keep them under about 100 µs.
- Jitter: a loop-based schedule runs a task late when the loop is busy. If timing must be exact, use a timer interrupt or a hardware timer output, or an RTOS with priorities (see Firmware Architecture, State Machines and RTOS).
Clock accuracy and drift
| Clock source | Typical accuracy | Error per day | Notes |
|---|---|---|---|
| Internal RC oscillator | ±1 to ±5 % (factory trimmed ±1 %) | 14 to 72 minutes | Drifts with temperature and voltage; fine for timing loops, not for UART at high speed or time-keeping |
| Ceramic resonator | ±0.5 % | 7 minutes | Cheap and good enough for UART |
| Quartz crystal (20 to 50 ppm) | about 0.002 to 0.005 % | 4.3 s at 50 ppm | The normal choice; temperature affects it |
| 32.768 kHz watch crystal (RTC) | 10 to 20 ppm at 25 °C | 1 to 2 s | Low power, used for a real-time clock; the error is largest at temperature extremes |
| TCXO / GPS-disciplined | 0.5 to 2 ppm, or better | under 0.2 s | Time and frequency standards |
A 50 ppm clock is off by 50 × 10-6 × 86 400 s = 4.3 s per day, about 26.3 minutes a year. A device that must keep the right time over months needs a time source (GPS, network time) to correct it, or a better oscillator.
Related
- Embedded Systems: the overview of this guide.
- GPIO, Interrupts and Debouncing: interrupts and debouncing.
- Analog Input and Signal Processing: sampling at a steady rate from a timer.
- Firmware Architecture, State Machines and RTOS: schedulers built on a timer tick.
- Analog Circuits and Sensing and Motion Control and Actuators: filters and motor drives.
FAQ
Why is my PWM at a different frequency than I expected?
The library may use a different timer mode or prescaler than you assumed (Arduino sets different defaults on different pins). Read the timer registers, check the library source and measure with an oscilloscope or logic analyzer.
What is the difference between millis() and a timer interrupt?
millis() is itself driven by a timer interrupt (about every millisecond); it gives a time you can compare. A timer interrupt of your own runs code at exact intervals regardless of what the main loop is doing.
How do I get more PWM channels than there are timers?
Each timer provides several compare channels at the same frequency. For more, use a dedicated PWM driver chip over I²C (such as a PCA9685) or a bit-banged software PWM at a low frequency.
Why does my servo jitter?
Noise on the signal, an unstable supply, or pulses generated by software with variable timing. Use hardware timer pulses, a separate servo supply with the grounds joined, and a decoupling capacitor.
References
- Microcontroller datasheets (timer/counter chapters; prescaler, CTC, PWM modes) for the ATmega328P, RP2040 and STM32.
- Hobby servo and ESC manufacturers’ data (pulse widths and rates); Horowitz and Hill, The Art of Electronics (RC filters).
- White, E., Making Embedded Systems, O’Reilly (timers and time-keeping).
Last reviewed: October 2026. Register names and counts are for the ATmega328P at 16 MHz: check your own chip’s datasheet.
