Learning Goals 5 min
- Learn the three core debugging moves: Print (show me what's happening), Isolate (which part is broken), Simplify (strip everything else away).
- Practise each on a sketch with planted bugs — there's a sketch later in this lesson that's deliberately broken in three ways. Find them.
- Build the habit of asking "what do I expect to see?" before running, then comparing to what you actually see.
Warm-Up 10 min
You've been debugging since the first lesson — every time a sketch didn't work, you found the problem somehow. Today we name those moves so you can apply them deliberately instead of by reflex. Three moves, used in roughly this order: Print → Isolate → Simplify. They're universal — Arduino, Python, web apps, anything.
The Sherlock principle
"When you have eliminated the impossible, whatever remains, however improbable, must be the truth." That's debugging in one sentence. Each move (print, isolate, simplify) is a way to eliminate possibilities.
New Concept · The three moves 25 min
Move 1 — Print (instrument the unknown)
Wherever you don't know what's happening, add a Serial.print. State of variables, values of sensors, timing of events. Print everything you suspect.
void loop() {
int raw = analogRead(LDR);
Serial.print("raw: ");
Serial.println(raw);
int pct = map(raw, dark, bright, 0, 100);
Serial.print("pct: ");
Serial.println(pct);
if (pct > 80) {
Serial.println("triggering alarm");
triggerAlarm();
}
delay(500);
}You're not adding features, you're adding visibility. Once the bug's fixed, comment out or remove the prints.
Move 2 — Isolate (which half is broken?)
If the sketch combines five modules and isn't working, you don't know which is the culprit. Strip it down: comment out four modules. Does the one remaining work? Yes → add the next one back, test again. No → that's the broken one.
This is binary search applied to bugs. Each test cuts the suspect-space in half. 5 modules → 3 tests typical.
Move 3 — Simplify (minimum failing case)
Reduce the sketch to the smallest version that still shows the bug. If your big Weather Station crashes mid-write, can you reproduce the crash with just "write the same line 100 times in a loop"? If yes, the bug is in the SD library's interaction with your style of writing, not anything specific to weather.
A minimum failing case is the most powerful artifact in debugging. You can post it on a forum. You can email it to a teacher. You can put it next to the working version and spot the difference.
The diagnostic mindset
Before pressing "upload", write down on paper:
- What I expect to see (Serial output, LED state, LCD content).
- What would prove the bug is in the sensor / wiring / code / library.
- What test would distinguish them.
Then upload and compare reality to expectation. If they match: hypothesis confirmed, move on. If they don't: you've learned something specific.
Common Arduino bugs & their tells
| Symptom | Likely cause | Test |
|---|---|---|
| Sketch resets randomly | RAM exhaustion | Comment out a big library; if the resets stop, it's memory. |
| Garbled LCD text | Loose data wire OR RAM | Wiggle the wires; if it changes, wiring. If it's stable garbage, RAM. |
| Sensor reads always 0 or 1023 | Wire fell off / module unpowered | Multimeter on the sensor's VCC pin. |
| Button registers double-presses | No debouncing | Add debounce (L02-37). |
| Sketch "hangs" sometimes | Blocking call (delay, pulseIn timeout) | Add prints around every blocking call. |
| Float arithmetic gives weird result | Integer division before promotion | Check all literals have .0 on them. |
| EEPROM value reads as 255 | Never wrote, or wrote then magic-number check failed | Print the actual byte read; verify it's being written. |
Worked Example · The broken sketch 25 min
Here's a sketch that's deliberately broken in three ways. Read it before running. Try to spot the bugs.
// L02-47 broken-sketch challenge.
// Supposed to: read a thermistor, light an LED when > 30°C,
// log the temperature once per second to Serial.
const int THERM = A0;
const int LED = 9;
const int R_FIXED = 10000;
const int R0 = 10000;
const int T0_K = 298;
const int BETA = 3950;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int raw = analogRead(THERM);
int R = R_FIXED * (1023 / raw - 1);
int tK = 1 / (1/T0_K + (1/BETA) * log(R / R0));
int tC = tK - 273;
if (tC > 30) digitalWrite(LED, HIGH);
Serial.print("T: ");
Serial.println(tC);
delay(1);
}Step 1 — predict before reading
Read the sketch. Predict what would be printed if it ran. Don't look at the answer yet.
Now run it (mentally or on real hardware). The Serial output will show T: 0 over and over. That's the symptom. Let's walk through finding why with the three moves.
Step 2 — Move 1: Print
We see T: 0. We don't know if raw is wrong, or the maths, or what. Add prints to each step:
int raw = analogRead(THERM);
Serial.print("raw: ");
Serial.println(raw);
int R = R_FIXED * (1023 / raw - 1);
Serial.print("R: ");
Serial.println(R);
int tK = 1 / (1/T0_K + (1/BETA) * log(R / R0));
Serial.print("tK: ");
Serial.println(tK);
int tC = tK - 273;
Serial.print("tC: ");
Serial.println(tC);Run. Now you see:
raw: 512 R: 10000 <- looks right tK: 0 <- WRONG. Should be ~298. tC: -273
The bug is in the tK calculation. Specifically, somewhere inside that line of math.
Step 3 — Move 3: Simplify (analyse the broken line)
Compute the expression by hand:
1/T0_K= 1/298 (integer division) = 0.1/BETA= 1/3950 (integer division) = 0.- So the whole denominator is 0 + 0 × log(...) = 0.
1 / 0in integer arithmetic on Arduino is... well, undefined, but the compiler returns 0 here.- Hence tK = 0, tC = -273. The symptom matches.
Bug #1 found: integer division. Fix: make the constants floats (const float T0_K = 298.15), and use 1.0 / T0_K not 1 / T0_K.
Step 4 — Move 2: Isolate (test that fix in isolation)
Comment out the LED line (if (tC > 30) ...). Comment out the delay. Just read and print the thermistor with the corrected maths. Verify the temperature now looks right (~25 °C at room temperature).
Step 5 — Find bug #2
The temperature now reads correctly. Re-add the LED line and the delay. Notice the delay(1) — that's a 1 ms delay, which means the loop spams Serial 1000 times per second. The Serial Monitor can't keep up; you'll see streaming numbers, and you barely have time to verify the LED logic.
Bug #2 found: delay(1) should be delay(1000) (the comment says "once per second"). Fix: change to 1000.
Step 6 — Find bug #3
Heat up the thermistor with your fingers. The LED turns ON when temperature crosses 30 °C. But it never turns OFF. Re-cool the thermistor — LED stays on.
Look at the sketch: if (tC > 30) digitalWrite(LED, HIGH); — there's no else to turn it off!
Bug #3 found: missing else branch. Fix:
if (tC > 30) digitalWrite(LED, HIGH);
else digitalWrite(LED, LOW);Or even simpler: digitalWrite(LED, tC > 30 ? HIGH : LOW);.
Step 7 — final sketch
Re-run with all three fixes. Print prints once per second. Temperature is sensible. LED turns on and off appropriately. Bug-fest over.
The point of this exercise: not the specific bugs (you'll see different ones), but the systematic moves. Print to gain visibility. Isolate to narrow the search. Simplify to confirm the fix.
Basic 5 min
Goal: Take any one of your earlier L2 projects. Without testing, predict what 3 prints you'd add to track its behaviour through one full cycle (sensor read → decision → action). Then add them.
Challenge 1 5 min
Goal: This press counter should count presses on D2 and light the LED on D9 on every 5th press. It should also remember the count after power-off. The button is wired from D2 to GND. There are four bugs: one stops it compiling, one is wiring-style, one is timing, and one only shows on a brand-new board.
#include <EEPROM.h>
const int BUTTON = 2;
const int LED = 9;
const int COUNT_ADDR = 0;
int count = 0;
int lastState = HIGH;
void setup() {
pinMode(BUTTON, INPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
EEPROM.get(COUNT_ADDR, count);
}
void loop() {
int state = digitalRead(BUTTON);
if (state == LOW && lastState == HIGH) {
count++;
EEPROM.put(COUNT_ADDR, count);
Serial.print("Presses: ");
Serial.println(count);
}
lastState = state;
if (count % 5 = 0) {
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
}Use the three moves. For each bug, write the line, what is wrong and the fix.
It works if one press always adds exactly one, the LED lights at 5, 10 and 15, and the count survives unplugging.
Challenge 2 5 min
Goal: Reduce one of your big sketches (Weather Station v2 or Combo Lock) to the smallest possible "reproduces the same critical bug" version. Aim for under 30 lines. Even if there isn't a current bug, practice the reduction process.
Challenge 3 · Print your way to the fault 15 min
A classmate's night-light should switch on the LED on D9 when the room is dark. The LED never turns on, however dark it gets. They wired the LDR from +5 V straight to A0, and nothing else. Here is their sketch.
const int LDR = A0;
const int LED = 9;
const int DARK_RAW = 200;
const int BRIGHT_RAW = 800;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int raw = analogRead(LDR);
int pct = (raw - DARK_RAW) / (BRIGHT_RAW - DARK_RAW) * 100;
if (pct < 30) {
analogWrite(LED, 255);
}
if (pct > 30) {
analogWrite(LED, 0);
}
delay(200);
}Build it exactly as described, then work through these steps:
- Print. Print
rawandpct. Cover the LDR. What doesrawread, and why? Fix the wiring. - Simplify. Now the LED is always on. Work out
pctby hand forraw= 500, step by step, as in the worked example. Fix the maths. - Isolate. Find the one value of
pctthat neitherifhandles. Fix it.
It works if pct moves smoothly from 0 to 100 as you cover and uncover the LDR. The LED must be on below 30 and off at 30 or above.
Recap 5 min
The three debugging moves — Print, Isolate, Simplify — apply to every project at every level of difficulty. The single biggest skill upgrade isn't any one move; it's the discipline of forming a hypothesis ("what do I expect to see?") before running, then comparing to reality. Combined with the "one change at a time" rule, this turns mysterious failures into systematic investigations. A debugging notebook accelerates the learning curve — patterns become visible after 20-ish entries. Tomorrow we recap the entire Level 2 and prep for the assessment.
- Symptom vs cause
- The symptom is what you see ("LED won't light"); the cause is the underlying reason ("pin 9 wired to ground"). Fixing the symptom without finding the cause means it'll happen again.
- Print debugging
- Adding temporary
Serial.prints throughout the code to make hidden state visible. The simplest, most universal technique. - Isolation
- Disconnecting parts of a complex system to find which one is broken. Comment out modules; bisect.
- Simplification (minimum failing case)
- Reducing the sketch to the smallest version that still shows the bug. Powerful for understanding, communication, and forum posts.
- Hypothesis-driven debugging
- Before running anything, predicting what you expect to see. Comparing reality to expectation tells you exactly what your mental model gets wrong.
- Shotgun debugging
- Anti-pattern: changing many things at once hoping one of them fixes it. Even if it works, you don't learn anything.
- Bug class
- A category of related bugs (integer division, missing else, blocking call). Recognising the class lets you fix bugs you've never seen by name.
- Debugging notebook
- A log of bugs encountered + their fixes. Accelerates pattern recognition; provides a personal "ways code goes wrong" reference over time.
- Postmortem
- A formal written analysis of a serious failure. Industry practice in software, aviation, medicine. The debugging notebook is your tiny version.
Extra Mission 5 min
Part 1 — Design a gadget that checks itself
Pick an everyday device that would be dangerous or annoying if it failed silently. Ideas: a smoke alarm, a fridge alarm, a bike light, a plant waterer. On paper, design a power-on self-test for it.
Your design must include:
- Each part it checks, and how. For example: "sensor reads 0 or 1023 → wire off".
- How it reports each fault — a blink code, a beep pattern or an LCD message.
- What it does when every check passes.
- How long the whole self-test takes.
Part 2 — Make it
Add a self-test to one of your Level 2 builds. Run it in setup(), before the main sketch starts. Then pull out one wire at a time, and check the right fault is reported each time.
Bring back next class: your design, your hw-l02-47.ino sketch, and a photo or Serial Monitor screenshot of one fault being caught.