How Do You Configure STM32 Timers for PWM, Input Capture, and Output Compare?
Last updated 14 July 2026 · 8 min read
Direct Answer
An STM32 general-purpose timer (TIMx) has independent channels that can each run in one of three roles: PWM mode generates a fixed-frequency output with a configurable duty cycle (set via the ARR and CCR registers); output compare mode toggles, sets, or clears a pin — or triggers an interrupt with no pin at all — the instant the counter matches the channel's compare register; and input capture mode does the reverse, latching the counter's value into the compare register the instant an edge arrives on the channel's input, which is how a timer measures an external signal's frequency or pulse width. All three modes are configured in CubeMX under the timer's peripheral settings and started in firmware with HAL functions like HAL_TIM_PWM_Start(), HAL_TIM_OC_Start(), and HAL_TIM_IC_Start_IT().
Detailed Explanation
STM32 general-purpose timers (TIM2–TIM5, TIM9–TIM14, and similar, depending on the specific family — see the STM32 family selection guide for which timers each series includes) are built around a single free-running counter shared by up to four independent channels. What makes the peripheral flexible is that each channel can be configured into one of three fundamentally different roles, all built on the same counter-and-compare-register hardware:
- PWM mode generates an output signal — the channel's pin goes high or low based on comparing the running counter against the channel's Capture/Compare Register (CCR), producing a fixed-frequency signal whose duty cycle is set by where CCR sits relative to the Auto-Reload Register (ARR). See PWM frequency and duty cycle explained for the underlying ARR/CCR/prescaler relationship, which this page assumes as background.
- Output compare mode is PWM's more general sibling: instead of always producing a repeating waveform, the pin (or nothing, if no pin is used) toggles, sets, clears, or generates an interrupt at the exact instant the counter reaches the CCR value — useful for one-shot timed events, not just periodic waveforms.
- Input capture mode runs the same hardware in reverse: instead of the timer driving a pin, an external signal drives the timer. The instant a configured edge (rising, falling, or both) arrives on the channel's input, the current counter value is automatically latched into the channel's CCR register — which is how a timer measures an incoming signal's frequency or pulse width without the CPU having to poll a GPIO pin in a tight loop.
All three modes share the same clock-tree dependency: the timer's counting rate depends on its input clock and prescaler exactly as covered in how the STM32 clock tree works — including the timer-clock-doubling behaviour on some bus configurations that page describes, which affects the tick-to-time conversion for every mode on this page equally.
PWM Mode
PWM mode is the most commonly used of the three and is covered at the conceptual level in PWM frequency and duty cycle explained. On STM32 specifically, CubeMX configures a channel for PWM by setting its mode to "PWM Generation CHx" in the timer's peripheral view, after which firmware starts it with:
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, new_duty_ticks);
Two PWM sub-modes exist (PWM1 and PWM2), differing only in whether the output is active while the counter is below or above the CCR value — PWM1 is the conventional choice for "higher CCR value means higher duty cycle" behaviour; PWM2 inverts that relationship. Most designs use PWM1 and invert the duty-cycle calculation in software if the opposite polarity is needed, rather than relying on PWM2's inverted semantics being obvious to a future maintainer.
Output Compare Mode
Output compare is configured similarly but with a mode selection of Toggle, Active, Inactive, or Frozen (no pin action, interrupt/DMA only) for each channel. The distinguishing behaviours:
- Toggle flips the pin state on every compare match — useful for generating a square wave via software-managed CCR updates, but note that a full output cycle requires two toggles, so the output frequency is half the compare-match rate (see the FAQ above for the common mistake this causes).
- Active/Inactive forces the pin to a fixed high or low state on match — typically used for one-shot timed pin events rather than repeating waveforms.
- Frozen (no output pin) generates the compare-match interrupt or DMA request with no pin action at all — the common configuration when a timer channel is being used purely as a precise, hardware-timed interrupt source (a periodic task trigger, for example) rather than to drive a physical signal.
One-pulse mode is a specific output-compare configuration where the timer counts once from a trigger event to its ARR value and then stops automatically, producing exactly one pulse of a precisely controlled width and delay — useful for a single timed trigger pulse (camera shutter, ultrasonic sensor trigger) without firmware needing to manually start and stop the timer around the pulse.
Input Capture Mode
Input capture configures a channel's pin as a timer input rather than an output. CubeMX setup selects the channel's polarity (rising edge, falling edge, or both edges) and an input filter (a digital debounce/noise-rejection setting that requires a configurable number of consecutive samples to agree before an edge is accepted — useful for a noisy or slow-edged input signal).
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint32_t captured = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// compute delta against the previous captured value
}
}
Frequency measurement: capture on a single edge polarity (typically rising) and compute the tick delta between consecutive captures — see the FAQ above for the full conversion to a frequency in Hz, including the overflow-handling subtlety for signals slow enough that the timer counter wraps around between edges.
Pulse-width / duty-cycle measurement: capture both edges, either by configuring one channel for both-edge capture (where available) or, more commonly, pairing two channels internally connected to the same input pin — one triggered on the rising edge and one on the falling edge, a configuration STM32 timers support directly via the channel's input selection (IC1 and IC2 both mapped to TI1, for example) specifically to make duty-cycle measurement a single-timer operation rather than needing two independent timers.
Timer Encoder Mode
STM32 general-purpose timers also support a dedicated encoder mode, a specialised configuration that automatically decodes a quadrature signal's direction and position into the counter register in hardware, without firmware needing to interpret the A/B channel phase relationship itself. This is a distinct, purpose-built mode from general input capture — see rotary and quadrature encoder interfacing for the full treatment of encoder mode specifically, including how it compares to a software ISR-based decoding approach.
CubeMX and HAL Setup Workflow
- In CubeMX's Pinout view, assign the desired timer channel pins — each TIMx channel maps to a specific set of alternate-function pins per the datasheet's pinout tables, and only those specific pins support that channel.
- In the timer's Configuration view, set the overall timer parameters (prescaler, counter period/ARR) that apply to all its channels, then set each channel's individual mode (PWM Generation, Output Compare, Input Capture, or Encoder Mode) under its own settings.
- CubeMX generates a
TIM_HandleTypeDefand the associatedMX_TIMx_Init()function; firmware starts the desired channel(s) with the matching HAL start function (HAL_TIM_PWM_Start,HAL_TIM_OC_Start,HAL_TIM_IC_Start_IT, etc.) and, for input capture or output compare, implements the relevant HAL callback (HAL_TIM_IC_CaptureCallback,HAL_TIM_OC_DelayElapsedCallback) to act on each event. See how to configure STM32 peripherals in CubeMX for the general CubeMX-to-HAL workflow this follows. - For interrupt-driven capture or compare, confirm the timer's interrupt is enabled and correctly prioritised in NVIC — see STM32 NVIC interrupt priority configuration for how timer interrupt priority interacts with other peripheral interrupts on the same system.
Design Considerations
- Confirm the actual timer input clock before calculating capture math or PWM frequency. The timer clock is not always the same as the APB bus clock it's attached to — see how the STM32 clock tree works for the doubling behaviour that catches many designs.
- Choose PWM mode over output-compare toggle mode for straightforward duty-cycle-controlled waveforms — toggle mode's half-frequency behaviour and manual CCR-update requirement make it a less direct tool for this than PWM mode's built-in duty-cycle semantics.
- Use interrupt- or DMA-driven capture for anything with real timing requirements, not polling — see the FAQ above for why a busy CPU can silently miss a polled capture event.
- Account for counter overflow in any capture-based measurement of a slow or intermittent signal. A timer with a 16-bit counter (most general-purpose STM32 timers) wraps around relatively quickly at a fast tick rate; track the update (overflow) interrupt alongside capture events for any signal whose period could plausibly exceed one counter period.
- Zeus Design's embedded firmware team implements timer-based PWM, capture, and precision-timing firmware as part of STM32 embedded firmware development for production hardware.
Common Mistakes
- Assuming output-compare toggle mode produces a signal at the compare-match interrupt rate — it actually produces one at half that rate, since a full cycle needs two toggles (see the FAQ above).
- Mapping a timer channel to a GPIO pin that isn't actually one of that channel's valid alternate-function options — each TIMx channel has a fixed, family-specific set of pins it can be routed to; CubeMX's pinout conflict warnings catch this, but only if the channel and pin are actually cross-checked against the datasheet's alternate-function table rather than assumed.
- Forgetting the timer-clock-doubling behaviour when calculating expected PWM frequency or capture timing — the same clock-tree subtlety covered on the STM32 clock tree page applies directly to every timer-based calculation on this page.
- Polling for a captured value on a signal fast enough that events can be missed between poll checks — use interrupt- or DMA-driven capture instead for any input whose rate isn't guaranteed slow relative to the rest of the firmware's loop timing.
- Not handling counter overflow in a frequency or pulse-width calculation for a slow input signal, producing a silently wrong result whenever the timer wraps around between two capture events rather than an obviously broken one.
Frequently Asked Questions
- How do I measure an unknown input signal's frequency using input capture?
- Configure the channel for input capture on the rising edge, and in the capture-complete callback, read the current CCR value and subtract the CCR value from the previous capture (handling counter overflow/wraparound if the timer has rolled over between captures — the timer's update interrupt, tracked as an overflow counter, resolves this). That difference, in timer ticks, converts to a period via period = (delta_ticks × (prescaler + 1)) / timer_clock_hz, and frequency is the reciprocal. For pulse-width (duty cycle) measurement rather than just frequency, configure the channel to capture both edges (or pair two channels on the same input, one rising- and one falling-edge triggered) and compare the two capture timestamps.
- Why does my output compare toggle mode produce a signal at half the frequency I expected?
- Toggle mode flips the pin state on every compare match, so a full output cycle (one low period plus one high period) requires two compare matches — meaning the output frequency is exactly half the compare-match interrupt rate. This is a common source of off-by-factor-of-two errors when a design expects toggle mode to behave like PWM mode's direct frequency setting. If a true 50% duty-cycle square wave at a specific frequency is the goal, PWM mode (with CCR set to half of ARR) is usually the more direct configuration than output compare toggle mode.
- Do I need to use HAL's interrupt-based capture functions, or can I poll?
- Polling (HAL_TIM_ReadCapturedValue() after checking the capture flag) works for simple, non-time-critical measurements, but interrupt-driven capture (HAL_TIM_IC_Start_IT() with the HAL_TIM_IC_CaptureCallback() override) is the standard approach for anything with real timing requirements, since polling risks missing a capture event entirely if the CPU is busy elsewhere when the next edge arrives before the previous one is read. DMA-driven capture (HAL_TIM_IC_Start_DMA()) is the next step up for very high-rate capture streams where even interrupt servicing overhead would be too slow — see how DMA works in embedded systems for the underlying mechanism.
References
Related Questions
How Does the STM32 Clock Tree Work?
The STM32 clock tree routes HSE or HSI through a PLL to generate SYSCLK, then divides it across AHB and APB buses. Learn how it works and how to configure it.
PWM Explained: Frequency, Duty Cycle, Dead-Time, and Hardware Timers
Learn how PWM works: duty cycle, frequency, resolution, dead-time for H-bridge drives, and when to use hardware timers versus software PWM on microcontrollers.
How Do You Interface a Rotary Encoder to a Microcontroller?
Interface an incremental rotary encoder to a microcontroller: quadrature A/B decoding, hardware timer/PCNT counting vs software ISR decoding, and debounce.
How Do You Configure STM32 Peripherals with HAL and CubeMX?
STM32CubeMX generates HAL initialisation code for UART, SPI, and I2C from a GUI. This guide explains key settings and how generated code maps to the hardware.
How Do You Configure STM32 NVIC Interrupt Priorities?
Learn how to configure STM32 NVIC interrupt priorities using HAL, priority grouping, and the FreeRTOS configMAX_SYSCALL_INTERRUPT_PRIORITY constraint.
Which STM32 Family Should You Use?
Compare STM32 families for new designs: G0, G4, F4, H7, L4, U5, WB, and WL — performance tiers, power profiles, peripheral sets, and which to choose.
Related Forum Discussions
STM32L4 never wakes from Stop mode — button EXTI interrupt just doesn't fire
Working on a battery-powered sensor node, STM32L476RG (Nucleo board for now). Idea is: device sits in Stop 2 mode most of the time, wakes wh
STM32F401 UART printing garbage after switching to 84 MHz PLL — same 115200 baud in CubeMX and PuTTY
Got a WeAct Black Pill (STM32F401CCU6) project that's been running happily on the default HSI clock at 16 MHz. Using USART1 on PA9/PA10 thro
STM32H743 HAL_UART_Receive_DMA fires error callback immediately — TEIF1 set, RxCplt never fires
Upgrading a project from STM32F4 to STM32H743. UART DMA receive worked on the F4 without any issues — standard CubeMX setup, call HAL_UART_R
STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board. Using HAL, generated the init code with CubeMX. The GPIO