Build an active low-pass filter around the MCP6002, sweep it with DAC-generated sine tones, and measure its magnitude response point-by-point to construct a Bode plot by hand. You’ll set a cutoff frequency with an RC, watch the roll-off match −20 dB/decade (first-order) or −40 dB/decade (Sallen–Key second-order), and — crucially — build the exact circuit that will serve as the anti-alias filter in front of the STM32 ADC in Module 5. Every sampled-data system needs a low-pass filter before the sampler to keep out-of-band energy from folding into the signal; this lab is where you build, characterize, and trust that filter. Reconciling a measured Bode plot against \(H(j\omega)\) is the core skill of analog-front-end design.
Recommended reading
PEI Ch. 9 — active filters: frequency-selective response, magnitude and phase, and the meaning of cutoff and bandwidth for a continuous-time filter.
Lyons Ch. 6 — FIR/analog filter fundamentals and how a filter’s frequency response is specified and read (passband, stopband, transition, roll-off). → Understanding DSP
PEI Ch. 9 — Filters: the active-filter portions — active low-pass topologies (Sallen–Key), cutoff, damping/Q, and roll-off — the practical build-it version of this lab. Skip the switched-capacitor and exotic approximation sections unless pursuing extensions.
MCP6002 dual op-amp with 0.1 µF decoupling; 100 kΩ/100 kΩ mid-rail divider (bias for single supply).
Siglent SDS1104X-E scope + two 10× probes.
Signal source: the MCP4725 DAC (Lab 3.3), programmed to output a mid-rail sine you can step across frequency. (The fixed ~1 kHz probe-comp square wave is not a sweep source and is not a sine — unsuitable here.)
Resistors and capacitors for a chosen cutoff (worked below for \(f_c \approx 1.6\) kHz): e.g. R = 10 kΩ, C = 10 nF; for the Sallen–Key add a matched second R/C and gain-set resistors.
Breadboard + jumpers.
Wiring & bench setup
The signal chain (first-order build): the MCP4725’s mid-rail-centred sine drives R, C rolls the node off against the bypassed mid-rail VB, amplifier A buffers it as a follower, and the scope reads input and output tone-by-tone.
Setup gotchas: C returns to VB, not the − rail — the bypassed mid-rail is this circuit’s AC ground; if you AC-couple the DAC instead of DC-coupling, add the 100 kΩ bias resistor pin 3 → VB per Procedure step 2 or the input floats; the Sallen–Key of Part C rewires this same chip (second R/C plus the \(R_f = 5.9\text{ kΩ}/R_g = 10\text{ kΩ}\) gain network) — pin map otherwise unchanged; probe switch AND channel menu at 10×.
Safety & don’t-break-it
ESD handling of the bare DIP (body only, insert with power off, decouple first) — same discipline as Labs 4.1–4.3.
Keep signals linear and inside the rails. Use a small mid-rail-centered input so the op-amp never clips (Lab 4.3) — a clipped input invalidates a magnitude measurement. Keep the DAC output within 0–5 V.
Discharge and observe polarity on any electrolytic used for coupling; prefer small ceramic/film caps for the filter’s frequency-setting C (lower tolerance drift and no polarity issue).
Common grounds for DAC, scope, op-amp, supply.
Project & environment setup
No new firmware — the MCP4725 keeps running the Lab 3.3 sine generator. Host side (the Part D plot) runs on the Mac, in the course venv (see Toolchain):
source venv/bin/activate # numpy + matplotlib are all this lab needsmkdir-p labs/lab-4-4/host labs/lab-4-4/captures
Put your Bode-plot script in labs/lab-4-4/host/ (numpy for the dB math, matplotlib for the semilog magnitude plot — you write the script; it’s ~15 lines).
Keep this lab’s reconciliation in labs/lab-4-4/host/analysis.ipynb — the notebook convention — and export final figures next to it.
LTspice schematic — both filters; .ac sweep = sim third opinion vs. the hand math and the bench
hardware/lab-4-4.asc
LTspice sim (third opinion) — mechanics in the LTspice workflow; the lab-specific recipe:
Draw (hardware/lab-4-4.asc, both circuits): (1) the first-order build — source → R = 10 kΩ → IN+, C = 10 nF from IN+ to the bypassed VB node, follower jumper; (2) the equal-component Sallen–Key (\(R = 10\text{ kΩ}\), \(C = 10\text{ nF}\), \(K = 1.586\) via \(R_f = 5.9\text{ kΩ}\) / \(R_g = 10\text{ kΩ}\)).
Op-amp:UniversalOpAmp2 with GBW = 1 Meg and 0/5 V rails, or the .include MCP6002 macromodel — per the workflow.
Source:SINE(2.5 0.2 1k) (mid-rail, small) with AC amplitude = 1.
Directive:.ac dec 100 10 1Meg.
Plot/cross-check:V(out)/V(in) in dB → every row of the predicted-\(|H|\) table (100 Hz … 30 kHz), the −3 dB cursor vs. \(f_c \approx 1.59\) kHz, and the −20 vs. −40 dB/dec slopes; near the top of the sweep the op-amp’s own GBW roll-off joins in — the very effect named in Analysis. The sim also covers the 10/30 kHz points the MCP4725 can’t reach, so overlay sim + hand math + bench in the notebook.
Background
First-order active LPF. The simplest realization buffers an RC low-pass with the op-amp (unity-gain, so the RC isn’t loaded), or places R and C in the feedback of an inverting stage. Either way the transfer function is single-pole:
The magnitude is \(|H| = H_0/\sqrt{1+(\omega/\omega_c)^2}\): flat (\(H_0\)) in the passband, −3 dB at \(f_c\), then falling at −20 dB/decade (one pole). For \(R = 10\text{ kΩ}, C = 10\text{ nF}\):
For the equal-component version (\(R_1=R_2=R\), \(C_1=C_2=C\)) with a unity-gain buffer, \(f_0 = 1/(2\pi RC)\) and the quality factor\(Q\) is set by the stage gain \(K = 1+R_f/R_g\) via \(Q = 1/(3-K)\). Choosing \(K = 1.586\) gives \(Q = 0.707\) — the Butterworth (maximally flat) response. Above \(f_0\) the magnitude falls at −40 dB/decade — twice as steep as first-order, which is why a second-order anti-alias filter buys you a much cleaner cutoff for the same corner frequency.
Reading the Bode plot. Magnitude in dB is \(20\log_{10}|H|\); frequency on a log axis. The passband is a flat line at \(20\log_{10}H_0\); the roll-off is a straight line of slope −20 (1st-order) or −40 (2nd-order) dB/dec; they intersect at the corner \(f_c\). You’ll build this plot from measured tone-by-tone gains.
Procedure
Part A — Build the first-order filter.
Bring up the MCP6002 (VDD=8, VSS=4, decoupled), mid-rail bias VB ≈ 2.5 V (per Wiring & bench setup).
Build a unity-gain first-order LPF: drive R = 10 kΩ in series into IN A+ (pin 3), put C = 10 nF from pin 3 to the bypassed mid-rail node VB, and wire A as a follower (OUTA→IN A−). This buffers the RC so downstream loads don’t shift \(f_c\). Give pin 3 a DC path to the bias, or the input floats and the follower slams to a rail: because the DAC output is already mid-rail-centred (≈ 2.5 V from Lab 3.3), the simplest choice is to DC-couple the DAC straight into R — R then carries the 2.5 V bias to pin 3. If instead you AC-couple the DAC through a series cap, you must add a 100 kΩ bias resistor from pin 3 to VB (as in Lab 4.2); otherwise the coupling cap and C both block DC and IN A+ has no path to the bias.
Power on. Verify DC: OUTA sits at ≈ 2.5 V.
Part B — Sweep and measure the magnitude response.
DAC → mid-rail sine, small amplitude, starting at 100 Hz. Scope CH1 = filter input (after the coupling cap, at R’s source), CH2 = OUTA. Use auto-measure Vpp on both.
Step the frequency across at least a decade below to a decade above \(f_c\): e.g. 100 Hz, 300 Hz, 1 kHz, 1.59 kHz, 3 kHz, 10 kHz, 30 kHz. At each point record \(|H| = V_\text{pp,out}/V_\text{pp,in}\) and \(20\log_{10}|H|\) dB. Source-ceiling caveat: the MCP4725 over I²C can only synthesize a clean sine to roughly 1–3 kHz (its update rate limits how many samples/period it can emit), so the top points here (10 kHz, 30 kHz) are likely out of reach with the DAC alone. Two options: (a) measure only up to the DAC’s clean-sine ceiling and confirm the roll-off trend toward −20 dB/dec, or (b) use the STM32 on-chip DAC driven by timer + DMA (introduced in Lab 5.4) as a higher-frequency sine source to reach the full sweep. Don’t mistake the DAC’s own reconstruction roll-off for the filter’s — verify the input amplitude (CH1) is flat across frequency before trusting a gain point.
Locate the −3 dB frequency (where gain = 0.707× passband) and compare to the predicted \(f_c \approx 1.59\) kHz. Verify the high-frequency slope is ≈ −20 dB/dec (gain drops ~20 dB from \(f_c\) to \(10 f_c\)).
Part C — Second-order Sallen–Key (optional, recommended for the anti-alias role).
Rebuild as an equal-component Sallen–Key with \(R = 10\text{ kΩ}, C = 10\text{ nF}\) (same \(f_0 \approx 1.59\) kHz) and gain \(K = 1.586\) (e.g. \(R_g = 10\text{ kΩ}, R_f = 5.86\text{ kΩ} \approx 5.9\text{ kΩ}\)) for a Butterworth \(Q = 0.707\).
Repeat the sweep. Confirm the roll-off is now ≈ −40 dB/dec and the passband is maximally flat (no peaking).
Part D — Plot by hand.
On semi-log paper (or plot in Python), draw the Bode magnitude: measured points, the flat passband asymptote, and the roll-off asymptote; mark \(f_c\) at their intersection.
Deliverable & expected results
A bench note (labs/lab-4-4/notes.md) with the swept magnitude table and a hand-drawn/plotted Bode magnitude curve for the first-order (and, if built, the Sallen–Key) filter, annotated with \(f_c\) and the roll-off slope.
Confirm the measured −3 dB corner matches \(1/(2\pi RC)\) within component tolerance — note ceramic caps can be ±10–20%, so measure the actual C on the LC1020E (Lab 0.3) and recompute \(f_c\); that usually explains most of the corner error. Check the high-frequency asymptote slope by fitting the dB-vs-log-f points: it should be −20 (or −40) dB/dec, and any shortfall at very high frequency is the MCP6002’s own GBW rolloff (Lab 4.2) adding to the filter’s — a real effect worth naming. For the Sallen–Key, watch for passband peaking: if \(Q\) came out above 0.707 (gain \(K\) too high), you’ll see a bump before the corner; trim \(R_f\) down to flatten it. Finally, connect to sampling: state the ADC sample rate \(f_s\) you plan for Module 5 and confirm this filter attenuates energy at and above \(f_s/2\) enough to keep aliased content below your ADC’s noise floor — that is the whole reason this filter exists.
Going further
This is your anti-alias filter — but re-tune the corner before carrying it to Lab 5.4 — Aliasing. Lab 5.4 runs \(f_s = 2\) kHz, so Nyquist is \(f_N = 1\) kHz and you need \(f_c \lesssim 1\) kHz — not the 1.59 kHz worked here. Scale R or C up (e.g. R = 16 kΩ or C = 16 nF gives \(f_c \approx 1\) kHz). Then sample a tone above Nyquist with and without the filter in place and watch the alias appear/disappear.
Measure and plot the phase response too; a first-order LPF passes through −45° at \(f_c\) and approaches −90°, a second-order Butterworth through −90° at \(f_0\) toward −180°.
Design the filter for a specific \(f_s\): pick \(f_c\) roughly \(f_s/2.5\) and compute R, C; predict the attenuation at \(f_s/2\) for first- vs. second-order and decide which order your Module 5 chain needs.
Cascade the first-order and Sallen–Key sections (both MCP6002 halves) for a third-order filter and confirm −60 dB/dec.