Lab 4.4 — Active Low-Pass Filter

Course 2 syllabus · Module 4 · Prev: « Lab 4.3 · Next: Lab 5.1 »

Goal

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.

Equipment & parts

  • WANPTEK supply at 5.0 V, current-limited.
  • 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.

flowchart LR
  DAC["MCP4725 sine<br/>(mid-rail, Lab 3.3)"] --> RC["R = 10 kΩ into pin 3<br/>C = 10 nF pin 3 → VB<br/>fc ≈ 1.59 kHz"]
  RC --> AMP["MCP6002 amp A<br/>follower buffer"]
  PSU["WANPTEK<br/>5.0 V, limit ~100 mA"] --> AMP
  AMP --> SCP["Siglent<br/>CH1 = filter input<br/>CH2 = OUTA"]

flowchart LR
  DAC["MCP4725 sine<br/>(mid-rail, Lab 3.3)"] --> RC["R = 10 kΩ into pin 3<br/>C = 10 nF pin 3 → VB<br/>fc ≈ 1.59 kHz"]
  RC --> AMP["MCP6002 amp A<br/>follower buffer"]
  PSU["WANPTEK<br/>5.0 V, limit ~100 mA"] --> AMP
  AMP --> SCP["Siglent<br/>CH1 = filter input<br/>CH2 = OUTA"]

Pin map (every wire; DC-coupled variant of Procedure step 2):

From To Pin/jack
WANPTEK + / + rail (5.0 V) / − rail banana → jumper
Nucleo GND − rail — common ground GND
MCP4725 VDD·GND·SCL·SDA Nucleo 3V3·GND·D15·D14, unchanged from Lab 3.3
MCP4725 OUT R = 10 kΩ → IN A+ (DC-coupled: R carries the bias to pin 3) pin 3
IN A+ C = 10 nF → node VB pin 3
+ rail 100 kΩ → node VB → 100 kΩ → − rail; 0.1 µF from VB → − rail
OUTA IN A+ buffer feedback — the follower jumper pin 1 → pin 2
MCP6002 VDD / VSS + rail / − rail (100 nF pin 8 → pin 4 at the chip) pin 8 / pin 4
Scope CH1, 10× DAC side of R (the filter input); ground clip → − rail
Scope CH2, 10× OUTA (pin 1)
 WANPTEK: + ──► + rail (5.0 V) · − ──► − rail · Nucleo GND ──► − rail

 + rail ──[100 kΩ]──● VB ──[100 kΩ]── − rail    [0.1 µF] VB → − rail

              MCP6002 — top view, pin-1 notch/dot at top
                    ┌────∪────┐
             OUTA 1 ┤         ├ 8 VDD ──●── + rail
             INA− 2 ┤         ├ 7 OUTB     [100 nF] pin 8 → pin 4, at the chip
             INA+ 3 ┤         ├ 6 INB−
             VSS  4 ┤         ├ 5 INB+
                    └─────────┘
 VSS (4) ── − rail
 DAC OUT ──●──[R = 10 kΩ]──► INA+ (3) ──[C = 10 nF]──► VB
          CH1                                   (C returns to the AC ground VB)
 OUTA (1) ──► INA− (2)                          (follower jumper = the buffer)
 scope: CH1 (10×) → DAC side of R · CH2 (10×) → OUTA (1) · clips → − rail

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 needs
mkdir -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.

Where results go:

Artifact Path
Bench note (sweep table + Bode plot) labs/lab-4-4/notes.md
Hand-recorded sweeps (CSV: freq_hz,vpp_in,vpp_out) labs/lab-4-4/captures/sweep-1st.csv, sweep-sk.csv
Bode magnitude plot (Part D) labs/lab-4-4/host/bode.png
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:

\[ H(j\omega) = \frac{H_0}{1 + j\omega/\omega_c}, \qquad \omega_c = \frac{1}{RC}, \qquad f_c = \frac{1}{2\pi RC}. \]

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}\):

\[ f_c = \frac{1}{2\pi (10^4)(10^{-8})} = \frac{1}{2\pi \times 10^{-4}} \approx 1.59\text{ kHz}. \]

Second-order Sallen–Key LPF (steeper, better anti-alias). Two RC sections around one op-amp give a two-pole response:

\[ H(j\omega) = \frac{H_0\,\omega_0^2}{\omega_0^2 - \omega^2 + j\,\omega\omega_0/Q}, \qquad \omega_0 = \frac{1}{\sqrt{R_1 R_2 C_1 C_2}}, \qquad f_0 = \frac{\omega_0}{2\pi}. \]

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.

  1. Bring up the MCP6002 (VDD=8, VSS=4, decoupled), mid-rail bias VB ≈ 2.5 V (per Wiring & bench setup).
  2. 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.
  3. Power on. Verify DC: OUTA sits at ≈ 2.5 V.

Part B — Sweep and measure the magnitude response.

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

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

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

Frequency Predicted \(\lvert H\rvert\) (1st-order) Predicted dB Measured dB
100 Hz 0.998 −0.02 dB
1.0 kHz 0.847 −1.44 dB
1.59 kHz (\(f_c\)) 0.707 −3.0 dB
3.0 kHz 0.469 −6.6 dB
10 kHz 0.157 −16.1 dB
30 kHz 0.053 −25.5 dB

Predicted from \(|H| = 1/\sqrt{1+(f/1.59\text{k})^2}\). Cutoff \(f_c = 1/(2\pi RC) \approx 1.59\) kHz; Sallen–Key \(Q = 0.707\) (Butterworth).

Analysis & reconciliation

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.