Module 3: Inputs & Outputs — Talking to the Physical World
Level: 🟢 Beginner
Board: Arduino Uno
Prerequisites: Module 2
Estimated time: 2.5–3.5 hours
Goal: Learn digital and analog I/O to read sensors and control components.
What You'll Learn
So far, your Arduino can do one thing: turn pins on and off. In this module, it learns to listen. You'll read button presses (digital input), sensor values (analog input), and create smooth brightness control (PWM output). You'll also meet the Serial Monitor — your most important debugging tool from here through the entire course.
3.1 Digital vs. Analog Signals
Digital: On or Off
A digital signal has exactly two states: HIGH (5V) or LOW (0V). There's nothing in between. A light switch is digital — it's either on or off.
When the Arduino reads a digital pin, it returns one of two values:
HIGH(which equals1) — the pin is at or near 5VLOW(which equals0) — the pin is at or near 0V
Analog: A Range of Values
An analog signal varies continuously. A dimmer switch is analog — it can be at any position between fully off and fully on.
The Arduino's analog pins read a voltage between 0V and 5V and convert it to a number between 0 and 1023. This conversion is done by the ADC (Analog-to-Digital Converter) built into the ATmega328P.
| Voltage at pin | analogRead() returns |
|---|---|
| 0V | 0 |
| 1.25V | ~256 |
| 2.5V | ~512 |
| 3.75V | ~768 |
| 5V | 1023 |
The formula: reading = (voltage / 5.0) × 1023
Resolution: With 10-bit resolution (0–1023), each step represents about 4.9 mV (5V ÷ 1024). That's precise enough for most hobby sensors, but professional applications often use external ADCs with 12-bit or 16-bit resolution.
3.2 Digital Input — Reading a Button
The Problem with Floating Pins
If you connect a button between a pin and 5V, what voltage does the pin see when the button is not pressed? You might think 0V, but actually the pin is "floating" — disconnected from everything, picking up random electrical noise. It might read HIGH, LOW, or flicker between both.
This is called a floating pin, and it's one of the most common beginner mistakes.
The Solution: Pull-Down and Pull-Up Resistors
Pull-down resistor (10kΩ to GND):
- When button is NOT pressed: pin is connected to GND through the 10kΩ resistor → reads LOW
- When button IS pressed: pin is connected to 5V through the button (low resistance) → reads HIGH
- The 10kΩ resistor "pulls" the pin down to a known state
Pull-up resistor (10kΩ to 5V):
- When button is NOT pressed: pin is connected to 5V through the 10kΩ resistor → reads HIGH
- When button IS pressed: pin is connected to GND through the button → reads LOW
- Logic is inverted: pressed = LOW, not pressed = HIGH
Arduino's Built-in Pull-Up Resistors
The ATmega328P has internal ~20kΩ pull-up resistors on every digital pin. You can activate them:
pinMode(BUTTON_PIN, INPUT_PULLUP); // Enables internal pull-up
With INPUT_PULLUP:
- No external resistor needed
- Button connects between pin and GND
- Logic is inverted: button pressed = LOW, button released = HIGH
This is the most common approach because it uses fewer components.
Reading a Button — Complete Example
/*
* Module 3: Button Read
* Reads a pushbutton and turns an LED on/off.
*
* Circuit:
* - Button: one side to digital pin 7, other side to GND
* - LED: anode → 220Ω resistor → digital pin 8, cathode → GND
*
* Uses INPUT_PULLUP — no external pull-up resistor needed for button.
*
* Board: Arduino Uno
*/
const int BUTTON_PIN = 7;
const int LED_PIN = 8;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP); // Button with internal pull-up
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Read the button state
int buttonState = digitalRead(BUTTON_PIN);
// With INPUT_PULLUP, LOW means pressed
if (buttonState == LOW) {
digitalWrite(LED_PIN, HIGH); // LED on when button pressed
} else {
digitalWrite(LED_PIN, LOW); // LED off when button released
}
}
Why is LOW = pressed? With INPUT_PULLUP, the internal resistor pulls the pin to 5V (HIGH) by default. Pressing the button connects the pin to GND, which overrides the weak pull-up because the button's resistance is nearly zero — much less than 20kΩ.
3.3 Analog Input — Reading a Sensor
How analogRead() Works
The ADC inside the ATmega328P takes about 100 microseconds to convert a voltage to a number. That's about 10,000 readings per second — fast enough for most applications.
int sensorValue = analogRead(A0); // Returns 0–1023
Converting ADC Values to Real Units
The raw 0–1023 number isn't very useful by itself. You'll often need to convert it:
To voltage:
float voltage = sensorValue * (5.0 / 1023.0);
To percentage:
float percentage = sensorValue / 1023.0 * 100.0;
To a sensor-specific range (using map()):
// Map 0–1023 to temperature range 0–100°C
int temperature = map(sensorValue, 0, 1023, 0, 100);
Note about
map(): It uses integer math, so it truncates decimals. For precision, use the float conversion formula instead. Also,map()doesn't constrain the output — if the input is outside the expected range, the output will be too. Useconstrain()if you need limits.
3.4 Voltage Dividers — The Circuit Behind Most Analog Sensors
Many sensors (photoresistors, thermistors, force sensors) are variable resistors. To read them with analogRead(), you need to convert a resistance change into a voltage change. That's what a voltage divider does.
The Voltage Divider Formula
V_out = V_in × (R2 / (R1 + R2))
Where:
- V_in = supply voltage (5V)
- R1 = top resistor (connected to V_in)
- R2 = bottom resistor (connected to GND)
- V_out = voltage at the junction (goes to analog pin)
How This Works with a Photoresistor
A photoresistor (LDR) changes resistance based on light:
- Bright light: ~1kΩ
- Dim light: ~10kΩ
- Dark: ~100kΩ or more
Pair it with a fixed 10kΩ resistor in a voltage divider:
In bright light (LDR ≈ 1kΩ):
V_out = 5V × (10kΩ / (1kΩ + 10kΩ)) = 5V × 0.91 = 4.55V → analogRead ≈ 930
In darkness (LDR ≈ 100kΩ):
V_out = 5V × (10kΩ / (100kΩ + 10kΩ)) = 5V × 0.091 = 0.45V → analogRead ≈ 92
The resistance change becomes a measurable voltage change. This is the foundation for reading most analog sensors.
Choosing the Fixed Resistor
The fixed resistor should be close to the middle of the sensor's resistance range. For a photoresistor that varies from 1kΩ to 100kΩ, a 10kΩ fixed resistor gives you good sensitivity across the range. Too small and you lose sensitivity in darkness; too large and you lose sensitivity in bright light.
3.5 PWM — Faking Analog Output
The Arduino can't output true analog voltages. Its pins are either 5V or 0V. But it can fake intermediate voltages using Pulse Width Modulation (PWM).
How PWM Works
PWM rapidly switches a pin between HIGH and LOW — so fast that the result looks like a steady intermediate voltage. The key parameter is the duty cycle: what percentage of the time the pin is HIGH.
| Duty cycle | Average voltage | analogWrite() value |
|---|---|---|
| 0% | 0V | 0 |
| 25% | ~1.25V | 64 |
| 50% | ~2.5V | 128 |
| 75% | ~3.75V | 191 |
| 100% | 5V | 255 |
Using analogWrite()
analogWrite(pin, value); // value: 0–255
Only works on PWM pins: 3, 5, 6, 9, 10, 11 (marked with ~ on the board).
Despite the name analogWrite, this is NOT a true analog output — it's a digital square wave. But for LEDs (dimming) and motors (speed control), the effect is the same.
LED Dimming Example
/*
* Module 3: LED Fade
* Smoothly fades an LED up and down.
*
* Circuit:
* - LED anode → 220Ω resistor → digital pin 9 (PWM)
* - LED cathode → GND
*
* Board: Arduino Uno
*/
const int LED_PIN = 9; // Must be a PWM pin (~)
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Fade up
for (int brightness = 0; brightness <= 255; brightness += 5) {
analogWrite(LED_PIN, brightness);
delay(30);
}
// Fade down
for (int brightness = 255; brightness >= 0; brightness -= 5) {
analogWrite(LED_PIN, brightness);
delay(30);
}
}
3.6 The Serial Monitor — Your Debugging Window
The Serial Monitor is a text communication channel between the Arduino and your computer. It's the single most useful debugging tool you have.
Setting Up Serial Communication
void setup() {
Serial.begin(9600); // Start serial at 9600 baud (bits per second)
}
Printing Values
Serial.print("Sensor value: "); // Print text (stays on same line)
Serial.println(sensorValue); // Print value + new line
Opening the Serial Monitor
In the Arduino IDE: Tools → Serial Monitor (or click the magnifying glass icon).
Make sure the baud rate dropdown at the bottom matches what you set in Serial.begin(). If they don't match, you'll see garbled text.
Debugging with Serial — The Essential Workflow
When something doesn't work, your first instinct should be: add Serial prints.
void loop() {
int sensorValue = analogRead(A0);
// Print the raw reading
Serial.print("Raw: ");
Serial.print(sensorValue);
// Convert and print the voltage
float voltage = sensorValue * (5.0 / 1023.0);
Serial.print(" Voltage: ");
Serial.print(voltage, 2); // 2 decimal places
Serial.println("V");
delay(250); // Don't flood the Serial Monitor
}
This tells you:
- Is the sensor connected? (values change when you interact with it)
- Is it in the expected range? (compare to your voltage divider calculations)
- Is the reading stable? (random fluctuations might indicate a wiring issue)
3.7 Debugging Basics: Is It Hardware or Software?
When your project doesn't work, the problem is either in the circuit or in the code. Here's a decision tree:
The Hardware-or-Software Decision Tree
1. Does the Arduino have power?
- Check the power LED on the board
- If no: check USB cable, try a different cable or port
2. Does the sketch upload?
- If upload fails: check board and port settings in IDE
- If compiles but doesn't upload: check cable, press reset during upload
3. Add Serial prints to verify your code logic.
- Print the values of every variable at key points
- If the prints show expected values: the problem is probably hardware
- If the prints show unexpected values: the problem is probably software
4. Use the multimeter to verify your circuit.
- Check voltage at each node
- Verify continuity of every connection
- Confirm component polarity
5. The "one change at a time" rule.
- Change one thing, test, observe
- Never change three things at once — you won't know which one fixed it
Common Beginner Issues
| Symptom | Likely Cause |
|---|---|
| LED doesn't light | Wrong polarity, wrong pin in code, loose wire |
| LED always on | Pin set wrong in code, wired to 5V instead of data pin |
| Sensor reads 0 always | Wiring disconnected, wrong analog pin in code |
| Sensor reads 1023 always | Voltage divider wired wrong (sensor and resistor swapped) |
| Serial shows garbage | Baud rate mismatch between code and monitor |
| Button doesn't respond | Floating pin (forgot pull-up/pull-down), wrong pin number |
| Values fluctuate wildly | Loose connection, floating pin, electrical noise |
Module Project: Light-Responsive LED
Objective
Build a circuit where a photoresistor reads ambient light and automatically adjusts an LED's brightness. When it's bright, the LED dims; when it's dark, the LED brightens. Use the Serial Monitor to display real-time sensor readings.
Components Needed
| Component | Quantity | Notes |
|---|---|---|
| Arduino Uno | 1 | |
| USB-B cable | 1 | |
| Breadboard | 1 | |
| Photoresistor (LDR) | 1 | |
| 10kΩ Resistor | 1 | For the voltage divider |
| 5mm LED (any color) | 1 | |
| 220Ω Resistor | 1 | For the LED |
| Jumper wires | 5 |
Circuit Description
The circuit has two parts:
Sensor side (voltage divider):
- 5V → Photoresistor → junction → 10kΩ resistor → GND
- The junction connects to analog pin A0
Output side (LED):
- Digital pin 9 (PWM) → 220Ω resistor → LED anode → LED cathode → GND
Wiring Steps
Step 1: Build the voltage divider
- Photoresistor: one leg into row 5 column E, other leg into row 8 column E
- 10kΩ resistor: one leg into row 8 column A, other leg into row 12 column A
- Jumper wire from positive (+) power rail to row 5 column A
- Jumper wire from row 12 column B to negative (−) power rail
- Jumper wire from row 8 column B to Arduino A0
Step 2: Wire the LED 6. LED: anode (long leg) into row 20 column E, cathode into row 21 column E 7. 220Ω resistor: one leg into row 20 column A, other leg into row 17 column A 8. Jumper wire from row 17 column B to Arduino pin 9 9. Jumper wire from row 21 column A to negative (−) power rail
Step 3: Connect Arduino power to breadboard 10. Jumper wire from Arduino 5V to positive (+) power rail 11. Jumper wire from Arduino GND to negative (−) power rail
The Code
/*
* Module 3 Project: Light-Responsive LED
* Reads ambient light with a photoresistor and adjusts LED brightness inversely.
* Bright room → dim LED. Dark room → bright LED.
* Real-time values displayed on Serial Monitor.
*
* Circuit:
* - Photoresistor + 10kΩ voltage divider → A0
* - LED + 220Ω resistor → pin 9 (PWM)
*
* Board: Arduino Uno
*/
const int SENSOR_PIN = A0; // Analog input from photoresistor
const int LED_PIN = 9; // PWM output to LED
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
// Analog pins don't need pinMode — they default to input
}
void loop() {
// Read the light level (0 = dark, 1023 = bright)
int lightLevel = analogRead(SENSOR_PIN);
// Convert to voltage for debugging
float voltage = lightLevel * (5.0 / 1023.0);
// Map the sensor range to LED brightness — INVERTED
// More light (high reading) → less LED brightness
// Less light (low reading) → more LED brightness
int ledBrightness = map(lightLevel, 0, 1023, 255, 0);
// Constrain to valid PWM range (safety net)
ledBrightness = constrain(ledBrightness, 0, 255);
// Set the LED brightness
analogWrite(LED_PIN, ledBrightness);
// Print debug info to Serial Monitor
Serial.print("Light: ");
Serial.print(lightLevel);
Serial.print(" Voltage: ");
Serial.print(voltage, 2);
Serial.print("V LED: ");
Serial.print(ledBrightness);
Serial.print("/255 (");
Serial.print((ledBrightness / 255.0) * 100, 0);
Serial.println("%)");
delay(100); // Read 10 times per second
}
What to Observe
- Open the Serial Monitor at 9600 baud
- Watch the values change as you cover/uncover the photoresistor
- The LED should get brighter as you block light from the sensor
- Note the sensor's range in your environment — it might not span the full 0–1023
Calibration Tip
Your actual sensor range likely won't be 0–1023. In a typical room, you might see 200–800. You can improve the response by calibrating:
// Replace the generic map with your measured range
int ledBrightness = map(lightLevel, 200, 800, 255, 0);
ledBrightness = constrain(ledBrightness, 0, 255);
Measure your actual minimum (hand covering sensor) and maximum (flashlight on sensor) using the Serial Monitor, then plug those values in.
Circuit Diagram
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" font-family="monospace" font-size="12">
<!-- Arduino Uno -->
<rect x="50" y="80" width="160" height="280" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
<text x="130" y="110" text-anchor="middle" fill="white" font-size="15" font-weight="bold">Arduino</text>
<text x="130" y="130" text-anchor="middle" fill="white" font-size="13">Uno</text>
<!-- Pin labels -->
<rect x="210" y="150" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="222" y="162" text-anchor="middle" fill="white" font-size="9">5V</text>
<rect x="210" y="180" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="222" y="192" text-anchor="middle" fill="white" font-size="9">GND</text>
<rect x="210" y="230" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="222" y="242" text-anchor="middle" fill="white" font-size="9">A0</text>
<rect x="210" y="290" width="25" height="16" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="222" y="302" text-anchor="middle" fill="white" font-size="9">D9</text>
<!-- === SENSOR CIRCUIT (top) === -->
<text x="500" y="75" text-anchor="middle" font-size="13" font-weight="bold" fill="#333">Sensor Circuit</text>
<!-- 5V wire to photoresistor -->
<line x1="235" y1="158" x2="350" y2="158" stroke="red" stroke-width="2"/>
<line x1="350" y1="158" x2="350" y2="120" stroke="red" stroke-width="2"/>
<!-- Photoresistor (as a box with zigzag symbol) -->
<rect x="330" y="100" width="40" height="20" fill="none" stroke="#996600" stroke-width="2" rx="3"/>
<text x="350" y="114" text-anchor="middle" font-size="9" fill="#996600">LDR</text>
<text x="350" y="95" text-anchor="middle" font-size="10" fill="#996600">☀</text>
<!-- Junction wire down -->
<line x1="370" y1="110" x2="450" y2="110" stroke="#333" stroke-width="2"/>
<circle cx="450" cy="110" r="4" fill="#333"/>
<!-- Wire to A0 -->
<line x1="450" y1="110" x2="450" y2="238" stroke="green" stroke-width="2"/>
<line x1="450" y1="238" x2="235" y2="238" stroke="green" stroke-width="2"/>
<text x="450" y="175" text-anchor="start" font-size="10" fill="green"> → A0</text>
<!-- 10k resistor down to GND -->
<line x1="450" y1="110" x2="530" y2="110" stroke="#333" stroke-width="2"/>
<rect x="530" y="100" width="60" height="20" fill="none" stroke="#333" stroke-width="2" rx="3"/>
<text x="560" y="114" text-anchor="middle" font-size="10">10kΩ</text>
<line x1="590" y1="110" x2="650" y2="110" stroke="blue" stroke-width="2"/>
<line x1="650" y1="110" x2="650" y2="188" stroke="blue" stroke-width="2"/>
<!-- GND wire -->
<line x1="235" y1="188" x2="350" y2="188" stroke="blue" stroke-width="2"/>
<line x1="350" y1="188" x2="650" y2="188" stroke="blue" stroke-width="2"/>
<text x="650" y="205" text-anchor="middle" font-size="10" fill="blue">GND</text>
<!-- === LED CIRCUIT (bottom) === -->
<text x="500" y="260" text-anchor="middle" font-size="13" font-weight="bold" fill="#333">LED Circuit</text>
<!-- D9 wire to resistor -->
<line x1="235" y1="298" x2="350" y2="298" stroke="orange" stroke-width="2"/>
<!-- 220 ohm resistor -->
<rect x="350" y="288" width="60" height="20" fill="none" stroke="#333" stroke-width="2" rx="3"/>
<text x="380" y="302" text-anchor="middle" font-size="10">220Ω</text>
<!-- Wire to LED -->
<line x1="410" y1="298" x2="480" y2="298" stroke="orange" stroke-width="2"/>
<!-- LED -->
<polygon points="480,278 480,318 520,298" fill="none" stroke="red" stroke-width="2"/>
<line x1="520" y1="278" x2="520" y2="318" stroke="red" stroke-width="2"/>
<text x="500" y="270" text-anchor="middle" font-size="11" fill="red">LED</text>
<!-- Light rays -->
<line x1="510" y1="276" x2="518" y2="263" stroke="red" stroke-width="1" stroke-dasharray="2,2"/>
<line x1="520" y1="273" x2="528" y2="260" stroke="red" stroke-width="1" stroke-dasharray="2,2"/>
<!-- LED cathode to GND -->
<line x1="520" y1="298" x2="580" y2="298" stroke="black" stroke-width="2"/>
<line x1="580" y1="298" x2="580" y2="340" stroke="black" stroke-width="2"/>
<line x1="580" y1="340" x2="350" y2="340" stroke="black" stroke-width="2"/>
<line x1="350" y1="340" x2="350" y2="188" stroke="blue" stroke-width="2"/>
<!-- Annotations -->
<text x="430" y="330" text-anchor="middle" font-size="10" fill="#666">→ GND rail</text>
</svg>
Circuit Schema (JSON)
{
"module": 3,
"project": "Light-Responsive LED",
"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"
}
},
{
"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",
"power_rating": "0.25W",
"color_code": ["brown", "black", "orange", "gold"],
"purpose": "Voltage divider fixed resistor",
"pins": {
"pin1": "net_sensor_junction",
"pin2": "net_gnd"
}
},
{
"id": "R2",
"type": "resistor",
"value": "220",
"unit": "ohm",
"power_rating": "0.25W",
"color_code": ["red", "red", "brown", "gold"],
"purpose": "LED current limiter",
"pins": {
"pin1": "net_led_drive",
"pin2": "net_r2_led"
}
},
{
"id": "LED1",
"type": "led",
"color": "red",
"forward_voltage": 2.0,
"forward_current_ma": 20,
"pins": {
"anode": "net_r2_led",
"cathode": "net_gnd"
}
}
],
"nets": [
{
"name": "net_vcc",
"description": "5V supply rail",
"nodes": ["U1.5V", "LDR1.pin1"]
},
{
"name": "net_gnd",
"description": "Ground rail",
"nodes": ["U1.GND", "R1.pin2", "LED1.cathode"]
},
{
"name": "net_sensor_junction",
"description": "Voltage divider midpoint — LDR meets 10kΩ — sensed by A0",
"nodes": ["LDR1.pin2", "R1.pin1", "U1.A0"]
},
{
"name": "net_led_drive",
"description": "PWM output from pin 9 to LED resistor",
"nodes": ["U1.D9", "R2.pin1"]
},
{
"name": "net_r2_led",
"description": "LED current-limiting resistor to LED anode",
"nodes": ["R2.pin2", "LED1.anode"]
}
],
"power": {
"source": "USB",
"board_voltage": 5.0,
"sensor_current_ma": 0.5,
"led_max_current_ma": 20,
"total_max_current_ma": 20.5
}
},
"breadboard": {
"connections": [
{
"step": 1,
"instruction": "Place photoresistor: one leg into row 5 column E, other leg into row 8 column E",
"component": "LDR1"
},
{
"step": 2,
"instruction": "Place 10kΩ resistor: one leg into row 8 column A, other leg into row 12 column A",
"component": "R1"
},
{
"step": 3,
"instruction": "Jumper wire (red) from positive (+) power rail to row 5 column A",
"type": "wire",
"color": "red"
},
{
"step": 4,
"instruction": "Jumper wire (black) from row 12 column B to negative (−) power rail",
"type": "wire",
"color": "black"
},
{
"step": 5,
"instruction": "Jumper wire (green) from row 8 column B to Arduino A0",
"type": "wire",
"color": "green"
},
{
"step": 6,
"instruction": "Place LED: anode (long leg) into row 20 column E, cathode into row 21 column E",
"component": "LED1"
},
{
"step": 7,
"instruction": "Place 220Ω resistor: one leg into row 20 column A, other leg into row 17 column A",
"component": "R2"
},
{
"step": 8,
"instruction": "Jumper wire (orange) from row 17 column B to Arduino pin 9",
"type": "wire",
"color": "orange"
},
{
"step": 9,
"instruction": "Jumper wire (black) from row 21 column A to negative (−) power rail",
"type": "wire",
"color": "black"
},
{
"step": 10,
"instruction": "Jumper wire (red) from Arduino 5V to positive (+) power rail",
"type": "wire",
"color": "red"
},
{
"step": 11,
"instruction": "Jumper wire (black) from Arduino GND to negative (−) power rail",
"type": "wire",
"color": "black"
}
]
},
"code": {
"filename": "module03_light_responsive_led.ino",
"language": "cpp"
},
"validation": {
"expected_behavior": "LED brightness inversely follows ambient light. Serial Monitor shows live values.",
"measurements": {
"sensor_voltage_bright": { "min": 3.0, "max": 4.8, "unit": "V" },
"sensor_voltage_dark": { "min": 0.2, "max": 1.5, "unit": "V" },
"led_max_current": { "min": 12, "max": 22, "unit": "mA" }
},
"common_mistakes": [
"Sensor reads 0 always: LDR and 10kΩ resistor may be swapped in the divider",
"Sensor reads 1023 always: check that the junction wire actually reaches A0",
"LED doesn't dim smoothly: verify pin 9 is a PWM pin (marked with ~)",
"Serial shows garbage: baud rate mismatch — check Serial.begin matches monitor setting",
"Values don't change when covering sensor: photoresistor legs might not be in connected rows"
]
}
}
Experiments to Try
Experiment 1: Night Light with Threshold
Instead of smooth dimming, make the LED turn fully on below a threshold and fully off above it:
const int THRESHOLD = 400; // Adjust based on your readings
void loop() {
int lightLevel = analogRead(SENSOR_PIN);
if (lightLevel < THRESHOLD) {
digitalWrite(LED_PIN, HIGH); // Dark — light on
} else {
digitalWrite(LED_PIN, LOW); // Bright — light off
}
delay(100);
}
Experiment 2: Serial Plotter
Instead of the Serial Monitor, try Tools → Serial Plotter. Print just the number:
void loop() {
Serial.println(analogRead(SENSOR_PIN));
delay(50);
}
You'll see a live graph of the sensor readings — much easier to visualize than scrolling numbers.
Experiment 3: Button + Sensor + LED
Combine a button from section 3.2: when the button is pressed, the LED responds to light; when released, the LED is off.
const int BUTTON_PIN = 7;
const int SENSOR_PIN = A0;
const int LED_PIN = 9;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
// Button pressed — respond to light
int light = analogRead(SENSOR_PIN);
int brightness = map(light, 0, 1023, 255, 0);
analogWrite(LED_PIN, constrain(brightness, 0, 255));
} else {
// Button not pressed — LED off
analogWrite(LED_PIN, 0);
}
delay(50);
}
Self-Check: Module 3
Before moving to Module 4, make sure you can:
- Explain the difference between digital and analog signals
- Explain why floating pins are a problem and how pull-up/pull-down resistors fix it
- Use
digitalRead()withINPUT_PULLUPto read a button - Use
analogRead()to read a sensor and convert the value to voltage - Explain how a voltage divider works and why it's needed for variable resistors
- Use
analogWrite()(PWM) to control LED brightness - Use the Serial Monitor to display debug information
- Apply the hardware-or-software debugging decision tree
- Build the light-responsive LED project and calibrate it for your environment
Key Terms Glossary
| Term | Definition |
|---|---|
| Digital signal | A signal with exactly two states: HIGH (5V) or LOW (0V) |
| Analog signal | A signal that varies continuously across a range of values |
| ADC | Analog-to-Digital Converter — reads a voltage and returns a number (0–1023) |
| PWM | Pulse Width Modulation — rapidly switching a digital pin to simulate analog output |
| Duty cycle | The percentage of time a PWM signal is HIGH |
| Pull-up resistor | Connects a pin to VCC so it reads HIGH by default |
| Pull-down resistor | Connects a pin to GND so it reads LOW by default |
| Floating pin | A disconnected input pin that reads unpredictable values |
| INPUT_PULLUP | Arduino pin mode that activates the internal ~20kΩ pull-up resistor |
| Voltage divider | Two resistors in series that output a fraction of the input voltage |
| map() | Arduino function that scales a number from one range to another |
| constrain() | Arduino function that limits a number to a min/max range |
| Serial Monitor | Text display in the IDE for debugging (Tools → Serial Monitor) |
| Baud rate | Communication speed for serial — must match between code and monitor |
| Photoresistor (LDR) | A resistor whose value changes based on light level |
Previous: ← Module 2 — Meet the Arduino Next: Module 4 — Making Decisions: Logic & Control Flow →