Embedded

Timers

A hardware timer is a counter built into the chip that keeps ticking on its own, independent of whatever the CPU is doing — and can take an action (fire an interrupt, flip a pin, capture a value) the instant it reaches a target count. It's how firmware gets precise timing without burning CPU cycles in a busy-wait loop.

Quick reference
What it is
A hardware counter, clocked independently of the CPU, that can trigger an interrupt, toggle a pin, or capture a value when it reaches a target count.
Mental model
A stopwatch built into the chip — it keeps counting in the background and can tap the CPU on the shoulder, or wiggle a pin by itself, without anyone watching it.
Key numbers
Counter width (8/16/32-bit), a prescaler that divides the input clock, and a reload/period register (ARR) that sets where it wraps.
Reach for it when
You need a fixed-rate event, a precise delay, a PWM output, or to measure the timing of an external signal — all without polling.

Why not just a software delay loop?

A for loop that counts to some number and calls it "a delay" ties up the CPU for the entire wait, can't do anything else while it waits, and drifts with clock speed, compiler optimization level, and whatever else changes about the build. A hardware timer solves all three: it counts using dedicated silicon while the CPU is completely free to do other work, it only interrupts the CPU at the exact moment something needs to happen, and its timing is derived directly from a known clock source instead of "however many loop iterations happened to take."

Anatomy of a hardware timer

Strip away the vendor-specific register names and every hardware timer is built from the same handful of parts, chained together:

A timer clock source feeds a prescaler, which feeds a counter register, which is compared against an auto-reload register — a match resets the counter, fires an interrupt, and can toggle an output pin Clock source e.g. 84 MHz bus clock Prescaler divide by (PSC + 1) Counter (CNT) increments each tick Compare vs. ARR match! reset CNT to 0, repeat Interrupt fires "update event" — runs the ISR Output pin toggles (PWM) Both outcomes are optional and independently configurable — a timer can fire an interrupt, drive an output pin, both, or neither (just free-running).
Every hardware timer is this same pipeline under the hood, whatever the vendor calls the registers: a clock, a divider, a counter, and a comparison that resets the cycle.
Clock source
The input clock feeding the timer — often the CPU's own bus clock, sometimes a separate low-power oscillator for timers that need to keep running in sleep modes.
Prescaler (PSC)
Divides the input clock down before it reaches the counter — without it, most timers would wrap around far too fast to be useful for anything but the shortest intervals.
Counter (CNT)
The actual register that increments (or on some timers, decrements) once per prescaled tick. Reading it tells you the timer's current value at that instant.
Auto-reload register (ARR)
The "period" register — the value the counter counts up to before wrapping back to zero. This is what turns a free-running counter into a fixed-frequency generator.
Compare/capture register (CCR)
A second, independently-set value the counter is compared against mid-cycle — the basis for PWM duty cycle and for timestamping an external edge (input capture).

The frequency a timer actually produces follows directly from these parts:

f_timer = f_clk / ((PSC + 1) * (ARR + 1))

The +1 on both terms trips almost everyone up at least once — both the prescaler and the reload register are typically zero-indexed in hardware (a PSC of 0 means "don't divide at all," not "divide by zero"), so the actual divisor is always one more than the register value.

Timer modes

One-shot
Counts once from zero to ARR, fires its event, then stops — used for a single delayed action ("do this in exactly 200 µs, once").
Periodic (up-counting)
Counts to ARR, fires its event, automatically resets to zero, and keeps going — the default mode for anything that needs to happen at a fixed rate.
Output compare / PWM
Drives a physical output pin based on comparing CNT against a CCR value each cycle — see below.
Input capture
Instead of driving a pin, the timer latches its current CNT value into a CCR the instant an edge arrives on an input pin — see Input capture.

Generating PWM with a timer

PWM output is just the compare step from the anatomy diagram wired to a pin instead of (or in addition to) an interrupt: the pin goes high when the counter resets to zero, and drops low the instant the counter reaches the compare value (CCR). Because the counter's period is fixed by ARR, the fraction of each cycle spent high — the duty cycle — is just CCR / (ARR + 1), set purely by changing one register with no CPU involvement once configured.

A sawtooth counter waveform compared against a fixed threshold produces a PWM square wave whose high portion sets the duty cycle counter value (CNT) CCR 0 ARR PWM output pin time → high (CNT < CCR) low (CNT ≥ CCR) one full period = (ARR + 1) ticks
Every time the sawtooth counter resets, the output pin goes high; the instant it crosses CCR, the pin drops low. Moving CCR up or down changes the duty cycle without touching ARR (and therefore without changing the PWM frequency at all).

This is the exact same mechanism behind using PWM as a poor man's DAC — a fixed-frequency square wave whose average voltage, after filtering (or just the physical inertia of a motor or the persistence of vision of an LED), tracks the duty cycle.

Step by step: configuring a timer in C

Exact register names differ by vendor (this reads like a generic Cortex-M/STM32-style timer; an 8-bit AVR or a PIC will use different register names for the same five ideas), but the sequence is always the same shape:

  1. Decide the target frequency or period. Say we want a periodic interrupt every 1 ms (1 kHz), and the timer's input clock is 84 MHz.
  2. Solve for PSC and ARR. Pick a prescaler that gets the post-divide clock to a convenient round number, then let ARR set the actual period: 84,000,000 / (83 + 1) = 1,000,000 ticks/second (1 µs per tick), then ARR = 999 gives exactly 1000 ticks → 1 ms per period.
  3. Enable the timer's clock. On most MCUs the timer peripheral is clock-gated off by default to save power — nothing else works until this bit is set.
  4. Write PSC and ARR, then enable the update interrupt and unmask it in the interrupt controller (see Interrupts for what that unmasking step is actually doing).
  5. Start the timer by setting its enable bit — from this point the CPU is completely uninvolved until the ISR fires.
  6. Write the ISR: clear the interrupt flag first (skipping this is the single most common timer bug — see Gotchas), then do the minimum work needed and return.
// Target: 1 kHz periodic interrupt from an 84 MHz timer clock.
// f_timer = f_clk / ((PSC+1) * (ARR+1))
// 1000 Hz = 84,000,000 / ((83+1) * (999+1))

#define TIMER_PSC_VALUE   83     // 84 MHz / 84 = 1 MHz tick rate (1 us/tick)
#define TIMER_ARR_VALUE   999    // 1000 ticks per period = 1 ms period

void timer_init_1khz(void) {
    TIMx_CLOCK_ENABLE();               // step 3: power the peripheral

    TIMx->PSC = TIMER_PSC_VALUE;       // step 4: prescaler
    TIMx->ARR = TIMER_ARR_VALUE;       // step 4: period
    TIMx->CNT = 0;                     // start counting from zero

    TIMx->DIER |= TIM_DIER_UIE;        // step 4: enable "update" interrupt
    NVIC_EnableIRQ(TIMx_IRQn);         // step 4: unmask it in the NVIC

    TIMx->CR1 |= TIM_CR1_CEN;          // step 5: start the timer
}

volatile uint32_t ms_ticks;

void TIMx_IRQHandler(void) {
    TIMx->SR &= ~TIM_SR_UIF;          // step 6: MUST clear the flag first
    ms_ticks++;                        // step 6: keep it short — see Interrupts
}
What I learned

Most vendor HALs (STM32 HAL, NXP MCUXpresso, Microchip Harmony…) wrap exactly this sequence behind a config-struct-and-init-function API — learning the raw register version once makes every HAL's timer API make sense immediately, instead of feeling like a pile of unexplained struct fields.

Input capture: measuring a signal

Run the same hardware in reverse: instead of the timer driving a pin, an external signal edge (rising, falling, or both) tells the timer to latch its current CNT value into a capture register, without any CPU involvement or jitter from software reaction time. Two captures on successive edges, subtracted, give the period of an incoming signal directly in timer ticks — the standard way to measure the frequency of an unknown signal or the pulse width of something like an ultrasonic distance sensor's echo pin.

Real-world applications

Gotchas

Field notes