Project 11: Quadrature Encoder Reader
A hardware-timer-based quadrature decoder that reports position and speed with noise filtering.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 3: Advanced |
| 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 input filtering, interrupts |
| Key Topics | Quadrature decoding, encoder mode, debouncing |
1. Learning Objectives
By completing this project, you will:
- Configure a timer in encoder mode.
- Decode direction and position from A/B signals.
- Handle noise with filters and debouncing.
- Compute speed from count changes.
2. All Theory Needed (Per-Concept Breakdown)
Quadrature Encoding and Timer Encoder Mode
Fundamentals Quadrature encoders output two digital signals (A and B) that are 90 degrees out of phase. By observing which signal leads, you can determine direction, and by counting edges, you can measure position or speed. STM32 timers include an encoder mode that decodes these signals in hardware, making high-resolution position tracking efficient.
Deep Dive into the concept Quadrature encoding uses two square waves offset by a quarter period. When the shaft rotates, the order of transitions tells you direction. In X4 decoding, you count every rising and falling edge on both channels, giving four counts per mechanical cycle. STM32 timers support encoder interface mode, which configures the timer counter to increment or decrement based on the A/B inputs. This hardware approach avoids ISR overload when the encoder spins fast. However, real encoders are noisy. Mechanical encoders can bounce, and even optical encoders can have jitter. Filtering and debouncing are therefore important. STM32 timers include input filters that can reject short pulses. You must also handle counter overflow and wraparound. The timer counter is limited (e.g., 16-bit), so you need to extend it in software or periodically read and accumulate. Another consideration is zeroing and index pulses (Z channel). Some encoders provide an index pulse per revolution. If available, you can use it to re-zero position and correct drift. When computing speed, you can measure the change in counts over a fixed time interval or measure time between edges. Each approach has trade-offs between resolution and latency. A robust encoder reader documents counts per revolution, gear ratios, and how those map to physical units (degrees or meters). This project teaches both the signal-level behavior and the practical software strategies needed for accurate position tracking.
How this fit on projects In Quadrature Encoder Reader, you configure a timer in encoder mode, decode direction and position, and validate counts against a known rotation.
Definitions & key terms
- Quadrature -> Two-phase encoding using A/B signals offset by 90 degrees.
- X4 decoding -> Counting all edges on A and B for maximum resolution.
- Encoder mode -> Timer mode that counts based on A/B input transitions.
- Debounce -> Filtering to remove false transitions caused by noise or bounce.
- Counts per revolution -> Number of encoder counts for one full turn.
Mental model diagram (ASCII)
A: _-_-_-_
B: __-_-_- (A leads B -> direction)
How it works (step-by-step, with invariants and failure modes)
- Configure GPIO inputs for encoder A and B with appropriate pull-ups.
- Set the timer into encoder mode with input filters enabled.
- Read the counter periodically and compute delta counts.
- Convert counts to position or speed using counts-per-revolution.
- Invariant: counter increments/decrements consistently; failure mode: noise causing false counts.
Minimal concrete example
// Read position in degrees
int32_t counts = (int16_t)TIM2->CNT;
float degrees = (counts * 360.0f) / COUNTS_PER_REV;
Common misconceptions
- Encoders are noise-free ignores bounce and EMI.
- Position equals counts directly ignores gear ratios and wraparound.
- Polling is enough ignores high-speed transitions.
Check-your-understanding questions
- Why does X4 decoding give higher resolution?
- How do you handle timer counter overflow?
- What is the purpose of input filters in encoder mode?
Check-your-understanding answers
- It counts all edges of both channels, quadrupling counts per cycle.
- Extend counts in software by tracking overflows and using a larger accumulator.
- Filters reject short pulses to reduce noise-induced false counts.
Real-world applications
- Motor position control in robotics.
- User input devices like jog wheels.
- Industrial automation with precise motion tracking.
Where you’ll apply it
- In this project: see Section 4.2 Key Components and Section 6.2 Critical Test Cases.
- Also used in: P04 PWM Motor or LED Brightness Controller (closed-loop control extension).
References
- STM32F3 Reference Manual (timer encoder mode).
- Encoder datasheets (counts per revolution and signal characteristics).
- Motion control application notes.
Key insights
- Quadrature decoding is fundamentally about phase order; hardware timers make it reliable at high speed.
Summary Quadrature encoders provide direction and position with simple signals. Timer encoder mode lets you decode them accurately while minimizing CPU load.
Homework/Exercises to practice the concept
- Calculate counts-per-degree for a 1024 CPR encoder.
- Simulate encoder A/B signals and verify direction logic.
- Add software overflow tracking to extend a 16-bit counter.
Solutions to the homework/exercises
- Counts-per-degree = 1024 * 4 / 360 ~ 11.38 counts/degree.
- If A leads B, direction is forward; if B leads A, direction is reverse.
- Use a signed 32-bit accumulator and increment on overflow interrupt.
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 Quadrature Encoder Reader, 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)
- Select a timer and enable its clock.
- Compute prescaler and auto-reload for desired tick rate.
- Enable update interrupt and configure NVIC priority.
- In ISR, update the state machine and clear interrupt flags.
- 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
- What causes a factor-of-two error in timer frequency?
- How do you measure jitter in a timer-driven system?
- Why must you clear the update flag in the ISR?
Check-your-understanding answers
- Forgetting that timers may run at twice the APB clock when the prescaler is not 1.
- Toggle a GPIO in the ISR and measure period variation with a logic analyzer.
- 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
- In this project: see Section 4.4 Algorithm Overview and Section 5.10 Implementation Phases.
- Also used in: P04 PWM Motor or LED Brightness Controller, P05 ADC Sensor Sampler + Logging.
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
- Calculate prescaler and auto-reload for a 250 ms timer tick at 72 MHz.
- Toggle a GPIO on each timer interrupt and measure drift over 5 minutes.
- Experiment with different NVIC priorities and observe latency.
Solutions to the homework/exercises
- A 1 kHz tick requires prescaler 71999; a 250 ms interval uses auto-reload 249.
- Drift near zero indicates correct timer configuration; large drift indicates clock misconfig.
- Raising priority reduces latency but can starve lower-priority handlers.
GPIO Modes and Alternate Functions (Pin Multiplexing)
Fundamentals GPIO is the MCU’s physical interface to the outside world. Each pin can be configured as input, output, analog, or alternate function. Alternate function routes internal peripheral signals (UART, SPI, timers, etc.) to the pin. On STM32, a pin can support multiple alternate functions, but only one can be active at a time. A correct GPIO configuration must include mode, pull-up/pull-down, output type, speed, and alternate function selection. Misconfiguring any of these leads to silent failure, which is why a pin map validation project is essential.
Deep Dive into the concept The STM32 GPIO subsystem is both electrical and logical. Electrically, you choose input or output driver types (push-pull or open-drain), internal pulls, and slew rate. Logically, you choose whether the pin is GPIO-controlled or assigned to a peripheral. These decisions interact: for example, I2C requires open-drain outputs with pull-ups, while SPI requires push-pull. Alternate functions are selected via the AFR registers, which are split into low and high halves for pins 0-7 and 8-15. Each pin can have up to 16 alternate function mappings (AF0-AF15), and the mapping is defined in the datasheet’s AF table. You cannot infer AF numbers; you must consult the table. Additionally, some pins are already occupied on the discovery board by LEDs, sensors, or the ST-LINK interface. That means your pin map must include board-level constraints, not just MCU capability. A thorough pin map process starts with the board schematic, identifies which pins are routed to headers, and then annotates each with possible peripheral functions and conflicts. You should then validate at least one alternate function per peripheral class with a visible effect: UART TX should print, a timer channel should output PWM, and an ADC input should show a voltage change. This validation proves that your clock, GPIO configuration, and pin selection are all correct. It also trains you to resolve conflicts: if two peripherals want the same pin, you must choose a different mapping or a different peripheral instance. In real products, pin mux decisions are architectural: they affect board layout, firmware flexibility, and even BOM cost. By building a pin map and verifying functions, you practice the exact process used in professional embedded hardware bring-up.
How this fit on projects In Quadrature Encoder Reader, you document and verify a pin map that includes GPIO modes, alternate functions, and board-level conflicts.
Definitions & key terms
- Push-pull -> Output driver actively drives high and low; strong drive for digital outputs.
- Open-drain -> Output can only pull low; requires pull-up for high.
- Alternate function -> Pin routing selection for peripheral signals.
- Slew rate -> Output transition speed setting that affects EMI and signal integrity.
- Pin mux -> The multiplexer that selects which internal signal appears on a pin.
Mental model diagram (ASCII)
[Peripheral A]--
>--[Pin MUX]--> Pin PA9
[Peripheral B]--/
(select AF mapping)
How it works (step-by-step, with invariants and failure modes)
- Identify required peripherals and candidate pins from the datasheet.
- Check board schematic for pins already used by LEDs, sensors, or debug interfaces.
- Configure GPIO mode, pull, speed, and alternate function for each selected pin.
- Validate each mapped pin by creating a physical or observable signal.
- Invariant: each pin has a single active function; failure mode: silent conflicts or wrong AF number.
Minimal concrete example
// Configure PA9 as USART1_TX (AF7)
GPIOA->MODER |= (2 << (9 * 2));
GPIOA->AFR[1] |= (7 << ((9 - 8) * 4));
Common misconceptions
- Any pin can be mapped to any peripheral ignores fixed AF tables.
- GPIO defaults are safe ignores that pins may power up in analog mode.
- If a pin works once, it is always correct ignores board-level conflicts and alternate mappings.
Check-your-understanding questions
- Why does I2C require open-drain outputs?
- How do you find the correct AF number for a pin?
- What symptoms indicate that a pin conflict exists?
Check-your-understanding answers
- I2C uses wired-AND signaling where multiple devices pull the line low, requiring open-drain drivers and pull-ups.
- Consult the datasheet’s alternate function table for that specific pin and peripheral.
- Signals appear on the wrong pin, remain stuck, or multiple peripherals stop working when enabled together.
Real-world applications
- Board bring-up and pin validation in new hardware.
- Multi-peripheral embedded devices with strict pin budgets.
- Signal integrity tuning by selecting appropriate slew rates.
Where you’ll apply it
- In this project: see Section 5.4 Concepts You Must Understand First and Section 3.5 Data Formats / Protocols.
- Also used in: P09 I2C Sensor Driver and Calibration Log, P10 SPI Display or LED Matrix Driver.
References
- STM32F3 Reference Manual (GPIO and AF registers).
- STM32F3DISCOVERY board schematic (pin usage).
- Joseph Yiu, ‘The Definitive Guide to ARM Cortex-M3/M4’ (I/O configuration).
Key insights
- Pin muxing is an architectural choice; you must prove it with hardware behavior, not just code.
Summary GPIO configuration ties firmware to the physical board. Correct alternate-function selection, electrical mode, and board-level awareness are what turn a schematic into a working product.
Homework/Exercises to practice the concept
- Create a pin map table for 10 pins and include at least two alternate functions each.
- Verify one UART TX pin and one timer channel on hardware.
- Identify two pins that cannot be used because of on-board sensors.
Solutions to the homework/exercises
- A valid pin map includes port, pin, AF number, peripheral, and board conflict notes.
- UART TX can be verified by printing a known string at 115200 baud.
- On-board MEMS sensors occupy dedicated I2C/SPI pins; consult the schematic to identify them.
3. Project Specification
3.1 What You Will Build
A quadrature encoder reader that tracks position and speed using hardware timer encoder mode and logs results.
3.2 Functional Requirements
- Encoder Mode: Configure timer to decode quadrature signals.
- Position Tracking: Report position counts and direction.
- Speed Estimation: Compute speed from counts per interval.
- Noise Filtering: Enable input filter to reduce spurious counts.
3.3 Non-Functional Requirements
- Performance: Track at least 1 kHz edge rate without loss.
- Reliability: No false counts from noise under normal conditions.
- Usability: Logs in counts and converted units.
3.4 Example Usage / Output
Counts: 1024 Direction: CW
Speed: 120 RPM
Status: PASS
3.5 Data Formats / Schemas / Protocols
Log format: COUNT=<int> DIR=<CW|CCW> SPEED_RPM=<float>
3.6 Edge Cases
- Encoder signals bounce causing false counts.
- Timer counter overflow on long runs.
- Reversed A/B channels causing inverted direction.
- Noisy signals at high speed.
3.7 Real World Outcome
You will see accurate position and speed readings as the encoder rotates.
3.7.1 How to Run (Copy/Paste)
$ make flash
$ screen /dev/tty.usbmodem* 115200
3.7.2 Golden Path Demo (Deterministic)
- Rotate encoder 1 full turn; counts should match CPR.
3.7.3 CLI Transcript (Success)
COUNT=4096 DIR=CW SPEED_RPM=120
RESULT=PASS
# Exit code: 0
3.7.4 Failure Demo (Channels Swapped)
DIR=CCW (expected CW)
RESULT=FAIL
# Exit code: 2
4. Solution Architecture
Timer encoder mode counts A/B transitions; software converts counts to position and speed.
4.1 High-Level Design
Encoder A/B -> Timer Encoder Mode -> Count -> Speed Calc -> UART
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Encoder Input | GPIO inputs with filters | Enable digital filtering |
| Timer Encoder | Counts quadrature transitions | X4 decoding for resolution |
| Speed Calculator | Compute RPM from delta counts | Fixed interval measurement |
4.3 Data Structures (No Full Code)
typedef struct {
int32_t count;
float rpm;
} encoder_state_t;
4.4 Algorithm Overview
Speed Estimation
- Read count at interval start.
- Read count at interval end.
- delta = end - start; compute RPM.
Complexity: O(1) per interval.
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 do I reliably track position and direction from a quadrature encoder?”
5.4 Concepts You Must Understand First
- Quadrature signals and direction.
- Timer encoder mode configuration.
- Debouncing and filtering.
5.5 Questions to Guide Your Design
- What counts-per-revolution does your encoder provide?
- How will you detect and handle overflow?
- What filter setting removes noise without losing edges?
5.6 Thinking Exercise
Resolution Calculation
Encoder CPR=1024
X4 decoding -> 4096 counts/rev
Counts per degree = 4096/360 = 11.38
5.7 The Interview Questions They’ll Ask
- What is quadrature encoding?
- Why use timer encoder mode instead of interrupts?
- How do you compute RPM from counts?
5.8 Hints in Layers
Hint 1: Verify A/B signals with a logic analyzer. Hint 2: Start with slow rotations to validate direction. Hint 3: Use input filters to reduce noise.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Motion control | Control Systems text | Encoder chapter |
| Timers | STM32 Reference Manual | Timer encoder mode |
5.10 Implementation Phases
Phase 1: Signal Verification (2 days)
Verify A/B waveforms and direction.
Phase 2: Encoder Mode (4 days)
Configure timer and read counts.
Phase 3: Speed Calculation (3 days)
Compute RPM and validate.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Decoding | X2 vs X4 | X4 | Maximum resolution |
| Speed calc | Delta counts vs time between edges | Delta counts | Simpler and stable |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | RPM calculation | Known delta counts |
| Integration Tests | Encoder count accuracy | One full rotation |
| Edge Case Tests | Noise handling | Injected jitter |
6.2 Critical Test Cases
- One Revolution: Counts match expected CPR*4.
- Direction: CW/CCW detected correctly.
- Speed: RPM matches manual measurement.
6.3 Test Data
CPR=1024 -> counts=4096 for one rev
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Swapped channels | Direction reversed | Swap A/B or invert polarity |
| No filtering | Spurious counts | Enable input filter |
| Overflow | Count jumps | Extend counter in software |
7.2 Debugging Strategies
- Use slow rotation to verify count increments.
- Log counts at fixed intervals.
- Confirm signal integrity with scope or analyzer.
7.3 Performance Traps
High-speed encoders can overflow small counters quickly; extend in software.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a zeroing command to reset position.
8.2 Intermediate Extensions
- Use an index pulse if available.
8.3 Advanced Extensions
- Implement closed-loop motor control using encoder feedback.
9. Real-World Connections
9.1 Industry Applications
- Robotics: Position tracking for actuators.
- CNC machines: Precision motion control.
9.2 Related Open Source Projects
- SimpleFOC: Uses encoder feedback for motor control.
- ODrive: Open-source motor controller with encoder support.
9.3 Interview Relevance
- Quadrature decoding and encoder interface questions.
- Position and speed measurement trade-offs.
10. Resources
10.1 Essential Reading
- Motion Control Handbook by Various - Encoder signal interpretation.
- STM32F3 Reference Manual by ST - Encoder mode configuration.
10.2 Video Resources
- Quadrature encoder basics tutorial.
- STM32 timer encoder mode demo.
10.3 Tools & Documentation
- Logic analyzer: Inspect A/B signals.
- STM32CubeIDE: Build and debug.
10.4 Related Projects in This Series
- P04 PWM Motor or LED Brightness Controller - enabling closed-loop control.
- P07 Interrupt Latency Profiler - timing measurement skills.
11. Self-Assessment Checklist
11.1 Understanding
- I can explain quadrature signals and direction.
- I can configure timer encoder mode.
- I can compute speed from counts.
11.2 Implementation
- Position tracking is accurate.
- Speed calculation matches reference.
- Noise filtering reduces false counts.
11.3 Growth
- I can adapt to different encoder resolutions.
- I can implement a zeroing mechanism.
- I can explain this system in interviews.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Encoder counts tracked.
- Direction correct.
- Basic speed estimate.
Full Completion:
- Noise filtering enabled.
- Overflow handling implemented.
Excellence (Going Above & Beyond):
- Closed-loop control extension built.