Module 12: Troubleshooting & Debugging
Level: 🟡 Intermediate
Board: Arduino Uno (revisiting previous projects)
Prerequisites: Modules 1–8
Estimated time: 60–80 minutes
Goal: Develop a systematic approach to finding and fixing problems.
What You'll Learn
Every project breaks. The difference between a frustrated beginner and a confident builder isn't that things work on the first try; it's how quickly and systematically you find what's wrong. This module gives you a structured debugging methodology, teaches you to read error messages, and challenges you with three intentionally broken projects to fix.
12.1 The Debugging Mindset
The Three Principles
1. Isolate: Narrow down where the problem is. Is it hardware or software? Which part of the circuit? Which function in the code?
2. Test: Verify one thing at a time. Change one variable, observe the result. If you change three things at once and it works, you don't know which fix was the real one.
3. Verify: Don't assume. Measure. Use the multimeter. Use Serial prints. Confirm that what you think is happening is actually happening.
The Debugging Hierarchy
Start from the most basic layer and work up. Most bugs live at the bottom:
Layer 5: Logic errors (code does wrong thing)
Layer 4: Library issues (wrong version, missing dependency)
Layer 3: Code syntax (compiler errors, typos)
Layer 2: Wiring (wrong pin, loose connection, polarity)
Layer 1: Power (no power, wrong voltage, insufficient current)
The 80/20 rule of Arduino debugging: 80% of problems are power or wiring. Only 20% are actually in the code. Always check the physical circuit first.
12.2 Hardware Debugging: Systematic Circuit Checking
Step 1: Power Check
Before anything else, verify power is reaching your components.
| Check | Tool | What You're Looking For |
|---|---|---|
| Arduino power LED on? | Eyes | Basic power confirmation |
| 5V pin voltage | Multimeter (DC V) | Should read 4.8–5.2V |
| 3.3V pin voltage | Multimeter (DC V) | Should read 3.2–3.4V |
| Component VCC pins | Multimeter | Same voltage as the rail they're connected to |
| Ground continuity | Multimeter (continuity) | All GND points should beep to each other |
Step 2: Continuity Check
Verify that every connection you think exists actually exists.
Procedure:
- Disconnect power
- Set multimeter to continuity (🔊)
- Touch one probe to the Arduino pin, the other to the component pin
- If it beeps, the connection is good
- If silent, trace the path and find the break
Common finds:
- Wire not pushed in far enough into the breadboard
- Wrong row (off by one row in the breadboard)
- Breadboard center gap not bridged when it should be
- Power rail break in the middle of the breadboard
Step 3: Component Check
Test each component individually before blaming the circuit.
LEDs: Connect to 5V through a 220Ω resistor. Does it light? Try reversing it; wrong polarity is the most common issue.
Buttons: Set multimeter to continuity. Press the button; does it beep? Release; does it stop? If it always beeps or never beeps, the button is wired wrong or broken.
Sensors: Run a minimal test sketch that just reads and prints the sensor value. No other logic, no other components. If the sensor works in isolation, the problem is in how it interacts with the rest of the circuit.
Step 4: Signal Check
Use the multimeter (or an oscilloscope if available) to verify that signals are what you expect.
| Signal | Expected |
|---|---|
| Digital output HIGH | 4.8–5.2V |
| Digital output LOW | 0–0.3V |
| PWM output (average) | 0–5V depending on duty cycle |
| Analog sensor output | Between GND and VCC, varies with stimulus |
| I2C SDA/SCL (idle) | Pulled high to VCC (with pull-ups) |
12.3 Software Debugging: Finding Bugs in Code
Serial.println(): Your Best Debugging Tool
When code doesn't work as expected, add print statements at key points:
void loop() {
Serial.println("=== Loop start ===");
int sensorValue = analogRead(A0);
Serial.print("Sensor raw: ");
Serial.println(sensorValue);
int mapped = map(sensorValue, 0, 1023, 0, 255);
Serial.print("Mapped: ");
Serial.println(mapped);
if (sensorValue > THRESHOLD) {
Serial.println("→ Above threshold — LED ON");
digitalWrite(LED_PIN, HIGH);
} else {
Serial.println("→ Below threshold — LED OFF");
digitalWrite(LED_PIN, LOW);
}
Serial.print("LED pin state: ");
Serial.println(digitalRead(LED_PIN));
Serial.println();
delay(500);
}
What this tells you:
- Is
loop()actually running? (If nothing prints, setup might be stuck) - Is the sensor giving reasonable values?
- Is the
map()calculation correct? - Which branch of the
if/elseis executing? - Does the pin state match what you expect?
Binary Search for Bugs
When you have a long sketch and something is wrong, don't read the whole thing line by line. Use binary search:
- Comment out the bottom half of
loop(). Does the first half work? - If yes, the bug is in the bottom half. Uncomment it and comment out the top half of that section.
- If no, the bug is in the top half. Keep narrowing.
- Each step eliminates half the remaining code. You'll find the bug in a few iterations.
Commenting Out Code Blocks
void loop() {
readSensors(); // Step 1: Test this alone
// processData(); // Commented out — skip
// updateDisplay(); // Commented out — skip
// checkAlerts(); // Commented out — skip
}
If readSensors() works alone, uncomment the next function. The one that breaks things is where the bug lives.
Variable Watching with Serial
For time-dependent bugs, print variables continuously and watch for the moment things go wrong:
void loop() {
int reading = analogRead(A0);
unsigned long time = millis();
// CSV format — copy to spreadsheet for analysis
Serial.print(time);
Serial.print(",");
Serial.print(reading);
Serial.print(",");
Serial.print(ledState);
Serial.print(",");
Serial.println(currentState);
// ... rest of code
}
Paste the output into a spreadsheet. Chart it. Patterns that are invisible in scrolling text become obvious in a graph.
12.4 Reading Compiler Errors
When your code won't compile, the Arduino IDE shows error messages. They look intimidating but follow predictable patterns.
Common Error Messages and What They Mean
'X' was not declared in this scope
- The variable or function name
Xdoesn't exist at the point you're using it - Causes: typo in variable name, missing
#include, function defined after it's called, variable declared inside a different scope (e.g., insidesetup()but used inloop())
expected ';' before 'Y'
- You're missing a semicolon on the line above line Y
- The error points to where the compiler got confused, not where the semicolon is missing
invalid conversion from 'const char*' to 'int'
- You're passing text where a number is expected (or vice versa)
- Common: using
"HIGH"(string) instead ofHIGH(constant)
'Servo' does not name a type
- Missing
#include <Servo.h>at the top of your sketch
avrdude: stk500_recv(): programmer is not responding
- Upload failure, not a code error
- Check: correct board selected, correct port, try pressing reset, try a different cable
Sketch too big
- Your compiled code exceeds 32 KB (or your variables exceed 2 KB RAM)
- Solutions: remove unused libraries, reduce string usage, use
F()macro for Serial.print strings, use PROGMEM for large arrays
Warnings vs. Errors
- Errors (red): Prevent compilation. Must be fixed.
- Warnings (orange/yellow): Code compiles but something might be wrong. Read them; they often reveal real bugs (type mismatches, unused variables, potential overflows).
The F() Macro: Saving RAM
Every Serial.print("some text") stores that string in RAM. On a 2 KB RAM Uno, this adds up fast.
// Uses RAM for the string
Serial.println("Temperature sensor initialized");
// Stores string in flash, saves RAM
Serial.println(F("Temperature sensor initialized"));
Use F() for all static strings in Serial.print, display.print, etc. This is one of the most effective ways to avoid running out of RAM.
12.5 Common Failure Modes: A Reference
Physical Issues
| Problem | Symptom | Fix |
|---|---|---|
| Loose breadboard wire | Intermittent failure; works when touched | Push wire in firmly, or replace breadboard |
| Cold solder joint | Works sometimes, fails when bumped | Reheat joint with iron |
| Bent component pin | Component doesn't sit flat in breadboard | Straighten gently with pliers |
| Wrong resistor value | LED too dim/bright, sensor out of range | Verify with multimeter |
| Reversed polarity | LED doesn't light, electrolytic cap may bulge | Check component orientation |
Code Issues
| Problem | Symptom | Fix |
|---|---|---|
= instead of == |
Condition always true | Use == for comparison |
Missing break in switch |
Falls through to next case | Add break; after each case |
| Integer overflow | Values wrap around at 32767 or 65535 | Use long or unsigned long for large numbers |
| Floating-point comparison | if (x == 3.14) never true |
Use range: if (abs(x - 3.14) < 0.01) |
| String overflows RAM | Crashes, resets, garbage on screen | Use F() macro, reduce strings |
| delay() blocking | Button unresponsive, missed sensor readings | Use millis() pattern |
| Pin number mismatch | Code controls wrong pin | Verify pin constants match wiring |
Library Issues
| Problem | Symptom | Fix |
|---|---|---|
| Wrong library version | Compiles but doesn't work correctly | Check library changelog, try a different version |
| Missing dependency | Compile error on include | Install required dependency libraries |
| Two libraries conflict | Both try to use the same timer/interrupt | Check library docs for conflicts, choose alternatives |
| Library not for your board | Compiles but crashes | Verify board compatibility in library docs |
Communication Issues
| Problem | Symptom | Fix |
|---|---|---|
| Baud rate mismatch | Garbage in Serial Monitor | Match Serial.begin() with monitor setting |
| I2C wrong address | Library initializes but reads zeros | Run I2C scanner to find real address |
| Missing I2C pull-ups | Scanner finds nothing | Add 4.7kΩ pull-ups to SDA and SCL |
| SPI mode mismatch | Garbage data from device | Check device datasheet for SPI mode |
| TX/RX swapped | UART device silent | Cross TX→RX and RX→TX |
12.6 When Code Works but Circuit Doesn't (and Vice Versa)
Code Works, Circuit Doesn't
You're confident the code is correct (it worked before, or it compiles and Serial output looks right), but the physical circuit doesn't behave.
Checklist:
- Verify pin numbers match between code and actual wiring
- Confirm component polarity (LEDs, electrolytic caps, diodes)
- Check power at each component's VCC pin
- Test continuity along every wire path
- Try the component in a known-good circuit
- Swap the suspect component for a new one
Circuit Looks Right, Code Doesn't Work
The wiring has been triple-checked and is correct, but the code produces wrong behavior.
Checklist:
- Add Serial prints to trace execution flow
- Check that libraries are initialized correctly (begin, init, attach)
- Verify sensor/module address (I2C scanner)
- Confirm pin mode is set correctly (INPUT vs OUTPUT, INPUT_PULLUP)
- Check for timer conflicts (Servo library conflicts with PWM on pins 9/10)
- Run the simplest possible test sketch for each component individually
The "It Worked Yesterday" Bug
The circuit worked, you didn't change anything, and now it doesn't.
Common causes:
- A wire worked loose (especially on old breadboards)
- Battery voltage dropped below the component's minimum
- Temperature change affected a sensor's calibration
- The IDE updated and changed a library version
- You're using a different USB port (different COM port)
- You accidentally moved a wire while reaching for something else
12.7 Building a Troubleshooting Log
When debugging complex issues, write down what you try. This prevents going in circles and gives you data to analyze.
Log Template
PROJECT: [name]
DATE: [date]
SYMPTOM: [what's wrong — be specific]
---
Step 1: [what you checked/changed]
Result: [what happened]
Step 2: [what you checked/changed]
Result: [what happened]
Step 3: [what you checked/changed]
Result: [what happened]
SOLUTION: [what finally fixed it]
ROOT CAUSE: [why it was broken in the first place]
PREVENTION: [how to avoid this next time]
Example Log
PROJECT: Module 6 Environment Monitor
DATE: 2025-03-15
SYMPTOM: DHT22 reads NaN, all other sensors work fine
---
Step 1: Checked DHT22 wiring with continuity tester
Result: All connections OK — VCC, DATA, GND all beep to correct pins
Step 2: Ran isolated DHT22 test sketch (only DHT, no other sensors)
Result: Still reads NaN
Step 3: Measured voltage at DHT22 VCC pin
Result: 4.2V — below expected 5V
Step 4: Measured Arduino 5V pin
Result: 4.3V — low! Checked USB cable — only a charging cable, not data+power
Step 5: Swapped USB cable for a known data cable
Result: 5V pin reads 5.0V, DHT22 reads correctly
SOLUTION: Replaced USB cable with a data-capable cable
ROOT CAUSE: Charging-only USB cable had thin power wires with high resistance, causing voltage drop
PREVENTION: Label data cables. Test with multimeter when switching cables.
Module Project: Bug Hunt Challenge
Objective
Three pre-built projects with intentional bugs (both hardware and software). Your task: find and fix each one using the systematic methods from this module. Document each fix in a troubleshooting log.
Challenge 1: The LED That Won't Blink
Code provided (contains bugs):
/*
* Bug Hunt #1: LED Blink
* This sketch should blink an LED on pin 8.
* There are 3 bugs. Find and fix them.
*/
const int LED_PIN = 8;
void setup() {
Serial.begin(9600);
// Bug #1 is here or nearby
pinMode(LED_PIN, INPUT);
}
void loop() {
Serial.println("LED ON");
// Bug #2 is here or nearby
digitalwrite(LED_PIN, HIGH);
delay(1000);
Serial.println("LED OFF");
digitalWrite(LED_PIN, LOW);
// Bug #3 is here or nearby
delay(100);
}
Hardware setup: LED + 220Ω resistor on pin 8, cathode to GND.
Hints
- Bug #1: What mode should an output pin be in?
- Bug #2: Arduino is case-sensitive. Check function names carefully.
- Bug #3: The LED blinks, but the off-time is suspiciously short. Is the timing what you'd expect for a visible blink?
Solutions
pinMode(LED_PIN, INPUT)should beOUTPUT: an INPUT pin can't drive an LED.digitalwriteshould bedigitalWrite: C++ is case-sensitive. This won't compile.delay(100)should bedelay(1000): the LED turns off for only 0.1 seconds, making the blink barely visible. The off-time should match the on-time for a proper blink.
Challenge 2: The Sensor That Reads Zero
Code provided (contains bugs):
/*
* Bug Hunt #2: Photoresistor Reader
* Reads a photoresistor on a voltage divider and displays the
* light level. Should show 0–100% with the value changing when
* you cover the sensor. There are 4 bugs. Find and fix them.
*/
const int SENSOR_PIN = A0;
const int LED_PIN = 9;
void setup() {
Serial.begin(9600);
pinMode(SENSOR_PIN, OUTPUT); // Bug is here or nearby
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int sensorValue = analogRead(SENSOR_PIN);
// Convert to percentage
int percent = map(sensorValue, 0, 1023, 0, 100);
// Set LED brightness inversely
int brightness = map(sensorValue, 0, 1023, 255, 0);
analogWrite(LED_PIN, brightness);
Serial.print("Light: ");
Serial.print(percent);
Serial.println("%%"); // Bug is here or nearby
delay(100);
}
Hardware setup: Photoresistor + 10kΩ voltage divider on A0. LED + 220Ω on pin 9.
Bonus hardware bug: The 10kΩ resistor and photoresistor are in the wrong positions in the voltage divider, so the photoresistor connects to GND instead of VCC (the learner should discover this through measurement).
Hints
- Bug #1: Should analog input pins be set to OUTPUT?
- Bug #2: Look at the Serial output carefully. What does
%%print? - Bug #3: If the sensor reads 0 always, is it a code problem or a wiring problem? Measure the voltage at A0.
- Bug #4 (hardware): If the voltage divider reads backwards (high in dark, low in bright), which component is connected to VCC?
Solutions
pinMode(SENSOR_PIN, OUTPUT)is wrong: analog input pins should not be set to OUTPUT. Remove this line entirely (analog pins default to input, andanalogRead()handles the mode)."%%"prints a literal%%: it should be"%". In Serial.print,%is just a character, but the double%%prints two percent signs instead of one.- Sensor reads 0: Setting A0 to OUTPUT in bug #1 forces the pin low, overriding the analog read. Fixing bug #1 fixes this.
- Hardware: voltage divider is reversed: photoresistor should connect to VCC (5V), with the 10kΩ resistor on the GND side. When reversed, the voltage reading is inverted and may sit near zero in normal lighting.
Challenge 3: The State Machine That's Stuck
Code provided (contains bugs):
/*
* Bug Hunt #3: Multi-Mode LED
* Button should cycle through: OFF → DIM → BRIGHT → OFF
* There are 4 bugs. Find and fix them.
*/
const int BUTTON_PIN = 7;
const int LED_PIN = 9;
enum State { STATE_OFF, STATE_DIM, STATE_BRIGHT };
State currentState = STATE_OFF;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;
void setup() {
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Bug: something missing here
Serial.println("Ready");
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
static int buttonState = HIGH;
if (reading != buttonState) {
buttonState = reading;
// Bug is in the next few lines
if (buttonState == HIGH) {
switch (currentState) {
case STATE_OFF: currentState = STATE_DIM; break;
case STATE_DIM: currentState = STATE_BRIGHT; break;
case STATE_BRIGHT: currentState = STATE_DIM; break;
}
Serial.print("State: ");
Serial.println(currentState);
}
}
}
lastButtonState = reading;
// Execute state
switch (currentState) {
case STATE_OFF:
analogWrite(LED_PIN, 0);
break;
case STATE_DIM:
analogWrite(LED_PIN, 50);
case STATE_BRIGHT:
analogWrite(LED_PIN, 255);
break;
}
}
Hardware setup: Button on pin 7 (to GND), LED + 220Ω on pin 9.
Hints
- Bug #1: What's missing in
setup()? - Bug #2: With INPUT_PULLUP, what value means "pressed"? HIGH or LOW?
- Bug #3: Look at the state transitions. Can you ever get back to OFF?
- Bug #4: The
switchstatement for executing states has a classic C mistake. Look at the DIM case carefully.
Solutions
- Missing
pinMode(LED_PIN, OUTPUT): without this, analogWrite still works on some pins but behavior can be unreliable. buttonState == HIGHshould bebuttonState == LOW: with INPUT_PULLUP, a pressed button reads LOW, not HIGH. The state machine never receives button presses.STATE_BRIGHTtransitions toSTATE_DIMinstead ofSTATE_OFF: the cycle is OFF→DIM→BRIGHT→DIM→BRIGHT forever. Change the BRIGHT case tocurrentState = STATE_OFF;.- Missing
break;after STATE_DIM case: execution falls through from DIM directly into BRIGHT, so the LED is always at full brightness when in DIM mode. Addbreak;afteranalogWrite(LED_PIN, 50);.
Troubleshooting Log Template
Use this for each challenge:
CHALLENGE #: ___
SYMPTOM: ___
Step 1: ___
Result: ___
Step 2: ___
Result: ___
[... as many steps as needed ...]
BUGS FOUND:
1. ___
2. ___
3. ___
4. ___
FIXES APPLIED:
1. ___
2. ___
3. ___
4. ___
TIME TO SOLVE: ___ minutes
WHAT I LEARNED: ___
12.8 Prevention: Habits That Reduce Debugging
The best debugging is the debugging you don't have to do. These habits prevent bugs:
While wiring:
- Wire one component at a time, test, then add the next
- Use consistent wire colors (red = power, black = ground)
- Double-check every connection before powering on
- Keep wires short and organized
While coding:
- Compile frequently, after every few lines, not after 200 lines
- Use meaningful variable and constant names
- Add comments explaining why, not just what
- Use
const int PIN_NAME = N;instead of magic numbers - Test each function independently before combining them
While expanding a project:
- Save a working version before making changes (File → Save As)
- Add features one at a time, testing after each addition
- If something breaks, undo the last change and try again more carefully
Self-Check: Module 12
Before moving to Module 13, make sure you can:
- Apply the isolate → test → verify debugging methodology
- Systematically check power, continuity, and signals with a multimeter
- Use Serial.println() strategically to trace code execution
- Apply binary search to narrow down bugs in large sketches
- Read and interpret common compiler error messages
- Use the F() macro to save RAM
- Recognize common hardware and software failure modes
- Determine whether a problem is hardware or software
- Maintain a troubleshooting log
- Find and fix all bugs in the three challenge projects
Key Terms Glossary
| Term | Definition |
|---|---|
| Isolate | Narrow down the problem by systematically eliminating possibilities |
| Binary search debugging | Commenting out half the code to find which half contains the bug |
| Cold joint | A solder joint that formed incorrectly. Looks dull and grainy |
| Solder bridge | Unintended solder connecting two adjacent pins |
| Fall-through | Missing break in a switch/case, causing execution to continue into the next case |
| Integer overflow | When a number exceeds its type's maximum, wrapping around to a negative or zero |
| F() macro | Stores string literals in flash memory instead of RAM |
| Scope | Where a variable is accessible. Variables declared inside {} aren't available outside |
| Compiler error | Prevents compilation. Must be fixed before uploading |
| Compiler warning | Code compiles but something may be wrong. Should be investigated |
| Troubleshooting log | Written record of what you tried and what happened. Prevents repeating dead ends |
| Root cause | The underlying reason for a bug, not just its symptom |
Previous: ← Module 11: Arduino Nano & Compact Builds Next: Module 13: Introduction to WiFi: ESP8266 →