If you need to measure ambient light with a microcontroller, the best default photoresistor for Arduino is the GL5528 paired with a 10kΩ pull-down resistor. This specific combination provides the most linear voltage response across standard indoor room lighting (10 to 100 lux) without requiring complex calibration or external amplification.
While phototransistors and digital I2C sensors exist, the passive Light Dependent Resistor (LDR) remains the most cost-effective and forgiving component for hobbyist light triggers, automatic night lights, and plant grow-box monitors. Below is the exact decision framework, wiring physics, and production-ready code to get it running on the bench today.
Decision Tree: Which Light Sensor Should You Actually Buy?
Not all light sensors behave the same way. Before ordering parts, run your project requirements through this decision matrix to ensure you are not fighting the physics of the wrong component.
| Sensor Type / Model | Response Time | Dark Resistance | Best Use Case | Drawbacks |
|---|---|---|---|---|
| GL5528 (LDR) | Slow (~20ms) | ~1 MΩ | Room lighting, streetlamp triggers, indoor automation | Non-linear, slow to react to strobes/lasers |
| GL5516 (LDR) | Slow (~20ms) | ~0.5 MΩ | Bright outdoor sunlight tracking | Too insensitive for dim indoor environments |
| TEMT6000 (Phototransistor) | Fast (~15µs) | N/A (Current output) | Fast pulse counting, IR proximity, laser tripwires | Requires transimpedance amplifier for high precision |
| TSL2561 (Digital I2C) | Digital (Variable) | N/A (Digital IC) | Accurate lux logging, horticulture, UI auto-brightness | Expensive (~$5), requires library and I2C wiring |
Parts List & Pin Mapping
This build targets the Arduino Uno R3 (ATmega328P, 5V logic, 10-bit ADC). If you are using a 3.3V board like the ESP32 or Arduino Nano 33 IoT, see the voltage warning below.
Required Components
- Microcontroller: Arduino Uno R3 (or Nano v3 with ATmega328P)
- Sensor: GL5528 Photoresistor (5mm package)
- Pull-down Resistor: 10kΩ, 1/4W carbon film (±5% tolerance)
- Indicator LED: 5mm standard diffused LED (any color)
- Current Limiting Resistor: 220Ω or 330Ω for the LED
- Wiring: 22 AWG solid core jumper wires
Pin Mapping Table
| Component | Component Pin / Leg | Arduino Uno R3 Pin | Notes |
|---|---|---|---|
| GL5528 LDR | Leg 1 | 5V | Polarity does not matter on LDRs |
| GL5528 LDR | Leg 2 | A0 (Analog In) | Forms the voltage divider junction |
| 10kΩ Resistor | Leg 1 | A0 (Analog In) | Shares the same breadboard node as LDR Leg 2 |
| 10kΩ Resistor | Leg 2 | GND | Pulls the analog pin to 0V in darkness |
| Indicator LED | Anode (Long Leg) | Pin 8 (via 220Ω) | Digital output for threshold trigger |
| Indicator LED | Cathode (Short Leg) | GND | Common ground with the resistor network |
The Voltage Divider Physics (Why 10kΩ?)
A microcontroller cannot read resistance directly; it reads voltage. We must convert the GL5528's changing resistance into a 0V–5V signal using a voltage divider. The formula governing this junction is:
V_out = V_in × (R_pull-down / (R_LDR + R_pull-down))
The GL5528 has a dark resistance of roughly 1 MΩ and a bright light (10 lux) resistance of roughly 10kΩ to 20kΩ. If you use a 1kΩ pull-down resistor, your maximum voltage in bright light will only reach ~0.45V, wasting 90% of the Arduino's 10-bit ADC resolution (0-1023). If you use a 100kΩ pull-down, the voltage in normal room light will peg at 5V, saturating the sensor.
By choosing a 10kΩ pull-down, the junction voltage sits at roughly 2.5V (ADC value ~512) when the LDR is exposed to standard indoor office lighting (~20kΩ). This centers your measurement range, giving you maximum resolution for both shadows and bright flashes. For a deeper look at analog input scaling, refer to the official Arduino AnalogInOutSerial documentation.
Complete Arduino Code with Error Handling
High-impedance analog nodes (like an LDR in a dark room hitting 1 MΩ) act as antennas, picking up 50/60Hz electromagnetic interference from nearby mains wiring. A single analogRead() will fluctuate wildly. The code below implements a 16-sample oversampling array to average out AC ripple, alongside runtime fault detection for disconnected components.
/*
* GL5528 Photoresistor Light Logger & Threshold Trigger
* Target Board: Arduino Uno R3 (ATmega328P, 5V Logic)
* Author: ElectricalFlux Bench Team
*/
// --- PIN DEFINITIONS ---
#define LDR_PIN A0 // Analog input from voltage divider
#define LED_PIN 8 // Digital output for night-light trigger
// --- CALIBRATION & THRESHOLDS ---
#define DARK_THRESHOLD 300 // ADC value (0-1023) to trigger LED (approx dim room)
#define SAMPLE_SIZE 16 // Number of reads to average (filters 60Hz noise)
#define FAULT_LOW 5 // ADC value indicating a short to GND
#define FAULT_HIGH 1018 // ADC value indicating a floating/disconnected pin
// --- GLOBAL VARIABLES ---
unsigned long lastPrintTime = 0;
const unsigned long printInterval = 1000; // Print to serial every 1 second
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Allow ADC to stabilize after power-on
delay(100);
analogRead(LDR_PIN); // Dummy read to clear multiplexer charge
Serial.println(F("System Initialized. Monitoring GL5528 on A0..."));
}
void loop() {
int rawLightLevel = readFilteredLDR();
// --- RUNTIME ERROR HANDLING ---
if (rawLightLevel <= FAULT_LOW) {
Serial.println(F("ERROR: Sensor shorted to GND or pull-up missing (Reading: 0)"));
digitalWrite(LED_PIN, LOW);
delay(500);
return;
}
if (rawLightLevel >= FAULT_HIGH) {
Serial.println(F("ERROR: Sensor floating or pull-down missing (Reading: 1023)"));
digitalWrite(LED_PIN, HIGH); // Blink LED as a visual hardware fault alarm
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
return;
}
// --- NORMAL OPERATION ---
// Map ADC value to an approximate Lux scale (highly non-linear, for relative logging only)
// Formula derived from typical GL5528 datasheet curve: Lux ≈ 32000 * (V_out / (5 - V_out))^(-1.4)
// For simplicity in integer math, we use a mapped approximation.
int approxLux = map(rawLightLevel, 0, 1023, 0, 1000);
// Threshold Trigger
if (rawLightLevel < DARK_THRESHOLD) {
digitalWrite(LED_PIN, HIGH); // Turn on night light
} else {
digitalWrite(LED_PIN, LOW);
}
// Throttled Serial Logging
if (millis() - lastPrintTime >= printInterval) {
lastPrintTime = millis();
Serial.print(F("ADC Raw: "));
Serial.print(rawLightLevel);
Serial.print(F(" | Approx Lux: "));
Serial.println(approxLux);
}
}
// --- FILTERING FUNCTION ---
// Averages 16 rapid samples to eliminate 50/60Hz mains hum on high-impedance nodes
int readFilteredLDR() {
long sum = 0;
for (int i = 0; i < SAMPLE_SIZE; i++) {
sum += analogRead(LDR_PIN);
delay(2); // 2ms delay spaces samples across the AC waveform
}
return (int)(sum / SAMPLE_SIZE);
}
Debugging: Why is My analogRead() Stuck at 0 or 1023?
When prototyping analog sensors, the most common failure mode is a hard-pegged reading. If your Serial Monitor outputs the exact error strings coded above, follow this diagnostic path.
First 3 Things to Check When It Fails
- Breadboard Power Rail Continuity: Many cheap breadboards have split power rails in the middle. Use a multimeter in continuity mode to verify that the 5V and GND rails actually reach the rows where your LDR and 10kΩ resistor are plugged in.
- Resistor Value Verification: Pull the 10kΩ resistor out of the board and measure it with a DMM. Color bands can be misread (e.g., confusing a 100kΩ for a 10kΩ). It should read between 9.5kΩ and 10.5kΩ.
- Breadboard Node Shorting: LDR legs are thick and can bend the internal breadboard contacts, causing the A0 node to short directly to the adjacent GND or 5V rail. Inspect the junction with a magnifying glass.
Ranked Causes for Specific Error Strings
| Serial Monitor Error String | Most Likely Cause (Ranked) | How to Fix |
|---|---|---|
ERROR: Sensor shorted to GND... |
1. 10kΩ pull-down resistor is missing or installed in the wrong row. 2. LDR leg is bent and touching the GND rail. 3. A0 pin is internally damaged. |
Verify the physical path from A0 to GND. Ensure the 10kΩ resistor bridges the A0 row and the GND row. |
ERROR: Sensor floating... |
1. 5V wire to the top of the LDR is disconnected. 2. The LDR itself is internally cracked (infinite resistance). 3. Pull-down resistor is missing. |
Measure voltage across the LDR legs with a DMM. If it reads 0V, your 5V supply isn't reaching the component. |
Compiler Error: 'A0' was not declared in this scope |
1. Code placed outside of setup() or loop().2. Missing #define LDR_PIN A0 at the top of the sketch. |
Ensure all pin definitions are in the global scope above void setup(). |
For more advanced troubleshooting on analog noise and ADC grounding issues, the Adafruit Photocell Tutorial provides excellent visual references for breadboard layouts.
Extending and Simplifying the Build
Once you have the baseline GL5528 circuit working, you will eventually hit the limits of analog resistance-based sensing. Here is how to scale the project up or down based on your final application.
How to Simplify (For Production / PCB Design)
If you are moving this from a breadboard to a custom PCB for a commercial night-light product, drop the GL5528 and the 10kΩ resistor. Instead, use a PTN3315 or similar integrated ambient light sensor IC. It eliminates the need for manual voltage divider tuning, draws microamps of quiescent current, and provides a clean digital I2C output, reducing your BOM (Bill of Materials) complexity and calibration time on the assembly line.
How to Extend (For Smart Home Integration)
To turn this into an IoT light logger:
- Swap the Board: Replace the Uno R3 with an ESP32-WROOM-32 DevKit v1.
- Fix the Voltage: Remember the 3.3V warning. Power the divider from the ESP32's 3.3V pin and use a 4.7kΩ pull-down resistor.
- Add Connectivity: Use the
WiFi.handPubSubClientlibraries to publish theapproxLuxinteger to an MQTT broker (like Mosquitto or Home Assistant) every 5 seconds. - Add Hysteresis: To prevent the LED from flickering rapidly when a cloud passes over the sensor, implement a software deadband. Only turn the LED off if the reading exceeds
DARK_THRESHOLD + 50.






