Learning Goals 5 min
- Recognise the "jitter" of a raw
analogReadon a still sensor — even a held-still pot reading bounces ±2 around its true value. - Smooth a noisy reading with a running average — store the last N samples in an array, average them, and use the average instead of the raw value.
- Tune the window size
Nto trade off responsiveness (small N) against smoothness (large N).
Warm-Up 10 min
Plug your potentiometer in from yesterday. Open the Serial Plotter (Tools → Serial Plotter, baud 9600) instead of the Monitor. Upload the L02-07 pot-reader sketch. Don't touch the pot.
Predict
What does the plotter line look like with the pot held perfectly still?
Reveal
It should look like a flat horizontal line — but it isn't. The line wobbles up and down by a couple of units constantly. That wobble is real: thermal noise in the ADC, tiny vibrations in the pot, EMI from the USB cable. Even when nothing is "happening", the chip is reporting microscopic changes. Today we smooth them out.
New Concept · Running average 20 min
Why noise happens
Three sources you can't avoid:
- ADC noise — the chip's last bit or two is always uncertain. Inherent to the design.
- Power-rail ripple — your USB cable and laptop supply aren't a perfectly clean 5 V; small voltage wobbles drift the reading.
- Vibration / EMI — even a quiet desk has vibrations that nudge a pot wiper; a phone nearby radiates RF.
The result: a perfectly steady input still reports values that bounce ±1 to ±5 raw units. For a knob you're reading by hand, that's invisible — but if you drive an LED brightness from the raw value, you'll see it flicker constantly.
The fix — average over a short window
Instead of using the latest reading, keep the last N readings in an array and use their average. The random ±2 noise on each individual reading cancels out across the window.
const int N = 10;
int samples[N];
int idx = 0;
long total = 0; // running sum
void setup() {
Serial.begin(9600);
for (int i = 0; i < N; i++) samples[i] = 0;
}
void loop() {
total -= samples[idx]; // remove the oldest from the sum
samples[idx] = analogRead(A0);
total += samples[idx]; // add the newest
idx = (idx + 1) % N; // wrap-around index
int avg = total / N;
Serial.println(avg);
delay(20);
}The two ideas that make this fast
- Circular buffer. The
idxwalks 0, 1, 2, ..., N−1, 0, 1, 2, ... — overwriting the oldest sample each pass.idx = (idx + 1) % Ndoes the wrap. - Running sum. Instead of re-adding the whole array every time (a loop of N adds), keep a running total. When you replace a sample, subtract the old value and add the new. Two arithmetic ops, regardless of N.
How big should N be?
| N | Effect | When to use |
|---|---|---|
| 1 | No smoothing — raw value. | Never, in practice. |
| 3–5 | Mild smoothing — kills the worst spikes, feels responsive. | Knob driving an LED, joystick driving a servo. |
| 10–20 | Solid smoothing — flat line on the plotter. | Temperature readings, slow trends. |
| 50+ | Heavy smoothing — sluggish response. | Climate sensors, long-term logging. |
Rule of thumb: pick the smallest N that hides the noise you care about. Bigger N feels more "laggy" — turn the pot quickly and the smoothed reading takes a beat to catch up.
The long on total
Each sample is 0–1023. With N = 50, the worst-case sum is 1023 × 50 = 51,150 — well within a 32-bit long but over the 32,767 limit of a 16-bit int. Use long on the sum to be safe at any N.
Worked Example · Smoothing the pot reading 20 min
Step 1 — wiring
Same pot wiring as L02-07. Pot wiper → A0, outer legs to +5 V and GND. No new hardware today.
Step 2 — type the sketch
Save as pot-smooth.ino:
// L02-08: running-average smoother for the pot on A0
const int POT = A0;
const int N = 10;
int samples[N];
int idx = 0;
long total = 0;
void setup() {
Serial.begin(9600);
for (int i = 0; i < N; i++) samples[i] = 0;
}
void loop() {
total -= samples[idx];
samples[idx] = analogRead(POT);
total += samples[idx];
idx = (idx + 1) % N;
int raw = samples[(idx + N - 1) % N];
int avg = total / N;
Serial.print(raw);
Serial.print(",");
Serial.println(avg);
delay(20);
}Note the two-column Serial output (raw, avg, separated by a comma) — this is the format the Arduino Serial Plotter understands as "plot two lines".
Step 3 — upload and open the Serial Plotter
Tools → Serial Plotter (not Monitor). You should see two coloured lines:
- The first (orange-ish in default theme) is the raw reading — wobbly even when you don't touch the pot.
- The second (blue) is the smoothed average — much flatter.
Turn the knob slowly. Both lines follow, but the smoothed line trails slightly behind — that's the lag from averaging.
Step 4 — vary N and watch the effect
Change const int N = 10; to 3, then 20, then 50, re-uploading each time:
| N | What you see on the plotter |
|---|---|
| 3 | Smoothed line still wobbles a bit. Catches up to your knob turn almost instantly. |
| 10 | Smoothed line is clearly flat when still, follows your knob with a short delay. |
| 20 | Very flat when still, but turning the knob quickly leaves a visible lag on the smoothed line. |
| 50 | Like watching the knob through a slow camera — the smoothed line lazily follows after the raw line. |
This trade-off is fundamental: more smoothing always costs responsiveness. Pick the smallest N that hides the wobble.
Try It Yourself 20 min
Goal: Wire your LDR (10 kΩ pull-down) on A1. Add a second smoother for A1 — another array, another idx, another total — and print both averages side-by-side.
Hint
The cleanest fix is to wrap the smoother in a struct or class — but we haven't taught those yet. For now just copy-paste the variables and use suffixes: samples_A, idx_A, total_A for A0; samples_B, idx_B, total_B for A1. Two parallel buffers, no interaction between them.
Goal: Drive an LED on pin ~9 from the smoothed pot reading. The LED brightness should change smoothly as you turn the pot — no flicker at any position.
Hint
int avg = total / N;
analogWrite(LED, avg / 4); // 0-1023 → 0-255If your N is too small (say 1 or 2), the LED will visibly flicker even when the pot is still. If N is too big (say 50), the LED will lag behind your hand. N = 10 is usually the sweet spot.
Goal: Reduce the running-average code to a reusable helper function: int smoothed(int sensorPin). The function should keep its own internal state — meaning the buffer, idx and total are static inside the function, not global.
Hint
int smoothed(int pin) {
const int N = 10;
static int samples[N]; // persists between calls
static int idx = 0;
static long total = 0;
total -= samples[idx];
samples[idx] = analogRead(pin);
total += samples[idx];
idx = (idx + 1) % N;
return total / N;
}The static keyword makes the variables persist across calls — they keep their values instead of being re-initialised every time. Catch: this version only works for one sensor — calling smoothed(A0) and smoothed(A1) would share the same buffer and confuse the readings. Fixing that needs L03's classes.
Mini-Challenge · The ghost-detector display 15 min
Wire the LDR on A1 with its 10 kΩ pull-down. Smooth the reading with N = 20. If the smoothed value drops by more than 50 units within one second, blink an LED on pin 13 three times — like a ghost just passed in front of the sensor and cast a shadow.
It works if:
- The sensor sits steady when nothing moves — no false triggers from random noise.
- Wave your hand slowly across the sensor → LED blinks three times.
- Cover and uncover quickly → still triggers (the drop is fast and definitely > 50).
- Walk past quickly → triggers reliably.
Reveal one valid sketch
const int LDR = A1;
const int LED = 13;
const int N = 20;
const int THRESHOLD = 50;
int samples[N];
int idx = 0;
long total = 0;
int prevAvg = 0;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
for (int i = 0; i < N; i++) samples[i] = 0;
}
void loop() {
total -= samples[idx];
samples[idx] = analogRead(LDR);
total += samples[idx];
idx = (idx + 1) % N;
int avg = total / N;
Serial.println(avg);
if (prevAvg - avg > THRESHOLD) {
for (int i = 0; i < 3; i++) {
digitalWrite(LED, HIGH); delay(100);
digitalWrite(LED, LOW); delay(100);
}
// re-read after the blink so we don't re-trigger on the same shadow
for (int i = 0; i < N; i++) samples[i] = analogRead(LDR);
total = 0;
for (int i = 0; i < N; i++) total += samples[i];
avg = total / N;
}
prevAvg = avg;
delay(50);
}The re-read after the blink is the "reset the baseline" trick — without it, the LED would keep blinking until the LDR settled back to normal light. THRESHOLD = 50 is tuned for typical room light; adjust for your room (more sunlight = higher numbers, so threshold scales up).
Recap 5 min
Every analog input wobbles a little — that's physics, not bad code. A running average of the last N readings hides the wobble. Use a circular buffer + running sum so the cost stays constant no matter how large N is. Pick the smallest N that flattens the noise; bigger N = more lag. Next lesson — calibration: how to make a sensor figure out its own min and max during the first 5 seconds.
- Jitter / noise
- Tiny random fluctuations in a sensor reading. Caused by thermal noise, power-supply ripple and EMI.
- Running average
- The average of the last N readings. Smooths short-term noise while still tracking long-term changes.
- Circular buffer
- A fixed-size array used to store the most recent N values. The write index wraps around with
idx = (idx + 1) % N. - Running sum
- A maintained total so you don't re-add the whole buffer each pass. Subtract the value you're replacing, add the new value.
- Window size (N)
- How many recent samples to average. Trade-off: bigger N = smoother but laggier.
- Static variable
- A variable inside a function that keeps its value between calls instead of being re-initialised each time. Useful for "remember state" in a helper.
Homework 5 min
Tune-the-window. With your LDR on A1, run the smoother with four different N values: 1, 5, 10, 25. For each:
- Sit still for 30 seconds. Note the range of values you see (highest − lowest).
- Wave your hand slowly over the sensor. Note how quickly the value catches up.
- Decide which N you'd use for: (a) a fast-reacting toy, (b) a slow climate logger.
Then write one sentence: "The cost of smoothing more is ____."
Bring back next class:
- Your four-N comparison table.
- The completed sentence.
- Your homework sketch saved as
hw-l02-08.ino.