Measuring RC Filter Cutoff Frequency

Pico W with a 10 kΩ resistor from GP15 to a 0.1 µF ceramic capacitor, GP26 reading the node between them, capacitor other leg to the ground rail

Measuring RC Filter Cutoff Frequency

What You'll Learn

  • The cutoff frequency fc = 1 / (2πRC) and what it means.
  • Measure filter behaviour below, near, and above cutoff.
  • See ~70.7% output amplitude at cutoff and what −3 dB means.

What You'll Need

  • Raspberry Pi Pico W on a half breadboard
  • The RC low-pass filter from the previous lesson: one 10 kΩ resistor and one 0.1 µF ceramic capacitor — non-polarised, so no polarity to worry about
  • A few jumper wires
  • Comfortable building a filter (the Build an RC Low-Pass Filter lesson) and reading a plot in the editor (the Using the Plotter lesson)

Background

You already know this filter smooths fast changes and passes slow ones — you watched the output swing shrink as you drove it faster. This lesson pins down the changeover speed with a number: the cutoff frequency, written fc, the frequency where the filter starts to noticeably hold the output back.

Before you build anything, work out where that cutoff should land for these parts. The formula (which you'll meet properly after you measure) is fc = 1 / (2πRC). With a 10 kΩ resistor and a 0.1 µF capacitor:

Plain Text

fc = 1 / (2 × π × 10000 Ω × 0.0000001 F)
   ≈ 1 / (2 × π × 0.001)
   ≈ 159 Hz  (about 160 Hz)

So predict this: at drive speeds well below about 160 Hz the output should swing nearly as far as the input, and as you push the drive frequency up toward 160 Hz the output swing should shrink. Write down that prediction, then build the circuit and measure the swing to check it.

Build the Circuit

This is the same RC low-pass filter you built in the previous lesson — nothing to rewire if it's still on your breadboard. A resistor in series feeds a capacitor to ground; the Pico drives one end of the resistor with a square wave, and the point where the resistor meets the capacitor is the output node that the ADC reads.

  1. Place the 10 kΩ resistor so one leg is in hole f24 and the other in f23.
  2. Connect the Pico's GP15 to the resistor leg in row 24. This end drives the filter with the square wave.
  3. Place the 0.1 µF ceramic capacitor with one leg in row 23 (the same net as the resistor's other leg) and its other leg in row 22. The shared point in row 23 is the output node.
  4. Connect the Pico's GP26 to the output node in row 23. GP26 is analog input ADC0.
  5. Connect the capacitor's row-22 leg to the negative power rail.
  6. Connect a Pico GND pin to that same negative rail.

Note: The ceramic capacitor is non-polarised — it has no positive or negative leg, so it goes in either way round.

The output node sits between the resistor and the capacitor and is driven from 3.3 V through the resistor, so its voltage always stays between 0 V and 3.3 V. That keeps it inside the safe range for the ADC pin, which must never see more than 3.3 V.

The wiring diagram for this filter lives in the lesson's repository.

Pico W with a 10 kΩ resistor from GP15 to a 0.1 µF ceramic capacitor, GP26 reading the node between them, capacitor other leg to the ground railPico W with a 10 kΩ resistor from GP15 to a 0.1 µF ceramic capacitor, GP26 reading the node between them, capacitor other leg to the ground rail

Write the Code

This program drives the same filter with a square wave on GP15 and reads the output on GP26, just like the previous lesson, but now the job is to measure how big the output swing is at a chosen drive speed. Each pass it prints the commanded input and the measured output for the plotter, and it also tracks the smallest and largest output it sees over a measurement window. At the end of each window it prints a summary line: the drive frequency, the output's peak-to-peak swing, and the output swing as a fraction of the input's full 0-to-3.3 V swing. That fraction is the number to watch. Type it in and run it.

Python

from machine import Pin, ADC
import time

drive = Pin(15, Pin.OUT)   # GP15 drives the filter input through the 10 kOhm resistor
adc = ADC(26)              # GP26 / ADC0 reads the filter output (the RC node)

SAMPLE_STEP = 0.02          # seconds between readings (~50 samples per second)
INPUT_SWING = 3.3           # the input square wave swings the full 0 to 3.3 V

# Set the square-wave speed by flipping the input every N samples. One half-cycle lasts
# N * SAMPLE_STEP seconds, so the drive frequency is about 1 / (2 * N * SAMPLE_STEP) Hz.
# N = 25 -> ~1 Hz,  N = 5 -> ~5 Hz,  N = 2 -> ~12.5 Hz.  Smaller N means a faster drive.
SAMPLES_PER_HALF_CYCLE = 25

# How many samples to watch before reporting the output swing. A few full cycles is plenty.
WINDOW_SAMPLES = 200

# The drive frequency this setting produces, worked out from the numbers above.
drive_freq = 1.0 / (2 * SAMPLES_PER_HALF_CYCLE * SAMPLE_STEP)

# At low drive frequencies the output follows the input and swings almost the full 3.3 V, so
# the ratio below is near 1.0 (100%). As the drive speeds up toward and past the filter's
# cutoff, the output can't keep up and its swing shrinks, so the ratio falls. The cutoff is
# the frequency where the output swing drops to about 70.7% of the input, and the next
# section gives that a formula.

level = 1               # start driving the input high
count = 0               # samples counted in the current half-cycle
window_count = 0        # samples counted in the current measurement window
v_out_min = INPUT_SWING  # smallest output seen this window (start high so any reading beats it)
v_out_max = 0.0         # largest output seen this window (start low so any reading beats it)

while True:
    drive.value(level)                          # command the input high or low
    v_in = level * INPUT_SWING                  # commanded input, in volts (0 or 3.3)
    v_out = adc.read_u16() * 3.3 / 65535         # measured output, in volts
    print("{:.3f},{:.3f}".format(v_in, v_out))  # CSV: input series, output series
    time.sleep(SAMPLE_STEP)                      # ~50 samples per second

    # Track the smallest and largest output over this measurement window.
    if v_out < v_out_min:
        v_out_min = v_out
    if v_out > v_out_max:
        v_out_max = v_out

    count = count + 1
    if count >= SAMPLES_PER_HALF_CYCLE:          # time to flip the square wave
        level = 0 if level else 1
        count = 0

    window_count = window_count + 1
    if window_count >= WINDOW_SAMPLES:           # window done: report the output swing
        output_swing = v_out_max - v_out_min     # measured peak-to-peak output, in volts
        ratio = output_swing / INPUT_SWING       # output swing as a fraction of the input
        # Summary line (starts with #, so the plotter ignores it and you read it in the log):
        print("# drive_freq={:.1f}Hz  output_swing={:.3f}V  ratio={:.1f}%  (cutoff ~= 70.7%)".format(
            drive_freq, output_swing, ratio * 100))
        # Reset for the next window.
        window_count = 0
        v_out_min = INPUT_SWING
        v_out_max = 0.0

Each plotted line is two numbers: the input voltage the code just drove, then the output it measured, so the plotter draws them as two lines on one time axis. Every WINDOW_SAMPLES readings the code also prints a summary line starting with #, the drive frequency, the measured output swing in volts, and that swing as a percentage of the input's full 0-to-3.3 V swing. The plotter skips lines beginning with #, so those summaries show up in the serial log for you to read. New to the plotter? See Using the Plotter.

To characterise the filter, rerun the program with different SAMPLES_PER_HALF_CYCLE values, smaller numbers drive faster, and note the reported ratio each time. At slow drive speeds the ratio sits near 100%; as you speed up, it falls. The frequency where it reaches about 70.7% is the cutoff, and the next section explains why.

How It Works

The number you watched fall — the output swing as a fraction of the input — is the filter's response. At slow drive speeds it sits near 100%: the capacitor has time to charge and drain fully, so the output swings almost as far as the input. As the drive speeds up the capacitor runs out of time, the swing shrinks, and the ratio drops.

There's a specific speed where that drop reaches a marker worth naming. The cutoff frequency fc is the frequency where the output amplitude falls to about 70.7% of the input — that's 1/√2, roughly 0.707. Below fc the filter mostly lets the signal through; above it, the signal is increasingly cut down.

fc depends only on the resistor and the capacitor:

Plain Text

fc = 1 / (2πRC)

Plug in this filter's parts — R = 10 kΩ, C = 0.1 µF — and you get the number you predicted in the Background: about 160 Hz. Bigger R or bigger C means a slower cutoff (a lower fc), because a larger resistor or capacitor takes longer to charge, just like it stretched the charge curve in the earlier lessons.

You'll also hear the cutoff called the −3 dB point. A decibel (dB) is a way of describing a ratio, and −3 dB happens to mean the output carries about half the power of the input. When power halves, the voltage amplitude drops to about 70.7% — the same 0.707 number. So "cutoff", "70.7% amplitude", and "−3 dB" all point at the same spot. Don't worry about the decibel maths; just remember the cutoff is where the output swing has fallen to roughly seven-tenths of the input.

Note: The code samples about 50 times a second, so it can't cleanly draw a 160 Hz wave — that's far too fast to trace. What it can do is report how big the output swing is at each drive speed, and you watch that swing shrink as you push the drive faster. The calculated 160 Hz is your theoretical anchor; the measurement confirms the trend and the ~70.7% idea rather than pinpointing 160 Hz exactly.

Try It

Run the program, then rerun it a few times with different SAMPLES_PER_HALF_CYCLE values and read the # summary line each time — the one reporting drive_freq and ratio.

  • Well below cutoff. Start with SAMPLES_PER_HALF_CYCLE = 25 (about 1 Hz). The output swings nearly the full 3.3 V and the reported ratio sits near 100%. The filter is barely holding it back.
  • Faster. Drop to 5 (about 5 Hz), then 2 (about 12 Hz), rerunning each time. Watch the reported ratio fall as the drive speeds up — the output swing is shrinking.
  • As fast as it goes. Set SAMPLES_PER_HALF_CYCLE = 1 (about 25 Hz), the quickest this ~50 Hz sampler can drive. The ratio should be its lowest here.

You won't reach 160 Hz — the sampler tops out well below it — so you won't catch the exact moment the ratio hits 70.7%. That's expected. What you should see is a clear downward trend: the faster you drive, the smaller the fraction of the input that survives. Sketch ratio against drive_freq from your runs and you'll see it heading down toward that 70.7% mark, right on course for the 160 Hz cutoff the formula predicts.

Challenge

Change one part and predict where the cutoff moves before you measure. If you double the capacitor to 0.2 µF (or add a second 0.1 µF in parallel), the formula says fc should halve to about 80 Hz. A lower cutoff means the output should start shrinking at slower drive speeds than before.

Work out your new fc with fc = 1 / (2πRC), write it down, then swap the part and rerun the sweep of SAMPLES_PER_HALF_CYCLE values. Does the ratio start falling sooner — at lower drive frequencies — than it did with the original filter? Compare the two trends.

Tip: Right now you rerun the program by hand for each drive speed. As a later project, you could have the code step through several SAMPLES_PER_HALF_CYCLE values on its own and print one ratio per step — an automated frequency sweep. It's not needed here, but it's a natural next build.

Review

  • What two components set a filter's cutoff frequency, and what is the formula?
  • At the cutoff frequency, roughly what fraction of the input amplitude does the output reach?
  • What does the term "−3 dB point" refer to, and how does it relate to that 70.7% figure?
  • If you made the capacitor larger, would the cutoff frequency go up or down?