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:

  1. Disconnect power
  2. Set multimeter to continuity (🔊)
  3. Touch one probe to the Arduino pin, the other to the component pin
  4. If it beeps, the connection is good
  5. If silent, trace the path and find the break

Common finds:

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:

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:

  1. Comment out the bottom half of loop(). Does the first half work?
  2. If yes, the bug is in the bottom half. Uncomment it and comment out the top half of that section.
  3. If no, the bug is in the top half. Keep narrowing.
  4. 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

expected ';' before 'Y'

invalid conversion from 'const char*' to 'int'

'Servo' does not name a type

avrdude: stk500_recv(): programmer is not responding

Sketch too big

Warnings vs. Errors

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:

  1. Verify pin numbers match between code and actual wiring
  2. Confirm component polarity (LEDs, electrolytic caps, diodes)
  3. Check power at each component's VCC pin
  4. Test continuity along every wire path
  5. Try the component in a known-good circuit
  6. 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:

  1. Add Serial prints to trace execution flow
  2. Check that libraries are initialized correctly (begin, init, attach)
  3. Verify sensor/module address (I2C scanner)
  4. Confirm pin mode is set correctly (INPUT vs OUTPUT, INPUT_PULLUP)
  5. Check for timer conflicts (Servo library conflicts with PWM on pins 9/10)
  6. 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:


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
  1. pinMode(LED_PIN, INPUT) should be OUTPUT: an INPUT pin can't drive an LED.
  2. digitalwrite should be digitalWrite: C++ is case-sensitive. This won't compile.
  3. delay(100) should be delay(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
  1. pinMode(SENSOR_PIN, OUTPUT) is wrong: analog input pins should not be set to OUTPUT. Remove this line entirely (analog pins default to input, and analogRead() handles the mode).
  2. "%%" prints a literal %%: it should be "%". In Serial.print, % is just a character, but the double %% prints two percent signs instead of one.
  3. Sensor reads 0: Setting A0 to OUTPUT in bug #1 forces the pin low, overriding the analog read. Fixing bug #1 fixes this.
  4. 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 switch statement for executing states has a classic C mistake. Look at the DIM case carefully.
Solutions
  1. Missing pinMode(LED_PIN, OUTPUT): without this, analogWrite still works on some pins but behavior can be unreliable.
  2. buttonState == HIGH should be buttonState == LOW: with INPUT_PULLUP, a pressed button reads LOW, not HIGH. The state machine never receives button presses.
  3. STATE_BRIGHT transitions to STATE_DIM instead of STATE_OFF: the cycle is OFF→DIM→BRIGHT→DIM→BRIGHT forever. Change the BRIGHT case to currentState = STATE_OFF;.
  4. 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. Add break; after analogWrite(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:

While coding:

While expanding a project:


Self-Check: Module 12

Before moving to Module 13, make sure you can:


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 →