LECTURE 06

Timer Modules

Duration: 121 min of teaching Level: undergraduate, year 2 - recommended after Lecture 05 Discipline: Microcontrollers and Microprocessors Associated laboratory: Laboratory 04 PDF: download the notes RO versiunea română

Until now, time has existed only indirectly, as the duration consumed by the executed instructions. Almost any real application, however, needs time as a quantity it can measure and impose: a motor needs an adjustable average power, an ultrasonic sensor needs a pulse width with microsecond precision, a communication protocol needs the line sampled exactly at the middle of every bit. For all of this, the microcontroller has a dedicated hardware block, at its core a simple counter that advances on its own, in parallel with the program - the timer module.

1Purpose and structure of the course6 min

A timer counts pulses received on its clock input, with no instruction being executed by the processor for it. Once started, it advances regardless of whether the central unit is computing, waiting, or in a low-power mode - one of the few peripherals that offers a measure of time independent of what the software is doing. On an Arduino Uno board, the clock is 16 MHz, so one pulse every 62.5 ns: the number accumulated in the register translates directly into a duration.

Recap from Lecture 05
  • A handler routine must be short, with no delays, and communicates through volatile variables (Lecture 05)
  • A variable larger than one byte needs a critical section for an atomic read (Lecture 05)
Both rules apply identically to timer routines, which are the most frequent type of interrupt used in a real application.
  • Explain the difference between the timer mode and the counter mode of the same hardware module
  • Distinguish the normal, CTC, fast PWM and phase-correct PWM modes
  • Calculate, from the clock frequency, the prescale factor and the compare value, the period or frequency of an interrupt or of a PWM signal
  • Explain the relationship between the duty cycle and the average value of a PWM signal
  • Explain why the capture unit gives a precision an ordinary interrupt routine cannot reach

2Timer or counter, and why delay() is a poor solution11 min

Timer or counter

We speak of a timer when the clock signal has a uniform distribution over time (a clock with a known period) - the accumulated number is proportional to elapsed time. We speak of a counter (event counter) when the signal comes from outside, with a random distribution - pulses from an encoder, an optical gate. The hardware is identical: on the ATmega328P, the clock-source selection bits switch the same module between the two modes - hence the official name, Timer/Counter.

Why delay() is the worst solution

delay(1000) runs a loop that repeatedly reads the millisecond counter, exiting when it has increased by a thousand - millions of instructions spent doing nothing. Worse: while the program waits, it cannot respond to anything else - a button pressed and released in this interval will not be noticed, a serial character may be lost.

Blocking timing and its non-blocking equivalent
/* blocking: the processor is tied up for 500 ms */
void loop() {
    digitalWrite(LED, HIGH); delay(500);
    digitalWrite(LED, LOW);  delay(500);
}

/* non-blocking: the loop runs thousands of times a second */
unsigned long lastToggle = 0;
const unsigned long PERIOD = 500;
void loop() {
    unsigned long now = millis();
    if (now - lastToggle >= PERIOD) {
        lastToggle = now;      /* the moment is stored, not accumulated */
        ledState = !ledState;
        digitalWrite(LED, ledState);
    }
    /* buttons, sensors, serial communication can be handled here */
}
Even the non-blocking variant would not exist without a timer millis() returns a counter incremented by a hardware timer's interrupt routine. The difference is that the program consults time instead of waiting for it. The next step, covered in this lecture, is to let the timer announce the program on its own, through an interrupt, or even generate the desired signal directly at the pin, with no processor involvement at all.

3Classifying timer modules11 min

A function-oriented classification distinguishes five structures, often sharing hardware.

The plain timer counts the internal clock and resets on overflow, signaling through a flag and/or an interrupt. The counting range is [0, 2ⁿ-1] - an 8-bit counter at 16 MHz overflows in 16 µs, a 16-bit one in 4.096 ms, not enough to time a second without prescaling.

The event counter is the same module, with the clock switched to an external pin - it no longer measures time, it accumulates events. Combining the two modes (a timer fixes a one-second window, a counter accumulates pulses in it) directly gives the frequency of a signal, in hertz.

The PWM generator produces, entirely in hardware, a signal with a constant frequency and an adjustable duty cycle - discussed at length below.

The watchdog timer counts continuously, independent of the program; the program is obligated to reinitialize it periodically. If it gets stuck in an infinite loop or in an interrupt it never exits, reinitialization no longer happens, and the timer generates a reset that restarts the system into a known state.

The RTC (Real-Time Clock) keeps the time and date over the long term, off a 32.768 Hz crystal (=2¹⁵, so that successive division by two, through 15 flip-flops, gives exactly one pulse per second, with no rounding error). The ATmega328P has no full RTC, but can approximate the function with Timer2 in asynchronous mode.

ModuleResolutionPrescale factorsDependent library functions
Timer08 bits1, 8, 64, 256, 1024millis(), micros(), delay(), analogWrite() on pins 5, 6
Timer116 bits1, 8, 64, 256, 1024the Servo library, analogWrite() on pins 9, 10
Timer28 bits1, 8, 32, 64, 128, 256, 1024tone(), analogWrite() on pins 3, 11
Reconfiguring a timer produces no visible error

The Arduino core configures Timer0 with a prescale of 64 and enables its overflow interrupt, which feeds millis(), micros() and delay(). If the program changes Timer0's prescale for a PWM signal of a different frequency on pin 5, it indeed obtains that frequency, but it simultaneously speeds up or slows down the millisecond counter - all timing based on millis() becomes wrong, with no error message. The rule: on an Arduino board, leave Timer0 alone; manual configuration experiments are done on Timer1 or Timer2. Only Timer1 has a capture unit and 16-bit compare registers - the only one suited for fine-grained time measurement.

4Operating modes: normal, CTC, fast PWM, phase-correct PWM11 min

The counter's behavior between two returns to zero defines the operating mode, chosen through the WGM bits in TCCRnA/TCCRnB. The difference concerns the maximum value (TOP), the counting direction and the moment the output switches.

Normal mode
The counter increases from 0 to 2ⁿ-1, returns to zero and flags the overflow. The period is fixed, determined only by the clock and the resolution - changing it requires preloading the counter in every routine, an operation that introduces an error dependent on interrupt latency.
CTC mode (Clear Timer on Compare Match)
The counter increases until it matches OCRnA, at which point it is automatically cleared by hardware. TOP is taken over by the compare register - the period becomes programmable in steps of one clock pulse, and the hardware clearing does not depend on interrupt timing, so it does not accumulate error from one period to the next. The mode suited for exact periodic interrupts.

Fast PWM: the counter continuously runs 0 → TOP; the output is active at the start of the cycle, inactive at the match with the compare register. The counter traverses the range only once per period, so the frequency is the highest possible for a given resolution.

Phase-correct PWM: the counter rises to TOP, then falls to zero; the output toggles at every crossing of the compare value, once on each ramp. The resulting pulse is centered on the middle of the period, regardless of the duty cycle - hence the name: the phase does not shift when the pulse width changes. The price: the period becomes roughly double, the frequency is halved compared to fast PWM.

When fixing the phase matters Fast PWM suits the vast majority: lighting, fans, simple motors. Phase-correct PWM is preferred where a shift in the pulse center would cause disturbances - brushless motors, H-bridges with complementary drive, audio applications.
Match the operating mode with the right application

5Ways of accessing timer data9 min

There are three ways the software gets in touch with the timer module.

Directly reading the counter register

uint8_t value8 = TCNT0;    /* Timer0's 8-bit counter */
uint16_t value16 = TCNT1;  /* Timer1's 16-bit counter */

Does not disturb the counter - a simple snapshot. Useful for timing a code sequence (read before and after) or short timings, without overflow.

Reading a 16-bit register on an 8-bit machine

TCNT1 is read through two successive instructions (the low byte, then the high one). The hardware partially solves the problem through a buffer register: when the low byte is read, the high byte is automatically copied into a buffer, read right after - but this mechanism only works if the two reads are not separated by an interrupt that in turn accesses another 16-bit register of the same timer, because the buffer is shared. Access from the main program to a 16-bit register of Timer1, if an interrupt accesses the same timer, must be protected by temporarily disabling interrupts.

Polling the status flags

while (!(TIFR1 & (1<<TOV1))) { ; }   /* empty loop: cycles are wasted */
TIFR1 = (1 << TOV1);                  /* clearing is done by writing 1 */

Maximum precision (a reaction within a few cycles, with no interrupt latency), but while the loop spins, the processor can do nothing else - exactly the flaw of delay(), more visible. Useful in short, critical sequences that cannot accept the latency of an interrupt.

Using interrupts - the preferred way

Three conditions, all mandatory: individually enabling the source (TIMSK0/1/2), the global enable (sei()), and a routine written for the correct vector - a vector with no routine leads to the microcontroller resetting. Three categories of timer interrupts: Overflow (on overflow), Compare Match (on matching the compare register, evenly spaced in CTC mode) and Input Capture (on an edge on the capture pin, with the counter automatically stored - the final section of the lecture).

6The prescaler circuit and the basic formulas10 min

An 8-bit counter fed directly with the 16 MHz clock overflows every 256×62.5 ns = 16 µs, that is 62,500 times a second - practically unusable for timing, and on every overflow the processor spends cycles on context. The solution: the prescaler circuit.

The prescaler is a chain of flip-flops that successively divide the frequency by two; a multiplexer picks the stage. The available values are of the form 2^P: for Timer0 and Timer1, 1, 8, 64, 256, 1024; for Timer2, additionally 32, 128.

The basic formulas
Duration of one counter tick: T_tick = N/f_clk.
CTC mode, event period: T = N·(OCRnA+1)/f_clk, hence f = f_clk / (N·(OCRnA+1)).
Compare value needed for an imposed period: OCRnA = f_clk/(N·f) - 1.
The prescaler trade-off

A large factor extends the time to overflow (long timings with a small counter), but worsens the resolution: if a pulse lasts 64 clock periods, the timing cannot be distinguished more finely than 64 periods (4 µs at 16 MHz). The practical rule: the smallest prescale factor that still covers the desired interval.

Worked exercise - an exact interrupt every millisecond

ATmega328P, 16 MHz. Configure a timer for an interrupt every millisecond.

See the solution

Clock pulses in one millisecond: f_clk/f = 16×10⁶/1000 = 16000.

With no prescaling (N=1): OCRnA = 16000-1 = 15999 - does not fit in 8 bits, but fits comfortably in Timer1's 16 bits (maximum resolution, 62.5 ns).

With an 8-bit timer, OCRnA ≤ 255: look for the smallest N with 16000/N - 1 ≤ 255, that is N ≥ 62.5. From the list, the first that satisfies it: N = 64. OCRnA = 16×10⁶/(64×1000) - 1 = 250 - 1 = 249 - an integer, fits in 8 bits, an exact period, with no error. Check: 250 × 4 µs = 1000 µs. ✓

Prescaler calculator: what value do I write into OCRnA?
The counter, the prescaler and the compare value

7Application: a one-millisecond time base6 min

volatile uint32_t milliseconds = 0;

void setupTimeBase(void) {
    cli();
    TCCR1A = 0x00;
    TCCR1B = (1 << WGM12);              /* CTC mode, TOP given by OCR1A */
    OCR1A = 249;                         /* 16 MHz / (64*250) = 1000 Hz */
    TCCR1B |= (1<<CS11)|(1<<CS10);      /* prescaler 64: starts the counter */
    TIMSK1 = (1 << OCIE1A);
    TCNT1 = 0;
    sei();
}
ISR(TIMER1_COMPA_vect) { milliseconds++; }
Why the clock-select bit is written last Writing it actually starts the counter - if the module started before configuration was complete, the first period would have the wrong duration. The result, compared with the overflow interrupt with no prescaling: the frequency dropped from 62,500 to 1000 per second - the load on the processor was cut by more than sixty times, and the period is exactly the desired one.

8Characteristics of periodic signals8 min

A signal x(t) is periodic, with period T, if x(t) = x(t+T) for any t. The frequency is the number of repetitions per second: f = 1/T.

For a rectangular signal, the duty cycle is the ratio between the duration of the active state T_on and the period: DC = (T_on/T)×100 [%]. 0% = permanently inactive, 100% = permanently active, 50% = symmetric.

Why the duty cycle is the quantity that matters If the frequency is high enough relative to the load's inertia, the load no longer perceives the individual switches, only the average value: V_avg = Vcc × DC/100. In a DC motor, mechanical inertia and the winding's inductance smooth the current - the speed is adjusted continuously through the duty cycle. In an LED, the eye integrates light over ~20 ms - above ~100 Hz, the switching becomes invisible (below this threshold flicker appears; that is why Arduino's default PWM frequencies, ~490 and ~980 Hz, are above the threshold).
Efficiency, not just convenience, is the real reason for PWM

The same power reduction could be obtained with a series resistor or a transistor in linear mode, but the difference in power would turn into heat. The PWM switch is either fully open (zero current) or fully closed (nearly zero voltage drop) - the power dissipated by it is small in both states. That is why PWM regulates powers of tens or hundreds of watts with a transistor that barely gets warm. Important: pulse-width modulation does not change the amplitude, only the average value - the load needs enough inertia to average it; applied to a fast load, with no filtering, a PWM signal remains a series of full pulses.

9Fast PWM and phase-correct PWM9 min

Duty cycle and average voltage - move the slider

In fast PWM, the counter continuously increases 0→TOP; the pin becomes active on the return to zero, inactive on the match with the compare register.

The fast PWM formulas
Frequency: f_PWM = f_clk / (N·(TOP+1)).
Duty cycle: DC = (OCRnx+1)/(TOP+1) × 100%.
Resolution (steps): R = log₂(TOP+1) bits.
With TOP=255, N=64, f_clk=16 MHz: f_PWM ≈ 976.6 Hz - exactly the frequency of analogWrite() on the Uno's pins 5 and 6, with 8 bits of resolution (256 steps).

The product of the PWM frequency and the number of steps is constant, equal to f_clk/N - a high frequency and a high resolution cannot both be obtained at once without raising the clock or reducing the prescale.

In phase-correct PWM, the counter traverses the range twice per period (up, down): f_PWM = f_clk / (2·N·TOP). On 8-bit AVR modules, the number of pulses per period is 510 (=2×255) - with N=64, this gives ≈490.2 Hz, the frequency of analogWrite() on pins 3, 9, 10, 11: roughly half of fast PWM, at the same resolution - the price paid for fixing the phase.

The three ways to change the PWM frequency The prescale factor (convenient, but discrete values, powers of two). The TOP value, where it is a register (near-continuous frequency adjustment, but reduces the duty-cycle resolution - the number of steps is exactly TOP). The system clock frequency (affects everything time-dependent - communication speeds, library functions - used only with solid justification, usually for reducing power draw).

10Implementing PWM and the link to analogWrite()9 min

Fast PWM on Timer2, with an adjustable duty cycle
void setupPWM2(void) {
    DDRB |= (1 << PB3);       /* OC2A = pin 11 on the Uno */
    /* WGM21:WGM20=11 -> Fast PWM, TOP=0xFF; COM2A1:COM2A0=10 -> non-inverted */
    TCCR2A = (1<<WGM21)|(1<<WGM20)|(1<<COM2A1);
    TCCR2B = (1 << CS22);     /* prescaler 64: 16MHz/(64*256) = 976.6 Hz */
    OCR2A = 25;                /* 26/256 ~ 10% duty cycle */
}
void setDutyCycle(uint8_t percent) {
    OCR2A = (uint16_t)percent * 255 / 100;
}

The COM2A1:COM2A0 bits choose a non-inverted output (active from the start of the period until the match), inverted (a complementary pair, a load driven active-low), or disconnected (the pin stays an ordinary GPIO, but the module keeps counting and can still generate interrupts).

What analogWrite() does

Identifies the timer associated with the requested pin, enables the compare block (the COM bits) and writes the argument into the compare register - the name is poorly chosen: the output is not analog but digital, switching between 0 V and 5 V, only the average value varies continuously. The argument has 8 bits because the 8-bit timers' compare registers have 8 bits - a hardware limitation, not one of the function. The frequency cannot be changed through analogWrite() (it does not touch the prescale bits). Only six pins on the Uno support it, because there are only six compare outputs in hardware.

Buffer registers avoid distorting the pulse on a "mistimed" write

If the compare value were changed exactly when the counter had just passed it, the current pulse would be distorted, could be missing, or could extend over a whole extra period. AVR modules use buffer registers: the written value is held and transferred into the active register only at the start of a new period - the mechanism eliminates most timing errors, but not all; in modes with a variable TOP, a change at the wrong moment can still produce a cycle of the wrong duration.

11Capturing external events13 min

The reverse problem of generation: determining the exact moment an external event occurred, solved by the capture unit (input capture).

Pin ICP1 (digital pin 8 on the Uno) is continuously watched by hardware. On an edge of the configured direction, the instantaneous value of the counter is automatically copied into ICR1, the ICF1 flag is set and, if enabled, an interrupt request is generated.

Why capture is more accurate than reading the counter in an ordinary routine The storing happens at the moment of the edge, not at the moment the program gets around to reacting. In an ordinary external interrupt, the routine would read the counter itself - the value would be affected by interrupt latency (finishing the current instruction, saving the context, a possible wait behind another routine), which varies by dozens of cycles and cannot be compensated by calculation. With hardware capture, the interrupt can be delayed however much - as long as the routine reads ICR1 before the next edge, the value stays accurate down to a single clock pulse.

The rule for choosing the prescaler: the timing error grows directly with the prescale factor - for captures, the smallest possible prescaler that does not exceed the measurement range is chosen (Δt_max = (2^r - 1)·N/f_clk).

Application - the HC-SR04 ultrasonic sensor

The sensor emits a burst of ultrasound and activates an output line for exactly as long as the round trip of the wave takes. Distance: d = v_sound·Δt/2, with v_sound ≈ 343 m/s.

ISR(TIMER1_CAPT_vect) {
    if (TCCR1B & (1<<ICES1)) {      /* rising edge */
        capture1 = ICR1;
        TCCR1B &= ~(1<<ICES1);      /* next: falling */
    } else {                          /* falling edge */
        width = ICR1 - capture1;
        TCCR1B |= (1<<ICES1);
        ready = 1;
    }
    TIFR1 = (1 << ICF1);
}

The usual beginner solution, pulseIn(), blocks the processor for tens of milliseconds and is affected by any interrupt occurring during that interval; with the capture unit, the measurement is done in hardware, and the program loses only a few microseconds in the two routines.

See the resolution and range calculation (prescaler 8)

T_tick = 8/16×10⁶ = 0.5 µs. One tick corresponds to Δd = 343×0.5×10⁻⁶/2 ≈ 0.086 mm - far below the sensor's own accuracy, so temporal resolution is not the limiting factor.

The maximum range: Δt_max = 65535×0.5 µs ≈ 32.8 ms, that is d_max ≈ 5.6 m - above the sensor's useful range (a few meters). Prescaler 8 is suitable: needlessly good resolution, sufficient range. With prescaler 1, the range would drop to 4.1 ms (~70 cm) - not enough.

Unsigned 16-bit subtraction and a flag that can set itself

ICR1 - capture1 under unsigned arithmetic stays correct even if the counter has wrapped through zero between the two captures. ICF1 is explicitly cleared after switching the edge direction, because the switching operation can itself set the flag, producing a false interrupt right after leaving the routine.

Measuring distance with the HC-SR04 through capture: put the steps in order

12Frequent mistakes4 min

  • "On an Arduino board I can reconfigure any timer, including Timer0, with no side effects." False - Timer0 feeds millis(), micros() and delay(). Reconfiguring its prescaler changes every timing based on millis(), with no visible error message. Manual timer configuration experiments are done on Timer1 or Timer2, not on Timer0.
  • "analogWrite() gives a real analog output." False - the output switches digitally between 0 V and 5 V; only its average value, as perceived by a load with inertia, varies continuously with the duty cycle. Treat PWM as a digital signal with a variable duty cycle, not as a real analog voltage.
  • "To measure the width of a pulse, I read the counter right inside the external interrupt routine triggered by the edge." False - the value read is affected by interrupt latency (finishing the current instruction, saving the context), which varies by dozens of cycles and cannot be compensated. Use the capture unit (Timer1 + ICP1), which stores the counter in hardware, exactly at the moment of the edge.

13Summary and glossary5 min

A timer is, at its core, a hardware counter fed through a prescaler from a stable clock source. Three mechanisms derive from it: comparing the counter to a threshold and clearing it automatically (CTC mode) gives exact periodic interrupts, replacing delay(); driving a pin based on the same comparison gives a PWM signal with an adjustable duty cycle, with much better efficiency than lowering the voltage with a series resistor; storing the counter on an external edge (input capture) gives exact timestamping of events, with a precision software alone cannot reach. The obtained period or frequency always results from three numbers - the clock frequency, the prescale factor, the compare value - and choosing them is a trade-off between resolution and range. Timer modules are resources shared with the Arduino libraries: any reconfiguration must be done knowing which functions, from that moment on, stop telling the truth.

Prescaler
a frequency divider between the system clock and the counter input.
TOP
the maximum value the counter reaches before returning to zero.
CTC mode
Clear Timer on Compare Match - the counter clears itself automatically on matching the compare register.
Duty cycle
the ratio between the duration of the active state and the total period of a rectangular signal.
Input capture
automatically storing, in hardware, the counter value when an external edge occurs.

14Self-check questions7 min

  1. Explain the difference between the timer mode and the counter mode of the same hardware module. What changes in the configuration when moving from one to the other?
  2. Why is delay() a waste of resources? Concretely describe what is lost during the wait.
  3. An 8-bit counter is fed directly with the 16 MHz clock. After how long does the overflow occur, and how many times a second would the interrupt routine be called?
  4. Why are the available prescale factors powers of two, not arbitrary values? What is lost with a prescale factor larger than needed?
  5. An interrupt is needed every 10 ms, with Timer1 at 16 MHz, in CTC mode. Calculate OCR1A for a prescale factor of 64 and check whether the result is achievable.
  6. Compare normal mode with CTC mode from the point of view of the precision of the generated intervals.
  7. A PWM signal has a period of 2 ms and an active state of 0.4 ms. Calculate the frequency, the duty cycle, and the average voltage at an amplitude of 5 V.

15Directions for further study2 min

The next lecture uses exactly the same compare mechanism of a timer, applied to the reverse problem of generation - turning a continuous analog voltage into a number: the analog-to-digital converter.

The prescaling formulas, PWM generation and event capture from this lecture become a real setup in Laboratory 04.