Project 4: Software PWM LED Dimmer
Generate PWM in software, dim an LED, and measure scheduling jitter.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2: Intermediate |
| Time Estimate | 8-12 hours |
| Main Programming Language | C (Alternatives: Python) |
| Alternative Programming Languages | Python |
| Coolness Level | Level 2: Solid Engineering |
| Business Potential | 1. The “Resume Gold” |
| Prerequisites | GPIO output, timing functions, PWM basics |
| Key Topics | PWM, duty cycle, jitter, scheduling |
1. Learning Objectives
By completing this project, you will:
- Explain the core question: How reliable is timing when you generate PWM in software on Linux?
- Implement the full hardware read/write path with correct configuration.
- Handle at least two failure modes with clear error messages.
- Validate output against a deterministic demo.
2. All Theory Needed (Per-Concept Breakdown)
Software PWM and Linux Timing Jitter
Fundamentals Software PWM uses timed GPIO toggles to approximate analog output. It is not enough to know the high-level description; you must understand the exact sequence of configuration steps, the expected signals, and the hardware limits. A GPIO line controlled from user space is only reliable when you respect Linux scheduler jitter and sleep granularity. The goal is to build a mental model that connects software intent to physical reality, so you can reason about failures and verify results with measurements. You should be able to explain what each signal means, which register or API controls it, and how the device responds to configuration changes.
In embedded work, this conceptual clarity is the difference between trial-and-error and engineering. If you can predict how the system should behave, you can diagnose why it doesn’t. That is why this fundamentals section emphasizes not just definitions, but the sequence of actions and the reasons behind them.
Deep Dive into the concept
Configuration comes first. Choose a PWM period and duty cycle, compute on/off durations, and implement a loop using nanosleep or busy-waiting. Decide how to measure timing errors (timestamps or logic analyzer). In practice, you should start with conservative settings and validate each step before moving on. A wrong mode, wrong address, or wrong pin function often produces silent failures. This project forces you to verify the interface at the protocol level—reading an ID register, observing a waveform, or confirming a response—before trusting higher-level logic.
The next layer is the protocol or signaling format itself. With A GPIO line controlled from user space, every byte, pulse, or edge has meaning. You should be able to map software commands to the on-the-wire representation and back again. That means understanding register maps, frame formats, or pulse widths, and knowing which values are valid or reserved. When you can describe the precise shape of the data, you can validate correctness with a logic analyzer or raw byte logs.
Timing is the second pillar. At 100 Hz, a 10 ms period must be split into on/off segments. Any scheduler delay distorts duty cycle, so measure jitter and compare under load. Linux is not a real-time OS, so you must decide whether user-space timing is sufficient or whether hardware support is required. When you need deterministic behavior, you should use hardware peripherals or kernel-space timing. This project includes a deterministic golden demo so you can measure timing and compare against expectations.
Electrical constraints are unavoidable. LEDs require current limiting. GPIO pins are 3.3 V and have safe current limits; use a 330-1k resistor. These are not theoretical concerns; violating voltage or current limits can damage the board or produce unreliable signals. This project explicitly integrates safe wiring patterns, such as level shifting, driver boards, or separate power rails, and requires you to document them in your lab notes.
Reliability depends on error handling. Plan for NACKs, framing errors, timeouts, noisy inputs, or disconnected devices. A robust system retries, backs off, and logs clear diagnostic information so failures can be reproduced. In this project, you will implement explicit timeouts and sanity checks so that errors become visible events, not silent data corruption.
Debugging and validation complete the loop. Capture the waveform on a logic analyzer and log per-cycle timestamps. Compare expected period and duty to actual measurements under CPU load. The goal is to correlate what your code thinks is happening with what the hardware is actually doing. If you can see the waveform, log the raw bytes, and reproduce the golden demo, you can trust your system. If you cannot, you must adjust your assumptions and re-check each layer.
A deeper look at Software PWM LED Dimmer starts with sequencing. Even simple hardware interactions require a strict order: configure the interface, validate the device, perform the transaction, and only then interpret results. The key topics here ({key_topics}) each have parameters that must be chosen deliberately, such as bus speed, pin mode, edge polarity, or timing period. When these are wrong, failures can look random. The discipline is to set conservative defaults, verify each step with a minimal test (like reading a device ID or toggling a pin), and then increase complexity gradually. This mirrors real-world bring-up procedures on embedded boards, where one wrong assumption can waste hours.
How this fits on projects This concept is the foundation for this project and determines whether your implementation is reliable or fragile.
Definitions & key terms
- PWM: Pulse Width Modulation
- Duty cycle: Fraction of the period high
- Jitter: Variation from expected timing
Mental model diagram
Desired PWM -> scheduler jitter -> actual PWM waveform
How it works (step-by-step)
- Compute on/off durations
- Set GPIO high
- Sleep on-time
- Set low
- Sleep off-time
Minimal concrete example
set_gpio(1); nanosleep(&on, NULL); set_gpio(0); nanosleep(&off, NULL);
Common misconceptions
- Software PWM is precise
- Higher frequency is always better
Check-your-understanding questions
- Why does CPU load affect PWM?
- Why is hardware PWM preferred for servos?
Check-your-understanding answers
- Scheduling delays shift toggle times.
- Hardware PWM runs independent of CPU scheduling.
Real-world applications
- LED dimming
- Low-frequency signal generation
Where you’ll apply it
- See §5.4 and §6.2
- Compare with P08-hardware-pwm-servo.md
References
- Linux scheduler docs
- Making Embedded Systems - timing chapter
Key insights Software PWM exposes the limits of a non-real-time OS.
Summary You can generate PWM in software, but you must measure and accept jitter.
Homework/Exercises to practice the concept
- Run PWM at 100 Hz and log jitter.
- Stress CPU and compare timing.
Solutions to the homework/exercises
- Use a logic analyzer to compute period variance.
- Run
stress --cpuand compare logs.
3. Project Specification
3.1 What You Will Build
A software PWM dimmer that fades an LED and logs jitter and duty cycle accuracy.
3.2 Functional Requirements
- Implement the primary hardware interaction
- Provide CLI configuration
- Log raw data and converted output
- Handle error conditions
3.3 Non-Functional Requirements
- Performance: Meets timing or throughput expectations for the device.
- Reliability: Handles timeouts, disconnects, or missing devices safely.
- Usability: Clear CLI flags and readable logs.
3.4 Example Usage / Output
./soft_pwm --gpio 18 --freq 100 --duty 30
3.5 Data Formats / Schemas / Protocols
CSV log: timestamp, expected_period_us, actual_period_us, duty_percent.
3.6 Edge Cases
- Duty=0 or 100
- Frequency too high
- CPU under heavy load
3.7 Real World Outcome
LED dims smoothly while logs quantify jitter under different system loads.
3.7.1 How to Run (Copy/Paste)
cd project-root
make
./soft_pwm --gpio 18 --freq 100 --duty 30
3.7.2 Golden Path Demo (Deterministic)
Run ./soft_pwm --gpio 18 --freq 100 --duty 30 with default wiring and verify output matches expected physical behavior.
3.7.3 If CLI: exact terminal transcript
$$ ./soft_pwm --gpio 18 --freq 100 --duty 30
[INFO] PWM 100 Hz, duty 30%
[INFO] Avg period 10.01 ms, jitter 0.7 ms
^C
$$ echo $$?
0
Failure Demo (Deterministic)
$$ ./soft_pwm --gpio 18 --freq 5000 --duty 50
[ERROR] Frequency too high for software PWM
$$ echo $$?
2
4. Solution Architecture
4.1 High-Level Design
Input -> Interface -> Logic -> Output
4.2 Key Components
| Component | Responsibility | Key Decisions | |———–|—————-|—————| | Interface layer | Configure and transact | Use correct mode/speed | | Parser/Logic | Interpret data | Validate ranges | | Output | Logs/actuation | Deterministic output |
4.3 Data Structures (No Full Code)
struct Config { int mode; int rate; int pin; };
4.4 Algorithm Overview
Key Algorithm: Control/Read Loop
- Configure interface.
- Perform transaction.
- Validate output.
- Log or actuate.
Complexity Analysis: O(n) iterations.
5. Implementation Guide
5.1 Development Environment Setup
sudo apt-get update
sudo apt-get install -y build-essential
5.2 Project Structure
project-root/
├── src/
│ └── main.c
├── Makefile
└── README.md
5.3 The Core Question You’re Answering
“How reliable is timing when you generate PWM in software on Linux?”
5.4 Concepts You Must Understand First
- Electrical limits
- Interface configuration
- Timing constraints
5.5 Questions to Guide Your Design
- How will you verify the hardware response?
- What timeout is safe?
- What is your retry strategy?
5.6 Thinking Exercise
Map each software step to a physical signal transition or bus event.
5.7 The Interview Questions They’ll Ask
- Explain the key interface parameters.
- What failure modes did you handle?
- How did you verify timing?
5.8 Hints in Layers
Hint 1: Start with default bus speeds. Hint 2: Log raw bytes before parsing. Hint 3: Use a logic analyzer.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | Timing and scheduling | Making Embedded Systems | Ch. 6 | | Linux timing APIs | The Linux Programming Interface | Ch. 21 |
5.10 Implementation Phases
Phase 1: Bring-up (3 hours)
Goals: Verify device presence. Checkpoint: First successful transaction.
Phase 2: Core loop (4-6 hours)
Goals: Stable operation. Checkpoint: Deterministic output.
Phase 3: Robustness (2-4 hours)
Goals: Error handling. Checkpoint: Clear logs and exit codes.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Interface mode | default, custom | default | Minimize variables | | Logging | stdout, file | stdout | Simpler debugging |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit | Config parsing | CLI flags | | Integration | Hardware IO | On Pi | | Edge | Missing device | Error path |
6.2 Critical Test Cases
- Golden path success
- Bad argument -> exit 2
- Device missing -> clear error
6.3 Test Data
Default config; invalid flag
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Wrong wiring | No response | Re-check pinout | | Wrong mode | Garbage data | Verify settings | | No timeouts | Hangs | Add timeout |
7.2 Debugging Strategies
- Use dmesg for kernel errors
- Log raw data
7.3 Performance Traps
Excessive logging or busy loops can distort timing.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a status LED
- Add config file support
8.2 Intermediate Extensions
- Add retry and backoff
- Add CSV/JSON output
8.3 Advanced Extensions
- Hardware timestamps
- Kernel driver variant
9. Real-World Connections
9.1 Industry Applications
- Prototyping
- Diagnostics
9.2 Related Open Source Projects
- libgpiod
- spidev
- mosquitto
9.3 Interview Relevance
- Demonstrates interface and timing knowledge
10. Resources
10.1 Essential Reading
- Raspberry Pi docs
- Device datasheet
10.2 Video Resources
- Interface tutorials
10.3 Tools & Documentation
- i2c-tools, spidev, libgpiod
10.4 Related Projects in This Series
- P01-sysfs-legacy-blink.md
- P02-register-blink-mmio.md
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the interface parameters
- I can reason about timing limits
11.2 Implementation
- Hardware responds consistently
- Errors handled
11.3 Growth
- I can integrate this into larger systems
12. Submission / Completion Criteria
Minimum Viable Completion:
- Basic hardware interaction works
- Deterministic demo runs
Full Completion:
- Error handling and logs
- Documentation updated
Excellence (Going Above & Beyond):
- Performance measurements
- Extended features