Lab 7.3 — End-to-End Capstone
← Course 2 syllabus · Module 7 · Prev: « Lab 7.2 · Next: Lab 8.1 »
Goal
This is the culmination of Modules 0–7: a complete, robust, real-time DSP system on the STM32, built from every skill in the course. A timer-triggered ADC + DMA front end (Module 5) feeds an FIR/FFT processing stage (Module 6), whose result drives a DAC / UART output — all running as tasks under the FreeRTOS pipeline (Lab 7.2), guarded by the watchdog and fault handling (Lab 7.1). You will fully characterize it — end-to-end latency, throughput, and CPU headroom — and then do the reconciliation that defines this whole course: an A/B comparison of the digital filter against the Module 4 analog active filter, the “analog twin,” measuring both with the same instruments and explaining every difference. The deliverable is a staff-level write-up that could stand in a design review: what was predicted, what was measured, where they diverged, and why.
Recommended reading
- Kuo (all) — the whole book comes together here: real-time architecture (Ch. 1), fixed-point vs. float (Ch. 3), FIR/IIR real-time implementation (Ch. 4–5), FFT (Ch. 6), and DMA/interrupt-driven I/O. Use it as the implementation reference for the pipeline.
- Lyons (all) — the practitioner’s view: FIR design and linear phase, the DFT/FFT, windowing and spectral leakage, and the number-format/quantization chapters. Your filter-design and spectrum-interpretation decisions come from here.
- O-DSP (all) — Oppenheim & Schafer for the rigorous backing: z-transform and stability, filter design methods, the DFT as sampled DTFT, and the sampling theorem. Cite it where you justify a design choice formally.
- Course 1: Week 9 (complex analysis — poles/ROC/stability behind the filter), Week 10 (distributions/Dirac comb — the sampling theorem behind the ADC front end), Weeks 1–3 (linear algebra / least-squares behind FIR design).
Equipment & parts
- STM32 Nucleo-64 (NUCLEO-L476RG) + USB to ST-LINK.
- Signal source: the MCP4725 DAC (Module 3) or the STM32’s own DAC/PWM generating the test stimulus — the Siglent base unit has no built-in generator, so you synthesize the input. A second Nucleo DAC channel or an external source also works.
- Module 4 analog active filter (Lab 4.4) on the breadboard — the “analog twin” for the A/B comparison, built around the MCP6002.
- Siglent SDS1104X-E scope (FFT math, auto-measure, CSV export) and Saleae Logic 8 (per-stage timing).
- Fluke 117 DMM; WANPTEK PSU for the analog rail.
- STM32CubeIDE (FreeRTOS project from Lab 7.2 + ADC/DMA from Module 5).
Safety & don’t-break-it
- Respect the 3.3 V analog world. The STM32 ADC input must stay 0–3.3 V (V_DDA) — it is not 5 V tolerant on analog pins. Bias and scale your test signal so it sits inside 0–3.3 V before it reaches the ADC pin; a swing that clips the rail corrupts the measurement and can stress the pin. Use the Module 4 buffer/divider to set the DC bias.
- Common grounds across the whole chain. Source, analog filter, Nucleo, scope, and Saleae must share one ground, or your A/B comparison measures ground loops, not filters. Clip scope grounds only to circuit ground.
- Discharge and de-power for analog changes. When rewiring the MCP6002 filter, power down the WANPTEK rail; observe MCP6002 supply limits (1.8–6 V) and handle the DIP by the body (ESD).
- DAC output drive is weak. The STM32/MCP4725 DAC outputs can’t drive heavy loads — buffer with the MCP6002 follower (Lab 4.1) before driving anything but a scope’s high-impedance input.
- Watchdog under the debugger. As in Lab 7.1, enable “Debug IWDG stopped” so the running watchdog doesn’t reset the board while you’re halted characterizing timing. Keep a known-good image to reflash if a short timeout loops.
- Nothing here is high-voltage or high-power — the hazards are all signal-integrity and part-protection.
Background
The full signal chain
The system is a real-time filter (and spectrum analyzer) with a bounded per-sample time budget:
\[ \underbrace{\text{TIM} \to \text{ADC} \to \text{DMA}}_{\text{acquire}} \;\longrightarrow\; \underbrace{\text{FIR / FFT}}_{\text{process}} \;\longrightarrow\; \underbrace{\text{DAC / UART}}_{\text{output}} \]
The timer sets the sample rate \(f_s\); the ADC samples on each trigger; DMA moves samples into a circular buffer with half- and full-transfer interrupts so Process always has a complete block while Acquire fills the other half (ping-pong). The hard constraint is that a block of \(N\) samples must be fully processed before the next block completes:
\[ C_\text{process}(N) \;\le\; N \cdot T_s = \frac{N}{f_s}. \]
CPU headroom is the fraction of the budget left over:
\[ U = \frac{C_\text{process}}{N\,T_s}, \qquad \text{headroom} = 1 - U . \]
You want \(U\) comfortably below 1 (say \(\le 0.7\)) so jitter and interrupts never push a block past its deadline. Throughput is \(f_s\) if the per-sample tasks keep up, or \(f_s \cdot (\text{block rate})\) for block processing; latency is the acquire-edge-to-output-edge time from Lab 7.2.
The FIR filter and its analog twin
The digital stage is a linear-phase FIR:
\[ y[n] = \sum_{k=0}^{M-1} h[k]\,x[n-k], \]
with a passband/stopband you choose to match the Module 4 analog active filter’s corner. The analog twin (Lab 4.4) is a Sallen–Key (or MFB) active low-pass with cutoff \(f_c\) and a Butterworth/Bessel response. The A/B comparison holds the specification constant (same \(f_c\), same input) and contrasts the two implementations:
| Property | Analog active filter | Digital FIR |
|---|---|---|
| Phase | nonlinear (analog) | exactly linear (symmetric taps) |
| Cutoff accuracy | R/C tolerance (±5–10%) | exact (defined by coefficients) |
| Noise/quantization | thermal + op-amp noise | quantization + rounding noise |
| Aliasing | none (continuous) | needs anti-alias filter before ADC |
| Reconfigurability | rewire parts | change coefficients |
| Latency | \(\sim\) group delay of the analog network | \(\frac{M-1}{2}T_s\) (FIR group delay) + pipeline |
The anti-alias point is the deep one: the digital filter cannot exist without an analog low-pass in front of the ADC (Module 4’s other job), so the honest comparison is “analog filter alone” vs. “analog anti-alias + digital filter.” Course 1 Week 10 is the reason the anti-alias filter is mandatory.
Procedure
Part A — Assemble the pipeline
- Start from the Lab 7.2 FreeRTOS project. Replace the placeholder acquire with the Module 5 timer-triggered ADC + DMA circular buffer (Lab 5.3): TIM triggers the ADC at \(f_s\), DMA into a double buffer, half/full-transfer callbacks hand a ready block to Acquire, which posts it to \(Q_1\).
- In Process, run the FIR (Lab 6.1) — use the CMSIS-DSP
arm_fir_f32(or a fixed-point variant per Kuo Ch. 3). Add an FFT path (Lab 6.3,arm_rfft_fast_f32) so the same pipeline can output a live spectrum. Keep the per-stage GPIO toggles from Lab 7.2. - In Output, write filtered samples to the DAC (reconstruct the analog output) and/or stream block metrics over UART (VCP). Buffer the DAC with the MCP6002 follower if driving anything real.
- Add the Lab 7.1 watchdog: refresh it from the lowest-priority task, and keep the fault handler in place. Now a hang or fault in any stage is caught.
Part B — Characterize timing and headroom
- With the Saleae on the three stage pins (Lab 7.2 method), capture per-stage execution time and end-to-end latency at a chosen \(f_s\) and block size \(N\) (e.g. \(f_s = 16\) kHz, \(N = 256\)).
- Read \(C_\text{process}\) (the FIR/FFT stage width) and compute utilization \(U = C_\text{process}/(N T_s)\) and headroom \(1-U\). Cross-check with
vTaskGetRunTimeStatsCPU percentages (independent method). - Find the throughput ceiling: raise \(f_s\) (or \(M\), or \(N\)) until Process misses its block deadline (queue backs up / a deadline pin overruns). Record the max sustainable \(f_s\) and filter length.
Part C — Measure the digital filter’s response
- Sweep the input frequency using the MCP4725/STM32 DAC as a source (step through frequencies, since there’s no analog sweep generator). At each frequency, measure the digital output amplitude on the scope (or from UART-reported RMS) and build the measured magnitude response \(|H_d(f)|\). Confirm the cutoff and stopband against the designed FIR.
- Capture the live FFT output for a known tone and compare the bin, leakage, and noise floor to Lab 6.3 expectations (windowing matters — note which window).
Part D — A/B against the analog twin
- Build/attach the Module 4 active low-pass (Lab 4.4) with the same cutoff as the FIR. Drive both the analog filter and the ADC front end from the same source at each swept frequency.
- On the scope, compare analog-filter output vs. DAC-reconstructed digital-filter output: magnitude at each frequency, phase (linear-phase FIR vs. the analog network’s nonlinear phase — use scope cursors / X-Y), transient/step response, and noise floor (scope FFT or Fluke AC).
- Tabulate both magnitude responses on one plot (host Python from the CSV exports), mark both cutoffs, and annotate where and why they diverge.
Part E — The staff-level write-up
- Write
docs/lab-7-3-reconciliation.md: the block diagram, the predicted numbers, the measured numbers, the timing/headroom budget, both filter responses overlaid, and a decision-review-quality discussion — which implementation you’d ship for which requirement, and the role of the anti-alias filter. This is the capstone artifact.
Deliverable & expected results
The full firmware/m7-rtos/ capstone project, host analysis scripts, Saleae + Siglent captures, and the reconciliation write-up. Record:
| Quantity | Predicted | Measured |
|---|---|---|
| Sample rate \(f_s\) / block \(N\) | 16 kHz / 256 → block period 16 ms | … |
| Process time \(C_\text{process}\) (FIR \(M\) taps, CMSIS-DSP @ 80 MHz) | \(\approx M\cdot N\) MACs / throughput | … |
| Utilization \(U = C_\text{process}/(N T_s)\) | target \(\le 0.7\) | … |
| CPU headroom \(1-U\) | \(\ge 0.3\) | … |
| End-to-end latency \(L\) | FIR group delay \(\frac{M-1}{2}T_s\) + pipeline | … |
| Throughput ceiling (max sustainable \(f_s\)) | where \(U \to 1\) | … |
| Digital cutoff \(f_c\) | FIR design value (exact) | … |
| Analog twin cutoff \(f_c\) | R/C design value (±tol) | … |
| Passband phase (digital vs. analog) | linear vs. nonlinear | … |
| Watchdog recovery on injected fault | resets & resumes within \(t_\text{IWDG}\) | … |
Analysis & reconciliation
Timing. Predict \(C_\text{process}\) from the MAC count (\(M\) taps \(\times\) \(N\) samples) and the Cortex-M4F’s single-cycle MAC / CMSIS-DSP throughput, then reconcile against the Saleae pulse width. Expect measured time to exceed the ideal MAC count because of flash wait states, FFT twiddle/bit-reversal overhead, and RTOS context switches — attribute the gap and confirm \(U\) stays below your safety threshold. If headroom is thin, that’s a real finding: state what you’d cut (shorter FIR, fixed-point, lower \(f_s\)) to buy margin.
Digital vs. analog. The magnitude responses should broadly agree in the passband; the informative differences are the reasons they diverge. The FIR cutoff is exact and its phase is linear (so the waveform shape is preserved — visible as no phase distortion on a multi-tone input), while the analog filter’s cutoff carries R/C tolerance (±5–10%, measure the actual parts from Lab 0.2/0.3 to explain the shift) and its phase is nonlinear (visible as waveform-shape change near cutoff). Compare noise floors: the analog path shows op-amp/thermal noise; the digital path shows quantization noise set by the 12-bit ADC and the coefficient word length (Kuo Ch. 3 / Lyons number-format chapters). Explicitly note that the digital result depends on the analog anti-alias filter in front of the ADC — the fair A/B is “analog filter” vs. “analog anti-alias + FIR,” and Course 1 Week 10 is why. Close with the staff-level judgment: for a fixed, well-defined corner with tight phase requirements, the FIR wins on accuracy and reconfigurability at the cost of latency and an anti-alias filter; for the lowest latency and no sampling constraints, the analog filter wins.
Robustness. Inject a fault mid-run (a deliberate bad access, per Lab 7.1) and confirm the watchdog + handler recover the system without corrupting the DMA buffers or leaving the DAC latched — a real product must survive this, and demonstrating it is the difference between a demo and a system.
Going further
- Fixed-point port: re-implement the FIR in Q15 (Kuo Ch. 3), measure the headroom gain and the added quantization noise, and reconcile the SNR change.
- Adaptive filter: swap the fixed FIR for an LMS adaptive filter (Hayes) that cancels a known interferer — the pipeline is already the right architecture for it.
- On-chip vs. edge: hand the same captured block to the Bonus Module 8 Pi 5 / Jetson pipeline and compare a learned filter against this classical one — the bridge from this course’s classical DSP to the edge-ML differentiator (Lab 8.1).
- Formal schedulability: attach the rate-monotonic utilization bound (Lab 7.2) and the worst-case latency analysis as an appendix, turning the measured characterization into a proof.
- Field-log the reconciliation: stream the block metrics over UART to the host and log them for a long run, so you can show timing/headroom stability over minutes — the kind of evidence a design review actually wants.