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.

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

  1. 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\).
  2. 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.
  3. 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.
  4. 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

  1. 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\)).
  2. 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 vTaskGetRunTimeStats CPU percentages (independent method).
  3. 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

  1. 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.
  2. 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

  1. 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.
  2. 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).
  3. 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

  1. 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.