Skip to content

Delays, timers and PWM

Why delay loops are unreliable, how a hardware timer counts, and how PWM dims an LED.

Explanation

The blink example waits with a delay function. The simplest version of a delay is a loop that counts to a large number and does nothing else. It works in a debug build, but with optimization enabled it takes no time at all: the compiler sees a loop with no effect and removes it. Declaring the counter volatile prevents this, but the main point is that timing should come from hardware, not from counting instructions.

A hardware timer is a counter driven by a clock. Its rate is set by 2 values. Taking an STM32 as the example, the prescaler divides the input clock: with a 72 MHz clock and a prescaler value of 71, the counter ticks at 72 MHz / 72 = 1 MHz, one million times per second. The auto-reload value, called ARR, is the point at which the counter wraps to zero. The period is ARR + 1 ticks, so 1 MHz ticks with ARR = 999 give an update 1000 times a second. Other chips divide differently: AVR has a fixed set of dividers and the RP2040 has a fractional divider. Check the timer chapter of your chip's manual for the exact formula.

Nearly every Cortex-M chip also has SysTick, a simple 24-bit timer that counts down and reloads. Most libraries use it for a millisecond tick, which gives you a millis() style clock. With this clock you can wait without blocking: record when something started, and check on each pass of the loop whether enough time has passed.

PWM (pulse width modulation) is generated by a timer: the output pin goes high at the start of each period and low when the counter reaches a compare value. The fraction of the period during which the pin is high is the duty cycle. A compare value of 500 out of 2000 counts is 25 percent. When the switching is fast enough, an LED appears dimmed, a motor runs slower and a servo moves to a position. The pin is still only ever fully on or fully off.

Key points

  • The optimizer removes empty counting loops. Use a timer.
  • On STM32, tick rate = clock / (prescaler + 1) and period = ARR + 1 ticks. Other chips divide differently, so check the manual.
  • SysTick gives a millisecond clock. Use it to wait without blocking the loop.
  • PWM duty cycle = compare / period. The pin is still only on or off.

Check your understanding

3 questions on this lesson. An explanation follows each answer.

Sign in to answer the questions. Your answers count towards your level.

Sign in

You can read the lesson and the project without an account.

Project

Fading LED and a servo

What you need

  • Your board and an LED with a resistor.
  • A small hobby servo (SG90 or similar). Power it from a separate 5 V supply, and connect that supply's ground to the board's ground as well as to the servo. The board's 5 V pin can work for a quick test, but a stalled servo can draw more current than a USB port supplies and reset the board.

Steps

  1. 1Rewrite blink without a delay call: use the millisecond tick and toggle the LED when 500 ms have passed. The main loop must remain free for other work.
  2. 2Set up a PWM output on the LED pin at about 1 kHz. Ramp the duty cycle from 0 to 100 percent and back, so that the LED fades in and out.
  3. 3Set up a second PWM output at 50 Hz for the servo. A 1.5 ms pulse is the center position; 1.0 ms and 2.0 ms are the ends. Calculate the compare values for your timer settings before you write them.
  4. 4Sweep the servo slowly from end to end while the LED continues to fade in and out.

Done when

The LED fades in and out and the servo sweeps at the same time, with no blocking delay left in your main loop.