Project 4: PWM Motor or LED Brightness Controller

A PWM control system that maps duty cycle to visible brightness or motor speed with measured frequency and resolution.

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 4: Hardcore Tech Flex
Business Potential 1. The “Resume Gold”
Prerequisites Timer configuration, GPIO AF mapping, basic measurement
Key Topics PWM, duty cycle mapping, timer output compare

1. Learning Objectives

By completing this project, you will:

  1. Generate stable PWM with a chosen frequency and resolution.
  2. Map duty cycle values to hardware compare registers.
  3. Measure PWM frequency and duty with external tools.
  4. Understand trade-offs between frequency and resolution.

2. All Theory Needed (Per-Concept Breakdown)

PWM Generation and Output Compare

Fundamentals Pulse Width Modulation (PWM) is a way to create an analog-like output using digital timers. A timer counts up to a period value, and an output compare register defines when the output switches from high to low. The ratio of high time to total period is the duty cycle, which controls average power delivered to a load. PWM is used for LED brightness, motor speed, and power control. Understanding how to compute frequency and duty, and how resolution changes with frequency, is essential.

Deep Dive into the concept PWM on STM32 uses timer output compare units. The timer counts from 0 to ARR (auto-reload), and when the counter matches the CCR value, the output toggles or changes level depending on the mode. In PWM mode 1, the output is high when the counter is below CCR and low otherwise. Duty cycle equals CCR/ARR. Frequency is determined by the timer clock and ARR, so you choose ARR to meet a frequency target and then compute CCR for duty. There is a trade-off: higher frequency requires a smaller ARR, which reduces duty resolution. For LEDs, frequencies above ~200 Hz avoid flicker, while for motors you may target higher frequencies to reduce audible noise. STM32 timers can also operate in center-aligned mode, which reduces harmonic content by toggling at both up and down counts, useful for motor control. In practice, you must consider the load. LEDs are current-driven devices; PWM controls perceived brightness, but current-limiting resistors and LED forward voltage determine actual current. Motors are inductive and respond to average voltage, but at low PWM frequencies you may get torque ripple. You also need to verify the waveform. A logic analyzer or scope can confirm frequency and duty, but you can also measure average voltage with a multimeter as a rough check. Another subtlety is timer channel remapping and pin configuration: the GPIO pin must be set to the correct alternate function, and the timer output must be enabled in the CCER register. If you forget either step, you may see no output despite correct timer math. PWM is thus a perfect example of hardware-software integration: you compute the numbers in firmware, but the output is only real if the pin is configured and the load is connected correctly.

How this fit on projects In PWM Motor or LED Brightness Controller, you generate PWM on a timer channel and map duty cycle steps to visible brightness or speed changes.

Definitions & key terms

  • Duty cycle -> Percentage of time the signal is high within one period.
  • ARR -> Auto-reload register that sets PWM period.
  • CCR -> Capture/compare register that sets duty cycle.
  • Center-aligned -> PWM mode that counts up and down for symmetrical waveforms.
  • Resolution -> Number of distinct duty steps, equal to ARR+1.

Mental model diagram (ASCII)

Counter: 0 .... CCR .... ARR
PWM:    ____|''''''''|________ (PWM mode 1)

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

  1. Choose a timer and target PWM frequency.
  2. Compute ARR from timer clock to hit the desired frequency.
  3. Compute CCR values for desired duty cycle steps.
  4. Configure the pin for the correct timer alternate function.
  5. Invariant: duty cycle equals CCR/ARR; failure mode: wrong AF or CCER disables output.

Minimal concrete example

// 1 kHz PWM, 50% duty
TIM1->PSC = 71; // 72 MHz / 72 = 1 MHz
TIM1->ARR = 1000 - 1;
TIM1->CCR1 = 500;

Common misconceptions

  • Duty cycle equals brightness linearly ignores human perception and LED current curves.
  • Higher frequency is always better ignores resolution and driver limits.
  • PWM output appears automatically ignores pin AF and CCER enable bits.

Check-your-understanding questions

  1. How does ARR affect PWM resolution?
  2. Why might a 50% duty cycle not look half as bright?
  3. What is the relationship between PWM frequency and motor audible noise?

Check-your-understanding answers

  1. ARR+1 defines the number of duty steps; smaller ARR means lower resolution.
  2. Perceived brightness is logarithmic, and LED current is nonlinear.
  3. Low PWM frequencies create torque ripple and audible whining; higher frequencies reduce it.

Real-world applications

  • LED dimming in consumer electronics.
  • DC motor control and fan speed regulation.
  • Power converters and battery charging systems.

Where you’ll apply it

References

  • STM32F3 Reference Manual (timer PWM modes).
  • Elecia White, ‘Making Embedded Systems’ (PWM and control).
  • ST application notes on motor control PWM.

Key insights

  • PWM is a precise digital mechanism that controls analog behavior through duty cycle and frequency.

Summary PWM bridges digital timers and analog behavior. By mastering timer configuration, duty mapping, and load effects, you can create stable brightness and speed control.

Homework/Exercises to practice the concept

  1. Compute ARR and CCR values for 500 Hz PWM with 10% duty at 72 MHz.
  2. Measure PWM duty with a multimeter and compare to expected average voltage.
  3. Try 200 Hz vs 1 kHz on an LED and describe perceived differences.

Solutions to the homework/exercises

  1. At 72 MHz, prescale to 1 MHz, ARR=2000-1 for 500 Hz, CCR=200 for 10%.
  2. Average voltage should be duty * Vcc; small errors are expected due to measurement limits.
  3. 200 Hz may show flicker, while 1 kHz appears steady to most observers.

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 PWM Motor or LED Brightness Controller, 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.

Clock Tree, PLL, and Prescalers (System Timing Backbone)

Fundamentals Every peripheral timing and every delay in your firmware depends on the clock tree. The STM32F3 can run from internal or external oscillators, and uses a PLL to multiply those sources to reach the desired system clock. From that system clock, prescalers divide down to generate the AHB, APB1, and APB2 bus clocks, which in turn feed timers, ADCs, UARTs, and other peripherals. If you misconfigure any divider, you will see subtle failures: UART baud errors, PWM drift, incorrect ADC sampling, and watchdog timeouts. A clock tree audit is the practice of proving, with measurements, that your configured frequencies match reality. It is the single most valuable habit for embedded reliability.

Deep Dive into the concept The STM32F3 clock system is a directed graph of sources, multipliers, and dividers. At the root are the HSI (internal RC oscillator) and HSE (external crystal or clock). The PLL takes one of those sources and applies multiplication factors to produce a higher-frequency output used as the system clock. On STM32 devices, the system clock (SYSCLK) feeds the AHB bus, while APB1 and APB2 buses are derived by additional prescalers. Timers on APB buses often have a ‘x2’ effect when the prescaler is not 1, which means timer clocks can be double the APB frequency. If you forget that rule, PWM frequencies are off by exactly 2x, a classic bring-up trap. The clock tree is also linked to power: higher frequencies increase current draw, and some peripherals require specific clock ranges. USB and ADCs, for example, require precise clocks to meet protocol or sampling accuracy requirements. The RCC (Reset and Clock Control) registers configure all of this. A good audit reads those registers at runtime and recomputes derived clocks, then compares expected timing (based on configuration) to observed timing (based on measurement). Measurement can be as simple as toggling a GPIO at a known rate and timing it with a stopwatch, or as precise as using a logic analyzer to measure a timer output. A thorough audit includes the SysTick frequency, timer tick frequency, and peripheral baud rate verification. There are also failure modes: the external crystal may not start, the PLL may fail to lock, or the clock security system may switch back to HSI without warning. In robust firmware, you detect and log these events. The clock tree is therefore not just a setup routine, but an operational dependency that must be measured. When you develop on the STM32F3DISCOVERY, you should record expected frequencies, read RCC_CFGR to confirm clock sources, and validate at least one timer output. This practice makes you immune to the most common cause of ‘mystery bugs’ in embedded projects: silent timing errors.

How this fit on projects In PWM Motor or LED Brightness Controller, you audit the clock tree by reading RCC configuration, computing derived clocks, and validating time with measured outputs (LEDs, timers, or UART).

Definitions & key terms

  • HSI -> Internal high-speed oscillator, convenient but less accurate.
  • HSE -> External crystal oscillator, more accurate but requires hardware.
  • PLL -> Phase-Locked Loop that multiplies an input frequency.
  • SYSCLK -> System clock feeding the CPU core.
  • AHB/APB -> Buses that distribute clocks to memory and peripherals.

Mental model diagram (ASCII)

HSI/HSE -> PLL -> SYSCLK -> AHB -> APB1/APB2 -> Peripherals
         |                |            |
       prescalers       timers       UART/ADC

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

  1. Select a clock source (HSI or HSE) and enable it.
  2. Configure PLL multipliers/dividers to reach target SYSCLK.
  3. Set AHB/APB prescalers to keep peripherals within spec.
  4. Validate that timers and UARTs use the expected derived clocks.
  5. Invariant: All configured clocks must be within device limits; failure mode: peripheral timing mismatch or PLL unlock.

Minimal concrete example

// Pseudocode: compute SYSCLK from RCC registers
uint32_t sysclk = rcc_get_sysclk();
uint32_t hclk = sysclk / ahb_prescaler();
uint32_t pclk1 = hclk / apb1_prescaler();
printf("SYSCLK=%lu HCLK=%lu PCLK1=%lu\n", sysclk, hclk, pclk1);

Common misconceptions

  • If SYSCLK is correct, peripherals are correct ignores bus prescalers and timer x2 behavior.
  • HSI is good enough for all peripherals ignores baud accuracy and ADC sampling constraints.
  • PLL always locks ignores startup and clock security failures.

Check-your-understanding questions

  1. Why can a timer run at twice the APB clock on STM32?
  2. How would you detect that the HSE crystal failed to start?
  3. What is the difference between SYSCLK and HCLK?
  4. Why does UART baud depend on clock prescalers?

Check-your-understanding answers

  1. When APB prescaler is not 1, timer clocks are doubled to preserve timer resolution.
  2. Check RCC flags for HSE ready or clock security events and log a fallback to HSI.
  3. SYSCLK feeds the core; HCLK is SYSCLK after the AHB prescaler.
  4. UART divider is computed from the peripheral clock; a mismatch shifts baud rate.

Real-world applications

  • Motor control where PWM frequency accuracy matters.
  • Precision sensing with ADC sampling rate requirements.
  • Communication interfaces that need accurate baud rates.

Where you’ll apply it

References

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

Key insights

  • If you cannot measure your clock tree, you cannot trust any timing claim in your system.

Summary Clock configuration is a system-wide contract. A small prescaler mistake can propagate into every peripheral. A clock audit makes timing visible and protects you from silent configuration errors.

Homework/Exercises to practice the concept

  1. Compute expected timer tick rates for APB1 and APB2 given a target SYSCLK.
  2. Toggle a GPIO every 1000 SysTick ticks and measure the period with a stopwatch.
  3. Force a fallback to HSI and log the resulting SYSCLK change.

Solutions to the homework/exercises

  1. Use the RCC_CFGR register to determine prescalers and apply the x2 rule for timers.
  2. 1,000 ticks at 1 kHz should be 1 second; if not, check SysTick reload value.
  3. When HSE fails, the system clock source bit will indicate HSI; log the event over UART.

3. Project Specification

3.1 What You Will Build

A PWM controller that drives an LED or motor with a configurable duty cycle, logs frequency and duty, and validates output with measurement tools.

3.2 Functional Requirements

  1. PWM Output: Generate PWM at a selectable frequency (e.g., 1 kHz) on a timer channel.
  2. Duty Mapping: Support duty cycle steps from 0% to 100% in 10% increments.
  3. Measurement Report: Log configured frequency and measured duty.
  4. Safe Defaults: Default to 0% duty on startup.

3.3 Non-Functional Requirements

  • Performance: PWM frequency within 1% of target; duty error <2%.
  • Reliability: No glitches during duty cycle changes.
  • Usability: Duty cycle change via UART command or button.

3.4 Example Usage / Output

PWM freq: 1000 Hz
Duty: 10%
Duty: 50%
Duty: 90%
Status: PASS

3.5 Data Formats / Schemas / Protocols

UART log format: PWM_FREQ_HZ=<int> DUTY_PCT=<int>

3.6 Edge Cases

  • Duty set to 0% or 100% and output should be constant low/high.
  • PWM frequency too low causing visible flicker.
  • Timer channel not mapped to correct pin AF.
  • Motor driver not enabled and output appears dead.

3.7 Real World Outcome

You will see brightness or speed change smoothly with duty steps.

3.7.1 How to Run (Copy/Paste)

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

3.7.2 Golden Path Demo (Deterministic)

  • Step duty 10% -> 90% and observe brightness/speed.

3.7.3 CLI Transcript (Success)

PWM_FREQ_HZ=1000 DUTY_PCT=10
PWM_FREQ_HZ=1000 DUTY_PCT=50
PWM_FREQ_HZ=1000 DUTY_PCT=90
RESULT=PASS
# Exit code: 0

3.7.4 Failure Demo (Wrong AF)

PWM_FREQ_HZ=1000 DUTY_PCT=50
OUTPUT=NO_SIGNAL
RESULT=FAIL
# Exit code: 2

4. Solution Architecture

A timer output compare channel generates PWM; a control loop updates the duty cycle based on commands.

4.1 High-Level Design

UART/Button -> Duty Update -> Timer CCR -> PWM Output -> Load

4.2 Key Components

Component Responsibility Key Decisions
PWM Driver Configures timer, ARR, and CCR Fixed frequency with variable duty
Command Interface Updates duty from UART or button Clamp to 0-100% safely
Measurement Logger Logs configured duty and frequency Fixed log format

4.3 Data Structures (No Full Code)

typedef struct {
uint16_t arr;
uint16_t ccr;
uint16_t duty_pct;
} pwm_config_t;

4.4 Algorithm Overview

Duty Update

  1. Compute CCR = (ARR + 1) * duty_pct / 100.
  2. Write CCR atomically.
  3. Log updated duty.

Complexity: O(1) per update.


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

“How does a digital timer create analog-like control?”

5.4 Concepts You Must Understand First

  1. PWM duty cycle and frequency trade-offs.
  2. Timer output compare and pin AF mapping.
  3. Electrical load considerations (LED vs motor).

5.5 Questions to Guide Your Design

  1. What frequency avoids LED flicker?
  2. How will you map 0-100% to CCR?
  3. How will you measure PWM without a scope?

5.6 Thinking Exercise

Duty Mapping

ARR=999 -> 1000 ticks per period
Duty 25% -> CCR=250
Duty 50% -> CCR=500

5.7 The Interview Questions They’ll Ask

  1. Explain PWM and how it controls motor speed.
  2. What trade-offs exist between PWM frequency and resolution?
  3. How do you verify PWM output without a scope?

5.8 Hints in Layers

Hint 1: Start at 50% duty and verify the output. Hint 2: Keep frequency fixed, vary CCR. Hint 3: Use a multimeter to approximate average voltage.

5.9 Books That Will Help

Topic Book Chapter
PWM basics Making Embedded Systems Ch. 6
Timer compare Definitive Guide to ARM Cortex-M Ch. 9

5.10 Implementation Phases

Phase 1: Static PWM (2 days)

Generate a fixed 50% duty PWM.

Phase 2: Duty Control (4 days)

Add duty adjustments and logging.

Phase 3: Validation (3 days)

Measure frequency and duty; refine settings.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Frequency 200 Hz vs 1 kHz 1 kHz Avoid flicker and audible noise
Control input Button vs UART UART Precise duty control

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Duty mapping math CCR calculation checks
Integration Tests PWM output verification Logic analyzer measurement
Edge Case Tests 0%/100% duty Constant low/high

6.2 Critical Test Cases

  1. Frequency Accuracy: PWM frequency within 1% of target.
  2. Duty Steps: Duty changes by correct increments.
  3. No Glitch: No visible glitches during duty updates.

6.3 Test Data

ARR=999, duty=25% -> CCR=250
ARR=999, duty=50% -> CCR=500

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Wrong AF mapping No PWM output Check AF table and CCER bits
Frequency too low Visible flicker Increase timer clock or reduce ARR
Duty scaling error Nonlinear steps Recompute CCR with integer math

7.2 Debugging Strategies

  • Toggle a debug pin at duty update time to measure update rate.
  • Measure average voltage with a multimeter.
  • Confirm timer clock using RCC registers.

7.3 Performance Traps

Avoid changing ARR frequently; it can cause glitches if not synchronized.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a breathing LED effect using a duty ramp.

8.2 Intermediate Extensions

  • Add closed-loop control using a sensor input.

8.3 Advanced Extensions

  • Implement center-aligned PWM and compare harmonics.

9. Real-World Connections

9.1 Industry Applications

  • LED drivers: Brightness control in displays and indicators.
  • Motor control: Speed regulation in fans and pumps.
  • ST Motor Control SDK: PWM configurations for motor drivers.
  • SimpleFOC: Open-source motor control library.

9.3 Interview Relevance

  • PWM configuration and timer trade-off questions.
  • Duty cycle and resolution reasoning.

10. Resources

10.1 Essential Reading

  • Making Embedded Systems by Elecia White - PWM fundamentals and control.
  • Definitive Guide to ARM Cortex-M by Joseph Yiu - Timer output compare details.

10.2 Video Resources

  • PWM basics with STM32 timers.
  • Using PWM to control DC motors.

10.3 Tools & Documentation

  • Logic analyzer: Measure PWM duty and frequency.
  • Multimeter: Approximate average voltage.

11. Self-Assessment Checklist

11.1 Understanding

  • I can compute PWM frequency from ARR and prescaler.
  • I understand duty cycle vs perceived brightness.
  • I can explain timer output compare operation.

11.2 Implementation

  • PWM output measured and matches target.
  • Duty cycle updates without glitches.
  • Logs show correct values.

11.3 Growth

  • I can adapt PWM to a new timer channel.
  • I can explain frequency/resolution trade-offs.
  • I can describe how to measure PWM without a scope.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • PWM output at target frequency.
  • Duty cycle changes work.
  • Measurement logged.

Full Completion:

  • Multiple duty profiles supported.
  • Verified with external measurement.

Excellence (Going Above & Beyond):

  • Closed-loop control with sensor feedback.
  • Frequency vs resolution study documented.