The Core Decision: Choosing the Right Series Resistor for Your LDR
The most common mistake when wiring a Light Dependent Resistor (LDR) to a microcontroller is picking an arbitrary series resistor. An LDR is a variable resistor; to read it with an Arduino's Analog-to-Digital Converter (ADC), you must build a voltage divider. The series resistor ($R_{series}$) dictates your voltage swing. If $R_{series}$ is too high, your ADC pegs at 1023 in normal room light. If it is too low, your reading stays near 0.
To maximize the ADC resolution across your specific lighting environment, calculate the geometric mean of the LDR's resistance in total darkness ($R_{dark}$) and bright light ($R_{light}$):
$R_{series} = \sqrt{R_{dark} \times R_{light}}$
| Your LDR Model | Dark Resistance | Light Resistance (10 lux) | Calculated Ideal | Concrete Pick (Standard E12 Value) |
|---|---|---|---|---|
| GL5528 (Most Common) | 1 MΩ | 10 kΩ | 100 kΩ (Wait, geometric mean of 1M and 10k is 100k. But for room light (~100 lux), LDR drops to ~2k. Let's optimize for indoor room light). | 10 kΩ |
| GL5516 | 500 kΩ | 5 kΩ | 50 kΩ | 47 kΩ |
| GL5539 | 2 MΩ | 50 kΩ | 316 kΩ | 330 kΩ |
| Generic / Unknown | Unknown | Unknown | N/A | 10 kΩ (Default safe pick) |
Parts List & Pin Mapping for the Arduino Uno R3 Build
This build targets the Arduino Uno R3 (ATmega328P microcontroller, 10-bit ADC, 5V logic). The principles apply identically to the Nano v3 or Mega 2560, but the Uno R3 is the baseline.
Bill of Materials
- Microcontroller: Arduino Uno R3 (or compatible clone with ATmega328P)
- Sensor: 5mm GL5528 LDR (Photoresistor)
- Resistor: 10 kΩ 1/4W carbon film resistor (Brown-Black-Orange-Gold)
- Capacitor (Optional but recommended): 0.1 µF (100 nF) ceramic capacitor for ADC noise filtering
- Hardware: Half-size breadboard, 5x male-to-male jumper wires
Pin Mapping Table
| Component | Component Pin / Leg | Arduino Uno R3 Pin | Notes |
|---|---|---|---|
| LDR | Leg 1 | 5V | Polarity does not matter for LDRs |
| LDR | Leg 2 | Analog A0 | This is the ADC read point (Vout) |
| 10kΩ Resistor | Leg 1 | Analog A0 | Ties into LDR Leg 2 to form the divider node |
| 10kΩ Resistor | Leg 2 | GND | Completes the circuit to ground |
| 0.1µF Cap | Leg 1 | Analog A0 | Filters high-frequency AC noise |
| 0.1µF Cap | Leg 2 | GND | Stabilizes the ADC sample-and-hold circuit |
Step-by-Step Wiring & Voltage Divider Math
- Insert the LDR: Straddle the breadboard's center trench. Place one leg in row 10, the other in row 11.
- Insert the 10kΩ Resistor: Place one leg in row 11 (sharing the LDR connection) and the other in row 15.
- Wire Power and Ground: Connect a red jumper from the Arduino 5V pin to row 10. Connect a black jumper from Arduino GND to row 15.
- Wire the ADC Node: Connect a yellow jumper from row 11 (the junction of the LDR and resistor) to the Arduino A0 pin.
- Add the Filter Cap: Insert the 0.1 µF capacitor between row 11 and the GND rail. This creates a low-pass RC filter, drastically reducing erratic ADC jumps caused by fluorescent light flicker or breadboard contact noise.
The Math in Practice:
The Arduino reads voltage using the formula $V_{out} = 5V \times \frac{R_{series}}{R_{LDR} + R_{series}}$.
If your room light hits the GL5528 and drops its resistance to 8 kΩ:
$V_{out} = 5 \times \frac{10000}{8000 + 10000} = 5 \times 0.555 = 2.77V$.
The 10-bit ADC maps 5V to 1023, so $2.77V \times (1023 / 5) = \textbf{568}$. This sits perfectly in the middle of the ADC's dynamic range, giving you maximum resolution for detecting changes.
Complete Arduino Code with ADC Smoothing & Error Handling
Raw ADC reads on an Arduino Uno R3 will naturally fluctuate by ±3 to ±5 bits due to internal thermal noise and reference voltage jitter. This code implements an Exponential Moving Average (EMA) filter and includes hardware fault detection to catch disconnected wires.
// Target Board: Arduino Uno R3 (ATmega328P)
// Sensor: GL5528 LDR with 10k Series Resistor on A0
const int LDR_PIN = A0;
const int BAUD_RATE = 9600;
// Smoothing factor for EMA (0.0 to 1.0). Lower = smoother but slower response.
const float ALPHA = 0.15;
float smoothedADC = 0.0;
int faultCounter = 0;
const int FAULT_THRESHOLD = 15; // Consecutive bad reads before triggering alarm
void setup() {
Serial.begin(BAUD_RATE);
pinMode(LDR_PIN, INPUT);
// Prime the EMA filter with an initial reading to prevent startup lag
smoothedADC = analogRead(LDR_PIN);
Serial.println("LDR Voltage Divider Initialized.");
}
void loop() {
int rawADC = analogRead(LDR_PIN);
// Hardware Fault Detection
// If ADC is pegged at 0 or 1023, we likely have a short or open circuit
if (rawADC <= 2 || rawADC >= 1021) {
faultCounter++;
if (faultCounter >= FAULT_THRESHOLD) {
if (rawADC >= 1021) {
Serial.println("FAULT: ADC pegged at 1023 - Check LDR continuity or 5V rail");
} else {
Serial.println("FAULT: ADC pegged at 0 - Check series resistor GND connection");
}
delay(1000); // Throttle serial spam during fault
return;
}
} else {
faultCounter = 0; // Reset counter on valid read
}
// Apply Exponential Moving Average (EMA)
smoothedADC = (ALPHA * rawADC) + ((1.0 - ALPHA) * smoothedADC);
// Convert to Voltage (5V reference, 10-bit resolution)
float voltage = (smoothedADC * 5.0) / 1023.0;
Serial.print("Raw: ");
Serial.print(rawADC);
Serial.print(" | Smooth: ");
Serial.print(smoothedADC, 1);
Serial.print(" | Volts: ");
Serial.println(voltage, 2);
delay(100); // 10Hz sample rate
}
Debugging: First Three Things to Check When Readings Fail
When your serial monitor outputs garbage or the FAULT strings trigger, follow this ranked decision path. These are the three most common failure modes on the workbench.
1. Symptom: Serial outputs 'FAULT: ADC pegged at 1023'
- Most Likely Cause: The LDR is disconnected, or the breadboard row connecting the LDR to the A0 jumper has a bad contact. The Arduino's internal pull-up/pull-down networks or floating gate capacitance are pulling the pin high.
- The Fix: Unplug the board. Use your multimeter in continuity mode. Probe from the Arduino A0 header pin directly to the junction leg of the 10kΩ resistor. It must read < 1 Ω. If it reads OL (Open Loop), move your jumper wires to a different breadboard row.
2. Symptom: Serial outputs 'FAULT: ADC pegged at 0'
- Most Likely Cause: The 10kΩ series resistor is shorted to ground, or the LDR is completely missing while the A0 pin is bridged to the GND rail via the resistor.
- The Fix: Verify the resistor color bands. Ensure you are using a 10kΩ (Brown-Black-Orange) and not a 10Ω (Brown-Black-Black) resistor, which would effectively short the 5V rail to GND and pull the voltage to near zero.
3. Symptom: Erratic Jumping (e.g., Raw: 450, 890, 12, 510)
- Most Likely Cause: 50/60Hz AC mains interference from overhead fluorescent lights coupling into the high-impedance ADC node, or a 'floating' pin if the LDR is removed.
- The Fix: This is exactly why the 0.1 µF capacitor is in the parts list. If you already installed it and still see noise, your USB cable might be unshielded. Swap to a high-quality, shielded USB-B cable, and ensure the 0.1 µF cap is placed as physically close to the A0 junction as possible.
Extending and Simplifying the Circuit
Once you have the baseline voltage divider working, you will eventually hit the limits of what a raw analog LDR can do. Here is how to pivot based on your actual project requirements.
Simplify: Switch to a Digital LM393 Module
If your project only needs to know "Is it dark enough to turn on the porch light?", you do not need an ADC. You do not need smoothing code. Buy a pre-built LM393 LDR Sensor Module (usually $1.50 to $3.00). These modules include a built-in 10kΩ potentiometer acting as the series resistor and an LM393 comparator. You simply wire the DO (Digital Out) pin to any Arduino GPIO and turn the potentiometer with a small flathead screwdriver until the onboard LED triggers at your desired darkness threshold. The code reduces to a simple digitalRead().
Extend: Upgrade to an I2C BH1750 for True Lux
The fundamental flaw of an LDR is that it is entirely relative and non-linear. An ADC reading of 500 does not mean 500 lux; it just means 'medium light' for that specific resistor combination. If you are building a horticulture grow-light controller or a museum display lighting system where legal or biological standards require exact lux measurements, abandon the LDR.
Swap to a BH1750FVI I2C Digital Light Sensor breakout board (approx. $4.00). It communicates via I2C (SDA to A4, SCL to A5 on the Uno R3) and outputs calibrated lux values directly, completely bypassing the need for voltage divider math, ADC smoothing, and analog noise filtering. You can find the official datasheet and integration notes via ROHM Semiconductor's BH1750 documentation.






