UNIT 08 · LESSON 4 OF 6

Periodic Events and Output Compare

How do you make things happen at exact times, over hours and to the microsecond?

INTERACTIVEOutput compare toggles a pin in hardware
A timer counter, its compare level and the output pin toggling at each matchARRCCRpinoutput = 1 MHz / (2 × 1000) = 500 Hz; each edge happens exactly at a compare match, 1µs resolution, with no software involved.
A timer counter, its compare level and the output pin toggling at each matchARRCCRpinoutput = 1 MHz / (2 × 1000) = 500 Hz; each edgehappens exactly at a compare match, 1 µs resolution,with no software involved.

Try this

Counter tick
ARR
Toggle output at 500 Hz.

In toggle mode the timer flips its output pin each time the counter matches the compare register (CCR). With the counter wrapping at ARR, that happens once per counter period, so the output is a square wave at f_tick/(2(ARR + 1)); CCR only sets the phase. No software runs at the edge, so its timing does not depend on interrupts.

What you will be able to do
  • Set up a periodic interrupt from a timer and explain why re-arming it in software can drift.
  • Schedule repeated events start-to-start rather than end-to-start and explain the difference.
  • Compute the frequency of an output-compare toggle signal and explain what CCR controls.
  • Generate several independent periods from one free-running counter by advancing each compare value.
  • Estimate the jitter of a software-generated edge and decide when hardware output compare is needed.
Before you start
  • Prescaler, auto-reload and update events (lesson 2).
  • Wrap-safe time arithmetic (lesson 3).
Steps in this lesson
  1. Periodic interrupts, and how they drift
  2. Output compare: the timer changes the pin
  3. Many periods from one counter
  4. Software edges jitter
  5. Worked example: why the logger lost 2000 samples
  6. Common misconceptions

The puzzle

A data logger should take a sample every 10 ms. After an hour it has taken 358 000 samples instead of 360 000; the missing ones vanished a few microseconds at a time. A second product toggles a pin in an interrupt to make a 1 kHz reference signal, and on a scope its edges wobble. How do you make things happen at exact times, over hours and to the microsecond?

STEP 1

Periodic interrupts, and how they drift

The simplest periodic event is the timer’s update interrupt (lesson 2): the hardware wraps the counter every period and raises an interrupt, and its timing is exact because nothing in software restarts it. Drift creeps in when software re-arms the timer after doing its work: “run, then wait 10 ms” makes the period 10 ms plus the time the work took.

The pico-sdk’s repeating timers make the choice explicit: a positive delay means from the end of one callback to the start of the next, a negative delay means from start to start. Only start-to-start keeps the long-term rate:

Tend-to-start=T+twork,T_{\text{end-to-start}} = T + t_{\text{work}}, Tstart-to-start=TT_{\text{start-to-start}} = T

STEP 2

Output compare: the timer changes the pin

A timer’s compare channels compare the counter with a compare register (CCR) and act on a match without software: raise an interrupt, and optionally set, clear or toggle an output pin. In toggle mode the pin flips at each match, once per counter period, so it makes a square wave at half the update rate:

fout=ftick2 (ARR+1)f_{\text{out}} = \frac{f_{\text{tick}}}{2\,(\text{ARR} + 1)}

↑ This step uses the figure at the top of the page.

CCR does not change the frequency; it places the edges within the period, so two channels with different CCRs give the same frequency with a phase shift.

STEP 3

Many periods from one counter

With a free-running counter (ARR at its maximum), each channel can keep its own period: in the compare interrupt, advance that channel’s CCR by its interval. The next match is then exactly one interval after the previous match, not after the moment the interrupt ran, so there is no drift and interrupt latency does not accumulate. Two conditions: compare preload must be off (STM32: OCxPE = 0), so the new CCR takes effect at once rather than at the next update, and the interval must be longer than the handler’s worst-case latency, or the counter passes the new value and a whole wrap is lost:

void TIM3_IRQHandler(void) {         /* one vector for all of TIM3's events */
    if (!(TIM3->SR & TIM_SR_CC1IF)) return;
    TIM3->SR = ~TIM_SR_CC1IF;         /* clear the flag */
    TIM3->CCR1 = (uint16_t)(TIM3->CCR1 + INTERVAL_1);   /* wraps with the 16-bit counter */
    do_task_1();
}

The RP2040’s timer offers the same idea with four alarms that match the low 32 bits of its microsecond counter.

STEP 4

Software edges jitter

If an interrupt handler toggles the pin instead of the hardware, every edge is delayed by the interrupt latency, and that latency varies: short when the core is idle, longer when another handler of equal or higher priority is running.

INTERACTIVEWhy software edges wobble
The spread of edge delays for a software-toggled pin compared with hardware output compareedge delay after the compare match, 40 edges0 µs11 µs22 µsdelays from 0.4 to 18.2 µs: jitter 17.8 µsA slow handler elsewhere in the system decides when your edge appears. Use outputcompare or PWM hardware for edges that must be precise.
The spread of edge delays for a software-toggled pin compared with hardware output compareedge delay after the compare match, 40 edges0 µs11 µs22 µsdelays from 0.4 to 18.2 µs: jitter 17.8 µsA slow handler elsewhere in the system decides whenyour edge appears. Use output compare or PWMhardware for edges that must be precise.
Software toggle: 17.8 µs of jitter.

Toggling a pin from a timer interrupt adds the interrupt latency to every edge, and that latency varies: most of the time it is a fraction of a microsecond, but when another interrupt of equal or higher priority is running, the edge waits for it. The spread is the jitter. Latencies are simulated (0.4 µs base, a 30 % chance of meeting a competing handler).

Output compare and PWM hardware remove that variation: the edge happens at the compare match, to within one timer tick, however busy the software is.

STEP 5

Worked example: why the logger lost 2000 samples

The logger re-armed a one-shot timer for 10 ms at the end of each sample, and each sample took about 56 µs:

T=10 ms+56 μs=10.056 ms,T = 10\ \text{ms} + 56\ \mu\text{s} = 10.056\ \text{ms}, 3600 s10.056 ms≈357 995 samples\frac{3600\ \text{s}}{10.056\ \text{ms}} \approx 357\,995\ \text{samples}

Scheduling start-to-start, with a periodic update interrupt or by advancing a compare value, gives 3600 s / 10 ms = 360 000 samples.

MYTHS AND FACTS

Common misconceptions

Re-arming the timer in the interrupt keeps the period

Only if you add the interval to the previous deadline; restarting from “now” adds the handler’s latency and run time every period.

CCR sets the output frequency

In toggle mode it sets the phase; ARR and the tick set the frequency.

Toggling a pin in a fast interrupt is as good as hardware

Its edges move with interrupt latency; output compare does not.

One timer, one period

Compare channels on a free-running counter can each run their own period.

Check yourself

Answer in your head, then open the card.

A 2 MHz timer tick with ARR = 499 drives a compare channel in toggle mode. What is the output frequency?

2 MHz / (2 × 500) = 2 kHz.

A task runs every 100 ms and takes 3 ms; it is re-armed end-to-start. How many runs happen in 60 s, and how many with start-to-start scheduling?

End-to-start: 60 / 0.103 ≈ 582. Start-to-start: 600.

In the compare interrupt, why add INTERVAL to the old CCR1 rather than write CCR1 = CNT + INTERVAL?

CNT has moved on by the interrupt latency; adding to the old CCR schedules exactly one interval after the previous match, so latency does not accumulate into drift.

A 1 kHz reference made by toggling a pin in an interrupt shows edges that move by up to 20 µs. What is the likely cause, and the fix?

Another interrupt of equal or higher priority (up to 20 µs long) sometimes runs when the toggle is due. Use a timer output-compare or PWM channel to drive the pin.

Sources (3)
  1. STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_tim.h — TIM_OC_InitTypeDef Pulse: “the pulse value to be loaded into the Capture Compare Register”; output-compare modes TIM_OCMODE_TOGGLE, TIM_OCMODE_PWM1, TIM_OCMODE_PWM2
  2. Raspberry Pi Ltd, pico-sdk 1.5.1, common/pico_time/include/pico/time.h — add_repeating_timer_ms: “if >0 then this is the delay between one callback ending and the next starting; if <0 then this is the negative of the time between the starts of the callbacks”
  3. Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_timer/include/hardware/timer.h — “Four alarms: match on the lower 32 bits of counter, IRQ on match”, each with its own interrupt; alarms can be set at most 2³² µs (~72 minutes) ahead