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}}$
LDR Series Resistor Decision Matrix
Your LDR ModelDark ResistanceLight Resistance (10 lux)Calculated IdealConcrete 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Ω
GL5516500 kΩ5 kΩ50 kΩ47 kΩ
GL55392 MΩ50 kΩ316 kΩ330 kΩ
Generic / UnknownUnknownUnknownN/A10 kΩ (Default safe pick)
Bench Tip: If you are building an indoor automatic lighting trigger (detecting when a room goes dark), optimize your $R_{series}$ for the transition point (around 50-100 lux), not pitch black. For 90% of hobbyist indoor projects using the standard 5mm GL5528 LDR, a 10 kΩ carbon film resistor is the exact part you need.

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

ComponentComponent Pin / LegArduino Uno R3 PinNotes
LDRLeg 15VPolarity does not matter for LDRs
LDRLeg 2Analog A0This is the ADC read point (Vout)
10kΩ ResistorLeg 1Analog A0Ties into LDR Leg 2 to form the divider node
10kΩ ResistorLeg 2GNDCompletes the circuit to ground
0.1µF CapLeg 1Analog A0Filters high-frequency AC noise
0.1µF CapLeg 2GNDStabilizes the ADC sample-and-hold circuit

Step-by-Step Wiring & Voltage Divider Math

  1. Insert the LDR: Straddle the breadboard's center trench. Place one leg in row 10, the other in row 11.
  2. Insert the 10kΩ Resistor: Place one leg in row 11 (sharing the LDR connection) and the other in row 15.
  3. 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.
  4. Wire the ADC Node: Connect a yellow jumper from row 11 (the junction of the LDR and resistor) to the Arduino A0 pin.
  5. 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.

Final Recommendation: For 95% of hobbyist projects (nightlights, solar trackers, basic alarms), stick with the GL5528 LDR and a 10 kΩ series resistor. It is cheap, requires no external libraries, and the EMA smoothing code provided above will easily handle the analog noise. Only upgrade to the BH1750 when your application demands calibrated scientific data.