To use a photoresistor with Arduino, you must wire it in a voltage divider circuit with a fixed 10kΩ resistor and read the resulting analog voltage on pin A0. The Arduino's analog-to-digital converter (ADC) cannot read resistance directly; it only reads voltage (0-5V). The voltage divider translates the light-dependent resistance into a proportional voltage that the microcontroller can process.
Project Overview & Difficulty Rating
Time to Complete: 20 minutes
Target Board: Arduino Uno R3 (ATmega328P) or Uno R4 Minima
Core Concept: Analog voltage division and 10-bit ADC mapping.
Parts List & Spec Sheet
Using the exact components below ensures the math in our voltage divider and code thresholds align perfectly. Substituting a different LDR (like a GL5516) will require recalibrating the fixed resistor and software thresholds.
| Component | Exact Variant / Spec | Estimated Price (2026) | Why This Part? |
|---|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | $24.00 | Standard 5V logic, 10-bit ADC (0-1023). |
| Photoresistor (LDR) | GL5528 (5mm epoxy) | $0.15 (pack of 20) | 10-20kΩ at 10 lux. Spectral peak 540nm (matches ambient room light). |
| Fixed Resistor | 10kΩ, 1/4W, 1% Metal Film | $0.05 | Matches the LDR's mid-point resistance for maximum voltage swing. |
| Filter Capacitor | 0.1µF (100nF) Ceramic | $0.05 | Hardware debouncing to smooth out high-frequency ADC noise. |
Wiring the Voltage Divider
The voltage divider is the bridge between the physical world (resistance) and the digital world (voltage). We place the GL5528 LDR on the high side (connected to 5V) and the 10kΩ fixed resistor on the low side (connected to GND). The analog read point sits exactly between them.
The Math: When room light hits the GL5528, its resistance drops to roughly 10kΩ. Because the fixed resistor is also 10kΩ, the voltage at the midpoint is exactly half of 5V (2.5V). In total darkness, the LDR spikes to ~1MΩ, dropping the midpoint voltage to near 0V. In bright sunlight, the LDR drops to ~2kΩ, pushing the voltage near 4.2V.
Pin Mapping Table
| Arduino Uno Pin | Component Lead | Wire Color (Standard) |
|---|---|---|
| 5V | LDR Leg 1 | Red |
| A0 | LDR Leg 2 & 10kΩ Leg 1 | Yellow / Orange |
| GND | 10kΩ Leg 2 & 0.1µF Cap Leg 2 | Black |
Wiring Steps
- De-energize: Ensure the Arduino is unplugged from USB before wiring.
- Place the LDR: Insert the GL5528 legs into two separate breadboard rows. Polarity does not matter; photoresistors are non-polarized.
- Bridge the Junction: Insert one leg of the 10kΩ resistor into the same row as one of the LDR legs. This shared row is your analog signal junction.
- Add the Filter: Insert the 0.1µF ceramic capacitor in parallel with the 10kΩ resistor (one leg in the junction row, one leg in the GND row). This creates a hardware low-pass filter, saving you from heavy software smoothing.
- Connect to Arduino: Run a red jumper from the Arduino 5V pin to the free LDR leg. Run a black jumper from Arduino GND to the free 10kΩ resistor leg. Run a yellow jumper from the junction row to Arduino pin A0.
- Verify: Use a multimeter set to DC Volts. Probe the junction row and GND. Shine a flashlight on the LDR; the meter should read between 2.5V and 4.5V. Cover it with your hand; it should drop below 0.5V.
Complete Arduino C++ Code
The following code targets the Arduino Uno R3 (ATmega328P). It utilizes a moving average array to smooth out any remaining ADC jitter that bypasses the hardware capacitor, and includes serial error handling to prevent hanging on native-USB board variants.
// Target Board: Arduino Uno R3 (ATmega328P) / Uno R4
// Sensor: GL5528 Photoresistor in a 5V voltage divider
const int LDR_PIN = A0;
const int LED_PIN = 13; // Onboard LED for dark detection
const int NUM_READINGS = 10;
const int DARK_THRESHOLD = 300; // Calibrate via Serial Monitor
int readings[NUM_READINGS];
int readIndex = 0;
long total = 0;
int average = 0;
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
// Initialize the smoothing array
for (int i = 0; i < NUM_READINGS; i++) {
readings[i] = 0;
}
// Wait for serial connection (good practice for all board types)
while (!Serial && millis() < 2000) {
delay(10);
}
Serial.println("GL5528 Photoresistor initialized. Cover sensor to trigger LED.");
}
void loop() {
// Subtract the oldest reading from the total
total = total - readings[readIndex];
// Read the analog pin (0-1023)
int rawValue = analogRead(LDR_PIN);
// Bounds checking to prevent array corruption from ADC spikes
if (rawValue < 0) rawValue = 0;
if (rawValue > 1023) rawValue = 1023;
// Add new reading to array and total
readings[readIndex] = rawValue;
total = total + readings[readIndex];
// Advance to the next array position
readIndex = (readIndex + 1) % NUM_READINGS;
// Calculate the smoothed average
average = total / NUM_READINGS;
// Output data for calibration
Serial.print("Raw: ");
Serial.print(rawValue);
Serial.print(" | Smoothed: ");
Serial.println(average);
// Trigger logic: Lower voltage = darker environment
if (average < DARK_THRESHOLD) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
delay(50); // 20Hz sample rate
}
Debugging Common LDR Errors
When working with analogRead and passive components, issues usually manifest as compile errors from typos or hardware faults that result in locked serial outputs.
Compile Error: 'analogRead' was not declared in this scope
Exact Error String: error: 'analogRead' was not declared in this scope
- Cause 1 (Most Likely): C++ is strictly case-sensitive. You typed
AnalogReadoranalogread. It must be exactlyanalogRead. - Cause 2: You placed the
analogRead()function call outside of thesetup()orloop()functions, directly in the global scope.
Runtime Symptom: Serial output stuck at 1023 or 0
If your serial monitor prints a constant 1023 (or 0) regardless of how much light hits the sensor, your voltage divider is wired incorrectly or shorted.
- Verify the Pulldown Resistor: Ensure the 10kΩ resistor connects the A0 junction to GND, not to 5V. If both the LDR and the 10kΩ resistor are tied to 5V, the pin will always read 1023.
- Measure the Junction Voltage: Unplug the Arduino, set your multimeter to DC Volts, plug it back in, and probe the A0 junction row. If it reads a flat 5.0V or 0.0V, you have a shorted breadboard trace or a broken component lead.
- Check for Floating Pins: If the reading is jumping randomly between 0 and 1023, your GND wire is loose, leaving the A0 pin 'floating' and acting as an antenna for ambient electrical noise.
Extending and Simplifying the Build
Depending on your end goal, you might want to strip this project down to its bare essentials or scale it up for home automation.
How to Simplify (Digital Output Only)
If you only need to know "is it dark or light?" and don't care about the exact lux level, ditch the raw GL5528 and buy a pre-built LM393 Light Sensor Module ($1.50). These modules have the voltage divider and an LM393 comparator built-in. They output a clean digital HIGH/LOW signal via a potentiometer you tune with a small screwdriver. You can wire this directly to a standard digital GPIO pin (e.g., Pin 2) and use digitalRead(), eliminating the need for analog smoothing code entirely.
How to Extend (IoT and Displays)
To turn this into a smart home lux meter, swap the Arduino Uno for an ESP32 DevKit V1. Wire the LDR to GPIO 34 (an input-only ADC pin on the ESP32). Note that the ESP32 ADC is 12-bit (0-4095) and operates at 3.3V logic. Warning: You must change the fixed resistor to 10kΩ tied to 3.3V, NOT 5V, or you will permanently damage the ESP32's ADC pin. From there, you can use the PubSubClient library to publish the smoothed lux readings to an MQTT broker for integration with Home Assistant.
Frequently Asked Questions
Can I use a photoresistor with Arduino without a resistor?
No. A microcontroller's ADC pin measures voltage potential, not resistance. If you wire the LDR directly between 5V and A0 with no pulldown resistor to GND, you create a short circuit through the microcontroller's internal protection diodes when the LDR's resistance drops in bright light. This can damage the ATmega328P chip. The 10kΩ fixed resistor is mandatory to limit current and create the voltage divider.
Why is my photoresistor reading fluctuating so much?
Photoresistors are highly susceptible to 50Hz/60Hz flicker from AC-powered room lighting (LEDs and fluorescents). If your serial monitor shows the value bouncing by ±20 points rapidly, you are sampling the AC flicker. You can fix this in hardware by adding a 1µF electrolytic capacitor in parallel with the fixed resistor, or in software by increasing the NUM_READINGS array size in the code above from 10 to 50, which averages out the AC waveform cycles.
What is the difference between a photoresistor and a photodiode for Arduino?
A photoresistor (LDR) changes resistance based on light and is great for slow, ambient light detection (like turning on a streetlamp). It has a slow response time (20-50ms). A photodiode generates a tiny current when hit by light and requires an op-amp transimpedance circuit to read with an Arduino. Photodiodes respond in nanoseconds and are used for high-speed optical data transmission (like IR remote receivers or fiber optics), but they are overkill and too complex for basic room-lighting projects.






