Module 4: Making Decisions

Level: 🟢 Beginner
Board: Arduino Uno
Prerequisites: Module 3
Estimated time: 60–80 minutes
Goal: Use conditional logic and loops to make your projects responsive and smart.


What You'll Learn

Up to now, your Arduino does the same thing forever. In this module, it learns to choose. You'll use conditionals and loops to build responsive behavior, debounce buttons properly, manage modes with state machines, and replace delay() with millis(), a shift that changes everything about what your Arduino can do.


4.1 Conditional Statements: if, else if, else

Basic if Statement

if (condition) {
  // This code runs ONLY when condition is true
}

A condition evaluates to either true or false. In Arduino, any non-zero value is true and zero is false.

if / else

int temperature = analogRead(A0);

if (temperature > 500) {
  digitalWrite(LED_PIN, HIGH);   // Too hot — warning LED on
} else {
  digitalWrite(LED_PIN, LOW);    // Temperature OK — LED off
}

if / else if / else: Multiple Conditions

int lightLevel = analogRead(A0);

if (lightLevel < 200) {
  // Very dark — full brightness
  analogWrite(LED_PIN, 255);
} else if (lightLevel < 500) {
  // Dim — medium brightness
  analogWrite(LED_PIN, 128);
} else if (lightLevel < 800) {
  // Bright — low brightness
  analogWrite(LED_PIN, 40);
} else {
  // Very bright — LED off
  analogWrite(LED_PIN, 0);
}

Important: The conditions are checked in order, top to bottom. The first one that's true wins, and the rest are skipped. Order matters; put your most specific or most critical conditions first.


4.2 Comparison and Logical Operators

Comparison Operators

Operator Meaning Example
== Equal to if (x == 5)
!= Not equal to if (x != 0)
< Less than if (x < 100)
> Greater than if (x > 100)
<= Less than or equal if (x <= 100)
>= Greater than or equal if (x >= 100)

Common mistake: Using = (assignment) instead of == (comparison). if (x = 5) doesn't check if x is 5; it sets x to 5 and always evaluates as true. The compiler won't warn you about this.

Logical Operators

Operator Meaning Example
&& AND: both must be true if (temp > 30 && humidity > 80)
|| OR: at least one must be true if (buttonA == LOW || buttonB == LOW)
! NOT: inverts true/false if (!buttonPressed)

Combining Conditions

// Check if temperature is in a comfortable range
if (temperature >= 20 && temperature <= 25) {
  Serial.println("Comfortable");
}

// Check if either button is pressed
if (digitalRead(BUTTON_A) == LOW || digitalRead(BUTTON_B) == LOW) {
  Serial.println("A button was pressed");
}

4.3 Loops: for and while

for Loop

Use for when you know how many times to repeat:

// Blink an LED 5 times
for (int i = 0; i < 5; i++) {
  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
  delay(200);
}

Breaking this down:

while Loop

Use while when you want to keep going until a condition changes:

// Wait until the button is pressed
while (digitalRead(BUTTON_PIN) == HIGH) {
  // Do nothing — just wait
  // (With INPUT_PULLUP, HIGH means not pressed)
}
Serial.println("Button pressed!");

Warning: A while loop that never becomes false creates an infinite loop, and your sketch freezes there forever. Always make sure the condition can change.

Practical Loop Example: LED Chase

/*
 * LED Chase — lights run back and forth across 5 LEDs
 */

const int LED_PINS[] = {3, 4, 5, 6, 7};
const int NUM_LEDS = 5;
const int CHASE_DELAY = 100;

void setup() {
  for (int i = 0; i < NUM_LEDS; i++) {
    pinMode(LED_PINS[i], OUTPUT);
  }
}

void loop() {
  // Forward
  for (int i = 0; i < NUM_LEDS; i++) {
    digitalWrite(LED_PINS[i], HIGH);
    delay(CHASE_DELAY);
    digitalWrite(LED_PINS[i], LOW);
  }

  // Backward
  for (int i = NUM_LEDS - 2; i > 0; i--) {
    digitalWrite(LED_PINS[i], HIGH);
    delay(CHASE_DELAY);
    digitalWrite(LED_PINS[i], LOW);
  }
}

4.4 Debouncing Buttons: Why and How

The Problem

When you press a mechanical button, the metal contacts don't make clean contact. They physically bounce, making and breaking contact dozens of times in a few milliseconds. To your Arduino, reading at millions of operations per second, one button press looks like this:

Time:     0ms   1ms   2ms   3ms   4ms   5ms  ...  50ms
Contact:  ON    OFF   ON    OFF   ON    ON   ...   ON

Your code reads this as multiple presses, not one.

Software Debouncing

The simplest fix: after detecting a state change, wait a short time before checking again.

/*
 * Software debouncing with millis()
 */

const int BUTTON_PIN = 7;
const int LED_PIN = 8;
const unsigned long DEBOUNCE_DELAY = 50;  // milliseconds

int ledState = LOW;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int reading = digitalRead(BUTTON_PIN);

  // If the button state changed (noise or actual press)
  if (reading != lastButtonState) {
    lastDebounceTime = millis();  // Reset the debounce timer
  }

  // If enough time has passed since the last state change
  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
    // The reading is stable — it's a real press
    static int buttonState = HIGH;
    if (reading != buttonState) {
      buttonState = reading;

      // Only toggle LED on the press (not the release)
      if (buttonState == LOW) {
        ledState = !ledState;  // Toggle: HIGH becomes LOW, LOW becomes HIGH
        digitalWrite(LED_PIN, ledState);
      }
    }
  }

  lastButtonState = reading;
}

Hardware Debouncing

You can also debounce with a capacitor across the button. A 0.1µF ceramic capacitor between the pin and GND smooths out the bouncing by charging/discharging slowly enough to absorb the noise. Hardware debouncing uses fewer code resources but adds a component.

When to Use Which

Approach Pros Cons
Software debounce No extra components Uses code and timing logic
Hardware debounce (capacitor) Simpler code Extra component per button
Both Most reliable More components + more code

For most Arduino projects, software debouncing with millis() is the standard approach.


4.5 millis() vs. delay(): Non-Blocking Code

The Problem with delay()

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(1000);                    // ← Arduino does NOTHING for 1 second
  digitalWrite(LED_PIN, LOW);
  delay(1000);                    // ← Arduino does NOTHING for 1 second
}

During those delay() calls, the Arduino can't read buttons, can't update sensors, can't do anything. For a simple blink this is fine. For anything interactive, it's a disaster.

The Solution: millis()

millis() returns the number of milliseconds since the Arduino started running. It never pauses; it just tells you the time. You use it to check whether enough time has passed, without stopping everything.

Blink Without Delay: The Pattern

const int LED_PIN = 8;
const unsigned long BLINK_INTERVAL = 1000;

int ledState = LOW;
unsigned long previousMillis = 0;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();

  // Has enough time passed?
  if (currentMillis - previousMillis >= BLINK_INTERVAL) {
    previousMillis = currentMillis;  // Remember when we last toggled

    // Toggle the LED
    ledState = (ledState == LOW) ? HIGH : LOW;
    digitalWrite(LED_PIN, ledState);
  }

  // Other code can run here without being blocked!
  // Read sensors, check buttons, update displays...
}

Why This Matters

With millis(), you can do multiple things "at the same time":

unsigned long lastBlinkTime = 0;
unsigned long lastSensorTime = 0;

void loop() {
  unsigned long now = millis();

  // Blink LED every 500ms
  if (now - lastBlinkTime >= 500) {
    lastBlinkTime = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }

  // Read sensor every 100ms
  if (now - lastSensorTime >= 100) {
    lastSensorTime = now;
    int reading = analogRead(A0);
    Serial.println(reading);
  }

  // Check button every loop iteration (no delay)
  if (digitalRead(BUTTON_PIN) == LOW) {
    // Respond instantly to button press
  }
}

Key insight: delay() says "stop everything for X milliseconds." millis() says "has X milliseconds passed since last time?" One blocks, the other checks. From this module forward, prefer millis() for all timing.


4.6 State Machines: Managing Modes Cleanly

The Problem with Complex if/else

As projects grow, you end up with deeply nested if/else chains that become impossible to follow. State machines are a cleaner pattern.

What Is a State Machine?

A state machine has:

  1. States: the distinct modes your project can be in (e.g., OFF, DIM, BRIGHT, AUTO)
  2. Transitions: what causes a change from one state to another (e.g., button press)
  3. Actions: what happens in each state (e.g., set LED to a certain brightness)

Implementing a State Machine

// Define states as an enum — readable names instead of magic numbers
enum LightState {
  STATE_OFF,
  STATE_DIM,
  STATE_BRIGHT,
  STATE_AUTO
};

LightState currentState = STATE_OFF;

The state variable replaces a tangle of boolean flags. Each time through loop(), you check the current state and act accordingly:

void loop() {
  // Handle state transitions (button press)
  handleButton();

  // Execute current state behavior
  switch (currentState) {
    case STATE_OFF:
      analogWrite(LED_PIN, 0);
      break;

    case STATE_DIM:
      analogWrite(LED_PIN, 50);
      break;

    case STATE_BRIGHT:
      analogWrite(LED_PIN, 255);
      break;

    case STATE_AUTO:
      int light = analogRead(SENSOR_PIN);
      int brightness = map(light, 0, 1023, 255, 0);
      analogWrite(LED_PIN, constrain(brightness, 0, 255));
      break;
  }
}

Why State Machines Are Better

  1. Each state is independent: you can understand DIM without reading all the other code
  2. Adding a new state is easy: add a case, done
  3. Transitions are explicit: you can see exactly what causes a mode change
  4. Debugging is clearer: print the current state to Serial and you immediately know what mode you're in

4.7 map() and constrain(): Value Transformation

You met map() in Module 3. Let's solidify it along with its essential companion, constrain().

map(value, fromLow, fromHigh, toLow, toHigh)

Scales a number from one range to another:

// Sensor reads 0–1023, we want 0–255 for PWM
int brightness = map(sensorValue, 0, 1023, 0, 255);

// Potentiometer to servo angle (0–180)
int angle = map(potValue, 0, 1023, 0, 180);

// Invert a range (high input → low output)
int inverted = map(sensorValue, 0, 1023, 255, 0);

constrain(value, min, max)

Clamps a value to stay within bounds:

int brightness = constrain(brightness, 0, 255);  // Never below 0 or above 255
int servoAngle = constrain(angle, 10, 170);       // Keep servo within safe range

Why You Need Both Together

map() doesn't clip its output. If the input goes outside the expected range, the output does too:

// If sensorValue somehow reads 1100 (noise spike):
int brightness = map(1100, 0, 1023, 0, 255);
// brightness = 274 → INVALID for analogWrite!

// Fix with constrain:
brightness = constrain(brightness, 0, 255);
// brightness = 255 → safe

Rule: Always constrain() after map() when the output goes to a function with strict limits (like analogWrite 0–255, or Servo.write 0–180).


Module Project: Multi-Mode Night Light

Objective

Build a night light that cycles through four modes when you press a button: OFF → DIM → BRIGHT → AUTO (sensor-based) → OFF. It uses a state machine, debounced button, and millis() for non-blocking operation.

Components Needed

Component Quantity Notes
Arduino Uno 1
USB-B cable 1
Breadboard 1
LED (any color) 1
220Ω Resistor 1 For the LED
Pushbutton 1 Tactile switch
Photoresistor (LDR) 1 For AUTO mode
10kΩ Resistor 1 For the voltage divider
Jumper wires 7+

The Code

/*
 * Module 4 Project: Multi-Mode Night Light
 *
 * Button cycles through: OFF → DIM → BRIGHT → AUTO → OFF
 * AUTO mode adjusts LED brightness based on ambient light.
 * Uses state machine, debounced button, and millis() timing.
 *
 * Circuit:
 * - LED + 220Ω → pin 9 (PWM)
 * - Button → pin 7 (INPUT_PULLUP, other side to GND)
 * - LDR + 10kΩ voltage divider → A0
 *
 * Board: Arduino Uno
 */

// --- Pin definitions ---
const int LED_PIN = 9;       // PWM pin for LED
const int BUTTON_PIN = 7;    // Button with internal pull-up
const int SENSOR_PIN = A0;   // Photoresistor voltage divider

// --- State machine ---
enum LightState {
  STATE_OFF,
  STATE_DIM,
  STATE_BRIGHT,
  STATE_AUTO
};

const char* STATE_NAMES[] = {"OFF", "DIM", "BRIGHT", "AUTO"};
LightState currentState = STATE_OFF;

// --- Debounce variables ---
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;

// --- Timing for Serial output ---
unsigned long lastPrintTime = 0;
const unsigned long PRINT_INTERVAL = 500;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  Serial.println("Night Light Ready");
  Serial.println("Press button to cycle: OFF → DIM → BRIGHT → AUTO → OFF");
  Serial.println("---");
}

void loop() {
  unsigned long now = millis();

  // --- Handle button with debouncing ---
  int reading = digitalRead(BUTTON_PIN);

  if (reading != lastButtonState) {
    lastDebounceTime = now;
  }

  if ((now - lastDebounceTime) > DEBOUNCE_DELAY) {
    static int buttonState = HIGH;
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {
        // Button pressed — advance to next state
        switch (currentState) {
          case STATE_OFF:    currentState = STATE_DIM;    break;
          case STATE_DIM:    currentState = STATE_BRIGHT; break;
          case STATE_BRIGHT: currentState = STATE_AUTO;   break;
          case STATE_AUTO:   currentState = STATE_OFF;    break;
        }

        Serial.print("Mode: ");
        Serial.println(STATE_NAMES[currentState]);
      }
    }
  }

  lastButtonState = reading;

  // --- Execute current state ---
  switch (currentState) {
    case STATE_OFF:
      analogWrite(LED_PIN, 0);
      break;

    case STATE_DIM:
      analogWrite(LED_PIN, 50);
      break;

    case STATE_BRIGHT:
      analogWrite(LED_PIN, 255);
      break;

    case STATE_AUTO: {
      int lightLevel = analogRead(SENSOR_PIN);
      int brightness = map(lightLevel, 0, 1023, 255, 0);
      brightness = constrain(brightness, 0, 255);
      analogWrite(LED_PIN, brightness);

      // Print sensor data periodically (non-blocking)
      if (now - lastPrintTime >= PRINT_INTERVAL) {
        lastPrintTime = now;
        Serial.print("AUTO — Light: ");
        Serial.print(lightLevel);
        Serial.print("  LED: ");
        Serial.println(brightness);
      }
      break;
    }
  }
}

Wiring Steps

LED circuit (same as Module 3):

  1. LED: anode (long leg) row 20 col E, cathode row 21 col E
  2. 220Ω resistor: row 20 col A to row 17 col A
  3. Jumper from row 17 col B → Arduino pin 9
  4. Jumper from row 21 col A → negative rail

Button circuit: 5. Button across the center gap: one pair of legs in row 25, other pair in row 27 6. Jumper from row 25 col A → Arduino pin 7 7. Jumper from row 27 col A → negative rail

Sensor circuit (same as Module 3): 8. LDR: row 5 col E to row 8 col E 9. 10kΩ resistor: row 8 col A to row 12 col A 10. Jumper from positive rail → row 5 col A 11. Jumper from row 12 col B → negative rail 12. Jumper from row 8 col B → Arduino A0

Power rails: 13. Arduino 5V → positive rail 14. Arduino GND → negative rail

Circuit Diagram

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850 450" font-family="monospace" font-size="12">
  <!-- Arduino -->
  <rect x="30" y="100" width="150" height="280" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
  <text x="105" y="130" text-anchor="middle" fill="white" font-size="15" font-weight="bold">Arduino</text>
  <text x="105" y="148" text-anchor="middle" fill="white" font-size="13">Uno</text>

  <!-- Pin labels -->
  <rect x="180" y="160" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="192" y="172" text-anchor="middle" fill="white" font-size="9">5V</text>
  <rect x="180" y="185" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="192" y="197" text-anchor="middle" fill="white" font-size="9">GND</text>
  <rect x="180" y="230" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="192" y="242" text-anchor="middle" fill="white" font-size="9">A0</text>
  <rect x="180" y="280" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="192" y="292" text-anchor="middle" fill="white" font-size="9">D9</text>
  <rect x="180" y="330" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="192" y="342" text-anchor="middle" fill="white" font-size="9">D7</text>

  <!-- === SENSOR CIRCUIT (top right) === -->
  <text x="500" y="95" text-anchor="middle" font-size="12" font-weight="bold" fill="#333">Sensor (AUTO mode)</text>

  <line x1="205" y1="168" x2="350" y2="168" stroke="red" stroke-width="2"/>
  <line x1="350" y1="168" x2="350" y2="120" stroke="red" stroke-width="2"/>

  <rect x="330" y="104" width="40" height="16" fill="none" stroke="#996600" stroke-width="2" rx="3"/>
  <text x="350" y="115" text-anchor="middle" font-size="9" fill="#996600">LDR</text>

  <line x1="370" y1="112" x2="450" y2="112" stroke="#333" stroke-width="2"/>
  <circle cx="450" cy="112" r="3" fill="#333"/>

  <line x1="450" y1="112" x2="450" y2="238" stroke="green" stroke-width="2"/>
  <line x1="450" y1="238" x2="205" y2="238" stroke="green" stroke-width="2"/>

  <line x1="450" y1="112" x2="520" y2="112" stroke="#333" stroke-width="2"/>
  <rect x="520" y="104" width="50" height="16" fill="none" stroke="#333" stroke-width="2" rx="3"/>
  <text x="545" y="115" text-anchor="middle" font-size="9">10kΩ</text>
  <line x1="570" y1="112" x2="620" y2="112" stroke="blue" stroke-width="2"/>
  <line x1="620" y1="112" x2="620" y2="193" stroke="blue" stroke-width="2"/>

  <line x1="205" y1="193" x2="620" y2="193" stroke="blue" stroke-width="2"/>

  <!-- === LED CIRCUIT (middle) === -->
  <text x="500" y="265" text-anchor="middle" font-size="12" font-weight="bold" fill="#333">LED (PWM dimming)</text>

  <line x1="205" y1="288" x2="350" y2="288" stroke="orange" stroke-width="2"/>
  <rect x="350" y="280" width="50" height="16" fill="none" stroke="#333" stroke-width="2" rx="3"/>
  <text x="375" y="291" text-anchor="middle" font-size="9">220Ω</text>
  <line x1="400" y1="288" x2="460" y2="288" stroke="orange" stroke-width="2"/>

  <polygon points="460,273 460,303 490,288" fill="none" stroke="red" stroke-width="2"/>
  <line x1="490" y1="273" x2="490" y2="303" stroke="red" stroke-width="2"/>
  <text x="475" y="268" text-anchor="middle" font-size="10" fill="red">LED</text>

  <line x1="490" y1="288" x2="540" y2="288" stroke="black" stroke-width="2"/>
  <line x1="540" y1="288" x2="540" y2="193" stroke="blue" stroke-width="1.5"/>

  <!-- === BUTTON CIRCUIT (bottom) === -->
  <text x="500" y="355" text-anchor="middle" font-size="12" font-weight="bold" fill="#333">Button (mode cycle)</text>

  <line x1="205" y1="338" x2="350" y2="338" stroke="purple" stroke-width="2"/>

  <!-- Button symbol -->
  <rect x="350" y="328" width="40" height="20" fill="none" stroke="#333" stroke-width="2" rx="3"/>
  <text x="370" y="342" text-anchor="middle" font-size="9">BTN</text>

  <line x1="390" y1="338" x2="450" y2="338" stroke="black" stroke-width="2"/>
  <line x1="450" y1="338" x2="450" y2="193" stroke="blue" stroke-width="1.5"/>

  <!-- State diagram -->
  <rect x="660" y="120" width="160" height="140" fill="#f0f0f0" stroke="#999" stroke-width="1" rx="6"/>
  <text x="740" y="140" text-anchor="middle" font-size="11" font-weight="bold">State Machine</text>
  <text x="740" y="160" text-anchor="middle" font-size="10">OFF → DIM</text>
  <text x="740" y="178" text-anchor="middle" font-size="10">DIM → BRIGHT</text>
  <text x="740" y="196" text-anchor="middle" font-size="10">BRIGHT → AUTO</text>
  <text x="740" y="214" text-anchor="middle" font-size="10">AUTO → OFF</text>
  <text x="740" y="245" text-anchor="middle" font-size="9" fill="#666">(button press cycles)</text>
</svg>

Circuit Schema (JSON)

{
  "module": 4,
  "project": "Multi-Mode Night Light",
  "board": "Arduino Uno",
  "schematic": {
    "components": [
      {
        "id": "U1",
        "type": "arduino_uno",
        "pins_used": {
          "5V": "net_vcc",
          "GND": "net_gnd",
          "A0": "net_sensor_junction",
          "D9": "net_led_drive",
          "D7": "net_button"
        }
      },
      {
        "id": "LDR1",
        "type": "photoresistor",
        "resistance_range": { "bright": "1k", "dark": "100k", "unit": "ohm" },
        "pins": { "pin1": "net_vcc", "pin2": "net_sensor_junction" }
      },
      {
        "id": "R1",
        "type": "resistor",
        "value": "10000",
        "unit": "ohm",
        "color_code": ["brown", "black", "orange", "gold"],
        "purpose": "Voltage divider",
        "pins": { "pin1": "net_sensor_junction", "pin2": "net_gnd" }
      },
      {
        "id": "R2",
        "type": "resistor",
        "value": "220",
        "unit": "ohm",
        "color_code": ["red", "red", "brown", "gold"],
        "purpose": "LED current limiter",
        "pins": { "pin1": "net_led_drive", "pin2": "net_r2_led" }
      },
      {
        "id": "LED1",
        "type": "led",
        "color": "white",
        "forward_voltage": 3.0,
        "forward_current_ma": 20,
        "pins": { "anode": "net_r2_led", "cathode": "net_gnd" }
      },
      {
        "id": "SW1",
        "type": "pushbutton",
        "pins": {
          "pinA": "net_button",
          "pinB": "net_gnd"
        },
        "note": "Uses internal pull-up resistor (INPUT_PULLUP)"
      }
    ],
    "nets": [
      {
        "name": "net_vcc",
        "description": "5V supply",
        "nodes": ["U1.5V", "LDR1.pin1"]
      },
      {
        "name": "net_gnd",
        "description": "Ground",
        "nodes": ["U1.GND", "R1.pin2", "LED1.cathode", "SW1.pinB"]
      },
      {
        "name": "net_sensor_junction",
        "description": "LDR-10kΩ voltage divider midpoint",
        "nodes": ["LDR1.pin2", "R1.pin1", "U1.A0"]
      },
      {
        "name": "net_led_drive",
        "description": "PWM output to LED",
        "nodes": ["U1.D9", "R2.pin1"]
      },
      {
        "name": "net_r2_led",
        "description": "Resistor to LED anode",
        "nodes": ["R2.pin2", "LED1.anode"]
      },
      {
        "name": "net_button",
        "description": "Button input (pulled HIGH internally)",
        "nodes": ["U1.D7", "SW1.pinA"]
      }
    ],
    "power": {
      "source": "USB",
      "board_voltage": 5.0,
      "total_max_current_ma": 25
    }
  },
  "breadboard": {
    "connections": [
      { "step": 1, "instruction": "LED anode (long leg) → row 20 col E, cathode → row 21 col E", "component": "LED1" },
      { "step": 2, "instruction": "220Ω resistor → row 20 col A to row 17 col A", "component": "R2" },
      { "step": 3, "instruction": "Jumper (orange) → row 17 col B to Arduino pin 9", "type": "wire", "color": "orange" },
      { "step": 4, "instruction": "Jumper (black) → row 21 col A to negative rail", "type": "wire", "color": "black" },
      { "step": 5, "instruction": "Button → legs across center gap at rows 25/27", "component": "SW1" },
      { "step": 6, "instruction": "Jumper (purple) → row 25 col A to Arduino pin 7", "type": "wire", "color": "purple" },
      { "step": 7, "instruction": "Jumper (black) → row 27 col A to negative rail", "type": "wire", "color": "black" },
      { "step": 8, "instruction": "LDR → row 5 col E to row 8 col E", "component": "LDR1" },
      { "step": 9, "instruction": "10kΩ resistor → row 8 col A to row 12 col A", "component": "R1" },
      { "step": 10, "instruction": "Jumper (red) → positive rail to row 5 col A", "type": "wire", "color": "red" },
      { "step": 11, "instruction": "Jumper (black) → row 12 col B to negative rail", "type": "wire", "color": "black" },
      { "step": 12, "instruction": "Jumper (green) → row 8 col B to Arduino A0", "type": "wire", "color": "green" },
      { "step": 13, "instruction": "Jumper (red) → Arduino 5V to positive rail", "type": "wire", "color": "red" },
      { "step": 14, "instruction": "Jumper (black) → Arduino GND to negative rail", "type": "wire", "color": "black" }
    ]
  },
  "code": { "filename": "module04_night_light.ino", "language": "cpp" },
  "validation": {
    "expected_behavior": "Button cycles through OFF → DIM → BRIGHT → AUTO. AUTO adjusts LED from sensor. Serial shows current mode.",
    "common_mistakes": [
      "Button registers multiple presses: debounce not working — check DEBOUNCE_DELAY value",
      "LED doesn't dim in DIM mode: pin 9 must be PWM (marked ~)",
      "AUTO mode LED doesn't respond: check LDR voltage divider wiring",
      "State doesn't change: button wired to wrong pin or pin number mismatch in code"
    ]
  }
}

Experiments to Try

Experiment 1: Add a Status LED for Each Mode

Wire 4 small LEDs to pins 2–5 as mode indicators. In each state, light up the corresponding LED.

Experiment 2: Long Press vs. Short Press

Detect how long the button is held: short press (<500ms) cycles forward, long press (>1 second) cycles backward.

Experiment 3: Brightness Memory

When returning to DIM or BRIGHT mode, remember the last brightness level the user set (using a potentiometer) instead of using fixed values.


Self-Check: Module 4

Before moving to Module 5, make sure you can:


Key Terms Glossary

Term Definition
Conditional Code that runs only when a condition is true (if, else)
Comparison operator Tests a relationship between values (==, !=, <, >, <=, >=)
Logical operator Combines conditions (&& AND, || OR, ! NOT)
for loop Repeats code a known number of times
while loop Repeats code while a condition is true
Debouncing Filtering out the rapid on/off noise from mechanical button contacts
millis() Returns milliseconds since startup, used for non-blocking timing
delay() Pauses execution. Blocks all other code during the pause
State machine A design pattern where code behavior depends on the current "state"
enum A named list of constants, used to define states
switch/case A control structure for executing different code based on a variable's value
map() Scales a number from one range to another
constrain() Limits a value to a minimum and maximum
Toggle Switch between two states (e.g., ledState = !ledState)

Previous: ← Module 3: Inputs & Outputs Next: Module 5: Power Management: Keeping Your Projects Alive →

Related Blog Posts