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:
int i = 0: start with i at 0i < 5: keep going while i is less than 5i++: add 1 to i after each iteration- The loop body runs 5 times: i = 0, 1, 2, 3, 4
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
whileloop 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, prefermillis()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:
- States: the distinct modes your project can be in (e.g., OFF, DIM, BRIGHT, AUTO)
- Transitions: what causes a change from one state to another (e.g., button press)
- 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
- Each state is independent: you can understand DIM without reading all the other code
- Adding a new state is easy: add a case, done
- Transitions are explicit: you can see exactly what causes a mode change
- 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()aftermap()when the output goes to a function with strict limits (likeanalogWrite0–255, orServo.write0–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):
- LED: anode (long leg) row 20 col E, cathode row 21 col E
- 220Ω resistor: row 20 col A to row 17 col A
- Jumper from row 17 col B → Arduino pin 9
- 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:
- Write
if / else if / elsechains to handle multiple conditions - Use comparison (
==,<,>) and logical (&&,||,!) operators - Write
forandwhileloops - Explain why buttons bounce and implement software debouncing
- Explain the difference between
delay()andmillis()timing - Write non-blocking code using the
millis()pattern - Implement a basic state machine with
enumandswitch - Use
map()andconstrain()together safely - Build and test the multi-mode night light project
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 →