Project 3: Timer-Driven LED Sequencer

A deterministic LED pattern engine driven by hardware timers and interrupts instead of blocking delays.

Quick Reference

Attribute Value
Difficulty Level 2: Intermediate
Time Estimate 1-2 weeks
Main Programming Language C (Alternatives: C++, Rust, Ada)
Alternative Programming Languages C++, Rust, Ada
Coolness Level Level 3: Genuinely Clever
Business Potential 1. The “Resume Gold”
Prerequisites Clock tree verified, GPIO outputs working, basic interrupts
Key Topics Timers, ISR scheduling, state machines, timing drift

1. Learning Objectives

By completing this project, you will:

  1. Configure a hardware timer for precise periodic interrupts.
  2. Build a non-blocking LED state machine.
  3. Measure and report timing drift over long runs.
  4. Compare timer-driven scheduling to delay loops.

2. All Theory Needed (Per-Concept Breakdown)

Timers, Prescalers, and Interrupt Scheduling

Fundamentals Hardware timers are the MCU’s timekeeping engines. They count ticks derived from a clock source and can generate periodic events without CPU busy-waiting. By configuring a prescaler and auto-reload value, you set a precise interval. Timers can trigger interrupts, toggle pins, or drive DMA. Interrupts allow your firmware to react to timer events with deterministic latency, which is the basis of real-time scheduling. Using timers and interrupts instead of delay loops is the core technique for reliable embedded timing.

Deep Dive into the concept STM32 timers are flexible peripherals that can operate as basic timebases, output compare generators, PWM engines, or input capture units. At their core is a counter clocked by a timer clock derived from the APB bus. The prescaler divides the timer clock, and the auto-reload register sets the period at which the counter resets. Each update event can trigger an interrupt, set a status flag, or generate a DMA request. The advantage over software delays is determinism: the hardware counts regardless of CPU load. However, interrupts introduce latency. The NVIC prioritizes interrupts, and higher-priority ISRs can delay timer handlers. If you rely on timer interrupts for scheduling, you must measure latency and account for jitter. A timer-driven LED sequencer is an ideal training ground: you build a state machine that advances on each timer tick, ensuring that your pattern timing is stable even if the CPU is busy. Advanced timers also support one-pulse mode, dead-time insertion, and complementary outputs, which are critical for motor control but beyond the basics here. For a correct timer configuration, you compute the prescaler as (timer_clock / desired_tick) - 1, and the auto-reload as (tick_rate / desired_interval) - 1. You then verify by toggling a GPIO on each interrupt and measuring the period. Misconfigurations are easy to spot: a factor-of-two error usually means you forgot the APB timer clock doubling rule. Another common failure is forgetting to clear interrupt flags, which results in repeated or missed interrupts. In short, timers are your deterministic clockwork. Mastering them means you can build schedules, measure drift, and guarantee timing, which is the heart of embedded systems.

How this fit on projects In Timer-Driven LED Sequencer, you design a timer-driven schedule that advances a state machine without delay loops, proving deterministic timing.

Definitions & key terms

  • Prescaler -> Divider that slows the timer clock.
  • Auto-reload -> Value that sets the timer period.
  • Update event -> Timer overflow event that can trigger interrupt or DMA.
  • Interrupt latency -> Time between event and ISR execution.
  • Jitter -> Variation in interrupt timing due to system load.

Mental model diagram (ASCII)

Timer clock -> prescaler -> counter -> overflow -> ISR -> state machine step

How it works (step-by-step, with invariants and failure modes)

  1. Select a timer and enable its clock.
  2. Compute prescaler and auto-reload for desired tick rate.
  3. Enable update interrupt and configure NVIC priority.
  4. In ISR, update the state machine and clear interrupt flags.
  5. Invariant: timer tick period remains stable; failure mode: drift or missed interrupts if flags are not cleared.

Minimal concrete example

// Timer ISR toggles LED state
void TIM2_IRQHandler(void) {
if (TIM2->SR & TIM_SR_UIF) {
    TIM2->SR &= ~TIM_SR_UIF;
    step_led_pattern();
}
}

Common misconceptions

  • Delays are fine for scheduling ignores CPU load and jitter.
  • Timer interrupts are always on time ignores NVIC priority and blocking ISRs.
  • Auto-reload equals milliseconds ignores prescaler and timer clock source.

Check-your-understanding questions

  1. What causes a factor-of-two error in timer frequency?
  2. How do you measure jitter in a timer-driven system?
  3. Why must you clear the update flag in the ISR?

Check-your-understanding answers

  1. Forgetting that timers may run at twice the APB clock when the prescaler is not 1.
  2. Toggle a GPIO in the ISR and measure period variation with a logic analyzer.
  3. If not cleared, the ISR may immediately retrigger or mask new interrupts.

Real-world applications

  • LED sequencing and deterministic UI timing.
  • Sensor sampling schedules with fixed intervals.
  • Control loops in motors or power electronics.

Where you’ll apply it

References

  • STM32F3 Reference Manual (timer chapter).
  • Joseph Yiu, ‘The Definitive Guide to ARM Cortex-M3/M4’ (interrupts).
  • Elecia White, ‘Making Embedded Systems’ (timing and scheduling).

Key insights

  • Timers create deterministic time; interrupts make that time actionable without blocking the CPU.

Summary Timers and interrupts are the foundation of real-time embedded behavior. They replace blocking delays with precise schedules and make timing measurable.

Homework/Exercises to practice the concept

  1. Calculate prescaler and auto-reload for a 250 ms timer tick at 72 MHz.
  2. Toggle a GPIO on each timer interrupt and measure drift over 5 minutes.
  3. Experiment with different NVIC priorities and observe latency.

Solutions to the homework/exercises

  1. A 1 kHz tick requires prescaler 71999; a 250 ms interval uses auto-reload 249.
  2. Drift near zero indicates correct timer configuration; large drift indicates clock misconfig.
  3. Raising priority reduces latency but can starve lower-priority handlers.

Timebase, SysTick, and Measurement Discipline

Fundamentals A reliable timebase is how you reason about time in firmware. SysTick is a dedicated timer tied to the core clock that can generate periodic interrupts. If you configure it for 1 kHz, you get a millisecond tick that becomes your system heartbeat. This tick drives delays, scheduling, timeouts, and timestamps. But SysTick is only reliable if you know the core clock frequency and if you measure the actual timing. Without measurement, a ‘1 ms tick’ is just a guess. A bring-up project must therefore treat SysTick as a calibration point and validate it with real-world observation.

Deep Dive into the concept SysTick is a 24-bit down-counter integrated into the Cortex-M core. It takes the core clock (or core clock divided by 8) and counts down from a reload value to zero, then sets a flag and optionally fires an interrupt. Because it is in the core, it is unaffected by peripheral bus prescalers, which makes it a good reference for measuring the system clock. To configure SysTick, you load the reload register with (core_clock / desired_tick) - 1, select the clock source, enable the counter, and enable its interrupt. The interrupt handler typically increments a global tick counter. On STM32, HAL_Delay and other drivers are often built on this tick. The main danger is implicit coupling: if SysTick is configured incorrectly, every delay and timeout in your system is wrong. Another hazard is jitter. SysTick interrupts can be delayed by higher-priority interrupts, which means the tick is not a precise real-time clock, but a best-effort scheduler. For measurement, you should use SysTick only as a reference and then validate it with a GPIO toggle or timer output. A robust timebase audit logs the computed reload value, the configured clock source, and the observed period. The audit should also include a drift test: toggle a pin every N ticks for several minutes and measure drift relative to a stopwatch or logic analyzer. If drift is observed, the root cause is often clock source accuracy (HSI vs HSE) or an incorrect reload value due to a wrong core clock assumption. You also need to consider wraparound. A 24-bit counter at 72 MHz will wrap quickly if used without an interrupt, and a 32-bit tick counter will wrap after ~49 days at 1 kHz. In embedded systems, you typically handle wraparound by using unsigned arithmetic and comparing time differences rather than absolute values. Finally, you must decide what ‘good enough’ measurement means. For LED blinking, 1-2% accuracy is acceptable. For UART baud or ADC sampling, you need tighter tolerances. The discipline is to tie every time-dependent feature to a measured reference rather than an assumption.

How this fit on projects In Timer-Driven LED Sequencer, SysTick is your baseline for clock validation. You compute the reload value, log it, and then validate the resulting tick with physical measurement.

Definitions & key terms

  • SysTick -> Core-integrated timer used for periodic interrupts and timekeeping.
  • Reload value -> The count value loaded into SysTick before it starts counting down.
  • Tick -> A periodic time event, commonly 1 ms in embedded systems.
  • Jitter -> Variation in the timing of periodic events due to interrupt latency.
  • Drift -> Long-term timing error relative to a reference clock.

Mental model diagram (ASCII)

Core clock -> SysTick down-counter -> interrupt -> tick++
                  |
                  v
             timing reference

How it works (step-by-step, with invariants and failure modes)

  1. Compute reload value based on expected core clock and desired tick rate.
  2. Configure SysTick to use core clock and enable interrupt.
  3. Increment a global tick counter in the SysTick handler.
  4. Toggle a GPIO every N ticks and measure the period.
  5. Invariant: tick increments at configured frequency; failure mode: drift or jitter due to wrong clock or interrupt priority.

Minimal concrete example

void SysTick_Handler(void) {
g_tick_ms++;
}
void delay_ms(uint32_t ms) {
uint32_t start = g_tick_ms;
while ((uint32_t)(g_tick_ms - start) < ms) {
    __WFI();
}
}

Common misconceptions

  • SysTick gives precise real-time scheduling ignores interrupt latency and jitter.
  • If LED blinks, tick is correct ignores the need to measure drift over time.
  • Tick counters never overflow ignores wraparound in long-running systems.

Check-your-understanding questions

  1. Why might SysTick interrupts be delayed even if configured correctly?
  2. How do you compute reload for 1 kHz at 72 MHz?
  3. What is the safest way to compare timeouts with wraparound?

Check-your-understanding answers

  1. Higher-priority interrupts can preempt SysTick, causing jitter.
  2. Reload = (72,000,000 / 1,000) - 1 = 71,999.
  3. Use unsigned subtraction: if (now - start) >= timeout.

Real-world applications

  • Scheduling periodic sensor sampling.
  • Timeouts in communication protocols.
  • Measuring CPU load and real-time performance.

Where you’ll apply it

References

  • ARM Cortex-M4 Technical Reference Manual (SysTick).
  • Joseph Yiu, ‘The Definitive Guide to ARM Cortex-M3/M4’ (timers and exceptions).
  • Elecia White, ‘Making Embedded Systems’ (timing discipline).

Key insights

  • A tick is only trustworthy when you can measure it and bound its jitter and drift.

Summary SysTick provides a convenient timebase, but it is only as accurate as your clock configuration and interrupt discipline. Treat it as a calibrated instrument, not a magic delay source.

Homework/Exercises to practice the concept

  1. Compute the reload value for 2 kHz and verify it in code.
  2. Measure the drift of a 1 Hz LED blink over 10 minutes.
  3. Experiment with interrupt priorities and observe SysTick jitter.

Solutions to the homework/exercises

  1. Reload for 2 kHz at 72 MHz is 35,999.
  2. A 1 Hz blink should be 600 seconds over 10 minutes; log the error and compute percent drift.
  3. Setting a higher-priority timer interrupt increases SysTick jitter; adjust priorities if needed.

NVIC, Interrupt Priority, and Latency Measurement

Fundamentals Interrupts allow the MCU to respond to events without polling. The NVIC assigns priorities and decides which interrupt runs next. Latency is the time between an interrupt event and when its handler actually executes. Measuring latency is critical for real-time systems because it defines how quickly you can respond to sensors, timers, or communication events.

Deep Dive into the concept On Cortex-M, when an interrupt event occurs, the NVIC determines whether it can preempt the current execution. If the new interrupt has higher priority, the core pushes registers onto the stack and jumps to the handler. This stacking and unstacking takes a predictable number of cycles, but additional latency can be introduced if interrupts are disabled or if higher-priority ISRs are running. The STM32F3 supports configurable priority levels; you must define a priority scheme that matches your system’s timing requirements. Latency measurement is done by toggling a GPIO at the first instruction of an ISR and measuring the delay from the event source. For timer interrupts, you can compare the timer output event to the GPIO toggle. For external interrupts, you can inject a known edge and measure the response. Another tool is the DWT cycle counter, which can count CPU cycles between event and handler execution. In many systems, average latency is less important than worst-case latency, because worst-case defines deadline misses. Therefore, a good profiler will stress the system by enabling other interrupts, running CPU-heavy tasks, and then measuring the maximum latency observed. Your results guide design decisions: if latency is too high, you might raise priority, reduce ISR work, or move tasks to DMA. This project teaches you that ‘real-time’ is a measured property, not a claim.

How this fit on projects In Timer-Driven LED Sequencer, you build a latency profiler that measures interrupt response under load and produces a report of min/avg/max latency.

Definitions & key terms

  • NVIC -> Nested Vectored Interrupt Controller that prioritizes and dispatches interrupts.
  • Latency -> Time between an event and ISR execution.
  • Preemption -> Higher-priority interrupt interrupting lower-priority code.
  • DWT -> Data Watchpoint and Trace unit, includes cycle counter.
  • Critical section -> Code where interrupts are temporarily disabled.

Mental model diagram (ASCII)

Event -> NVIC arbitration -> stacking -> ISR entry -> GPIO toggle

How it works (step-by-step, with invariants and failure modes)

  1. Configure a periodic timer interrupt as the event source.
  2. Toggle a GPIO at the first line of the ISR.
  3. Measure delay between timer event and GPIO edge using scope/logic analyzer.
  4. Stress the system with other interrupts and compute max latency.
  5. Invariant: ISR entry time remains bounded; failure mode: unbounded latency due to long critical sections.

Minimal concrete example

void TIM3_IRQHandler(void) {
GPIOA->ODR ^= (1 << 5); // toggle for timing
TIM3->SR &= ~TIM_SR_UIF;
}

Common misconceptions

  • Average latency is enough ignores worst-case deadlines.
  • Interrupts are always immediate ignores priority and masking.
  • ISRs can do heavy work ignores that long ISRs increase latency for others.

Check-your-understanding questions

  1. What factors increase worst-case interrupt latency?
  2. How do priorities affect preemption?
  3. Why is GPIO toggling a good measurement method?

Check-your-understanding answers

  1. Long critical sections, higher-priority ISRs, and flash wait states increase latency.
  2. Higher-priority interrupts can preempt lower-priority handlers at any time.
  3. It produces a physical edge that can be measured precisely with external tools.

Real-world applications

  • Safety-critical control loops.
  • Real-time motor control and power electronics.
  • High-speed data acquisition systems.

Where you’ll apply it

References

  • ARM Cortex-M4 Technical Reference Manual (exception entry/exit).
  • Joseph Yiu, ‘The Definitive Guide to ARM Cortex-M3/M4’ (NVIC).
  • ST application notes on interrupt latency measurement.

Key insights

  • Real-time behavior is defined by worst-case latency, not average latency.

Summary Interrupt profiling makes latency visible and measurable. By understanding NVIC priorities and measuring response time, you can engineer systems that meet real-time deadlines.

Homework/Exercises to practice the concept

  1. Measure ISR latency with and without other interrupts enabled.
  2. Configure two interrupts with different priorities and observe preemption.
  3. Use the DWT cycle counter to compute cycles between event and ISR entry.

Solutions to the homework/exercises

  1. Latency increases when other interrupts run; log max latency as your design bound.
  2. The higher-priority ISR preempts immediately, lower-priority waits.
  3. DWT->CYCCNT gives cycle-accurate timing; subtract to compute latency.

3. Project Specification

3.1 What You Will Build

A timer-driven LED sequencer that advances patterns at fixed intervals, logs drift statistics, and runs for extended periods without blocking delays.

3.2 Functional Requirements

  1. Timer Tick: Configure a timer to generate a 250 ms tick with <1% error.
  2. State Machine: Implement LED patterns as a deterministic state machine.
  3. Drift Measurement: Track expected vs actual elapsed time and report drift.
  4. Non-Blocking: No busy-wait delays in the main loop.

3.3 Non-Functional Requirements

  • Performance: Jitter below 2 ms per tick; drift <1 ms over 10 minutes.
  • Reliability: Sequencer runs for 30 minutes without missing ticks.
  • Usability: Pattern configuration in a simple table for easy edits.

3.4 Example Usage / Output

Pattern: chase-4
Tick: 250 ms
Elapsed: 600.00 s Expected: 600.00 s Drift: 0.2 ms
Status: PASS

3.5 Data Formats / Schemas / Protocols

Log line format:

  • PATTERN=<name>
  • TICK_MS=<int>
  • ELAPSED_MS=<int>
  • DRIFT_MS=<float>

3.6 Edge Cases

  • Timer interrupt not enabled.
  • ISR takes too long and causes missed ticks.
  • State machine index out of bounds.
  • Clock misconfiguration causing drift.

3.7 Real World Outcome

You will observe a stable LED pattern with minimal drift over long runtimes.

3.7.1 How to Run (Copy/Paste)

$ make flash
$ screen /dev/tty.usbmodem* 115200

3.7.2 Golden Path Demo (Deterministic)

  • Run the sequencer for 10 minutes and record drift.

3.7.3 CLI Transcript (Success)

PATTERN=chase-4
TICK_MS=250
ELAPSED_MS=600000
DRIFT_MS=0.2
RESULT=PASS
# Exit code: 0

3.7.4 Failure Demo (Blocking Delay)

  • Replace ISR tick with HAL_Delay and observe drift.
DRIFT_MS=120.5
RESULT=FAIL
# Exit code: 2

4. Solution Architecture

A timer interrupt drives a state machine that advances LED patterns and logs timing metrics.

4.1 High-Level Design

Timer IRQ -> Tick -> State Machine -> GPIO Outputs -> Drift Logger

4.2 Key Components

Component Responsibility Key Decisions
Timer Driver Generates periodic tick interrupts Use hardware timer not SysTick for isolation
Pattern State Machine Advances LED sequence Table-driven for easy pattern edits
Drift Logger Computes expected vs actual elapsed time Use monotonic tick counter

4.3 Data Structures (No Full Code)

typedef struct {
uint8_t leds[8];
uint8_t length;
} pattern_t;

4.4 Algorithm Overview

State Machine Tick

  1. On timer IRQ, increment tick counter.
  2. Advance pattern index.
  3. Update GPIO outputs.

Complexity: O(1) per tick.


5. Implementation Guide

5.1 Development Environment Setup

make init
make flash
screen /dev/tty.usbmodem* 115200

5.2 Project Structure

project-root/
|-- src/
|   |-- main.c
|   |-- drivers/
|   `-- app/
|-- include/
|-- Makefile
`-- README.md

5.3 The Core Question You’re Answering

“Why are timers better than software delays in embedded systems?”

5.4 Concepts You Must Understand First

  1. Timer prescalers and auto-reload.
  2. NVIC priority and interrupt latency.
  3. State machines for deterministic behavior.

5.5 Questions to Guide Your Design

  1. How will you measure drift over 10 minutes?
  2. How will you represent patterns without blocking?
  3. What priority should the timer interrupt have?

5.6 Thinking Exercise

Timer Math

Timer clock = 72 MHz
Prescaler = 7200-1 -> 10 kHz
ARR = 2500-1 -> 250 ms

5.7 The Interview Questions They’ll Ask

  1. Explain how a hardware timer generates periodic interrupts.
  2. Why are blocking delays dangerous in real-time systems?
  3. How do you compute timer prescaler values?

5.8 Hints in Layers

Hint 1: Start by blinking one LED with a timer. Hint 2: Move pattern logic into a state machine. Hint 3: Toggle a debug pin in the ISR to measure jitter.

5.9 Books That Will Help

Topic Book Chapter
Timers Making Embedded Systems Ch. 4
Interrupts Definitive Guide to ARM Cortex-M Ch. 7

5.10 Implementation Phases

Phase 1: Single LED Tick (2 days)

Set up a timer interrupt and toggle one LED.

Phase 2: Pattern Engine (4-5 days)

Implement table-driven patterns and logging.

Phase 3: Drift Validation (3-4 days)

Measure drift and refine timing settings.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Tick source SysTick vs TIMx TIMx Isolation from OS timing
Pattern storage Code vs table Table Easier to edit and extend

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Pattern table correctness Index wrap tests
Integration Tests Timer tick accuracy GPIO toggle measurement
Edge Case Tests ISR overrun Artificial delay in ISR

6.2 Critical Test Cases

  1. Tick Frequency: Measured tick period within 1% of 250 ms.
  2. Pattern Wrap: Pattern index wraps without skipping.
  3. Drift Report: Drift over 10 minutes <1 ms.

6.3 Test Data

Expected ticks in 10 minutes: 2400
Measured ticks: 2400
Drift: 0.2 ms

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Using delay loops Large drift Switch to timer interrupts
ISR too long Missed ticks Move work to main loop
Wrong prescaler Tick period off Recompute timer settings

7.2 Debugging Strategies

  • Toggle a debug pin on ISR entry to measure jitter.
  • Log tick counts and compare to wall time.
  • Use a logic analyzer to validate actual period.

7.3 Performance Traps

Avoid printf in the ISR; it will skew timing.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a second pattern mode selectable by button.

8.2 Intermediate Extensions

  • Add a watchdog to reset if ticks stop.

8.3 Advanced Extensions

  • Implement multiple timers for layered scheduling.

9. Real-World Connections

9.1 Industry Applications

  • User interfaces: LED patterns and UI timing in appliances.
  • Control systems: Deterministic scheduling for periodic tasks.
  • FreeRTOS timers: Reference for timer-driven scheduling.
  • libopencm3 timer examples: Timer configuration patterns.

9.3 Interview Relevance

  • Timer configuration and ISR timing questions.
  • Deterministic scheduling vs blocking delays.

10. Resources

10.1 Essential Reading

  • Making Embedded Systems by Elecia White - Timing and scheduling basics.
  • Definitive Guide to ARM Cortex-M by Joseph Yiu - Interrupt and timer details.

10.2 Video Resources

  • Timer interrupt tutorial for STM32.
  • Measuring jitter with logic analyzers.

10.3 Tools & Documentation

  • STM32CubeIDE: Build and flash firmware.
  • Logic analyzer: Measure ISR timing.

11. Self-Assessment Checklist

11.1 Understanding

  • I can compute prescaler and auto-reload values.
  • I understand interrupt latency and jitter.
  • I can explain why timers are deterministic.

11.2 Implementation

  • Timer tick accurate within 1%.
  • LED patterns run without blocking delays.
  • Drift report produced.

11.3 Growth

  • I can measure and explain drift sources.
  • I can adapt the state machine to new patterns.
  • I can present this project in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • LED sequence runs with timer tick.
  • No blocking delays.
  • Drift measurement logged.

Full Completion:

  • Multiple patterns supported.
  • Drift <1 ms over 10 minutes.

Excellence (Going Above & Beyond):

  • Automatic drift compensation implemented.
  • Pattern editor script added.