An LDR photoresistor sensor is a passive, light-dependent resistor. Out of the box, it does not output a voltage or a digital signal; it merely changes its internal resistance based on incident light. To interface it with a microcontroller like the ESP32 or Arduino, you must build a voltage divider circuit to convert that varying resistance into a readable analog voltage, then apply a logarithmic power-law equation to translate that voltage into physical illuminance (Lux).
How the LDR Photoresistor Sensor Actually Works
The most common LDRs, such as the GL55xx series, rely on the photoconductivity of cadmium sulfide (CdS) or cadmium selenide (CdSe). In the dark, the semiconductor material has very few free electrons, resulting in high resistance (often >1 MΩ). When photons strike the junction, they transfer energy to bound electrons, exciting them into the conduction band. This flood of charge carriers dramatically drops the resistance, sometimes down to 1 kΩ or less under direct sunlight.
It is critical to understand that CdS sensors do not perfectly mimic the human eye's spectral response. While they peak in the visible green-yellow spectrum (around 540 nm), they remain highly sensitive to near-infrared (IR) light up to 800 nm. This means an LDR will read artificially high lux values under incandescent bulbs or heat lamps compared to a calibrated digital lux meter, which typically employs an IR-blocking filter.
Hardware Specs and Voltage Divider Wiring
Because the LDR is purely resistive, you cannot wire it directly to a GPIO pin and expect a reading. You must pair it with a fixed pull-down (or pull-up) resistor to create a voltage divider. The output is an analog voltage ranging from 0V to your supply voltage (VCC).
The table below maps the real-world resistance of a standard GL5528 LDR against physical illuminance. Use this to select your fixed resistor: choose a fixed resistor value that matches the LDR's resistance at your target operating lux level for maximum ADC resolution.
| Illuminance (Lux) | Typical Environment | LDR Resistance (kΩ) | Recommended Fixed Resistor |
|---|---|---|---|
| 1 Lux | Deep twilight / Dim room | 80.0 - 120.0 kΩ | 100 kΩ |
| 10 Lux | Street lighting / Sunset | 8.0 - 12.0 kΩ | 10 kΩ |
| 100 Lux | Well-lit office / Overcast day | 1.2 - 1.8 kΩ | 1.5 kΩ |
| 1,000 Lux | Bright indoor showroom | 0.25 - 0.40 kΩ | 330 Ω |
| 10,000+ Lux | Direct sunlight | < 0.10 kΩ | 100 Ω |
ESP32 Wiring Pinout
For a general-purpose indoor light tracker (10 to 500 Lux), a 10 kΩ fixed resistor is the standard choice. Wire the circuit as follows, placing the LDR on the high side (VCC) and the fixed resistor on the low side (GND).
| Component Pin | Connects To | Notes |
|---|---|---|
| LDR Leg 1 | ESP32 3V3 | Supply range: 3.3V to 5V (LDR is polarity agnostic) |
| LDR Leg 2 | ESP32 GPIO 34 (ADC1_CH6) | Node between LDR and fixed resistor |
| Fixed Resistor (10kΩ) Leg 1 | ESP32 GPIO 34 | Shares the analog read node |
| Fixed Resistor (10kΩ) Leg 2 | ESP32 GND | Completes the voltage divider to ground |
Output Signal Math: Converting Raw ADC to Lux
The raw output from your microcontroller's ADC is a unitless integer (0-4095 on the ESP32's 12-bit ADC). To get Lux, we must reverse-engineer the voltage divider, calculate the LDR's current resistance, and apply the sensor's specific logarithmic decay curve.
Step 1: ADC to Voltage
The ESP32's raw analogRead() is notoriously non-linear at the extreme top and bottom of its range. Instead of mapping raw integers to 3.3V manually, use the ESP32 Arduino core's built-in analogReadMilliVolts() function, which applies factory-calibrated eFuse offsets to return a highly accurate millivolt reading.
Step 2: Voltage to Resistance
With the LDR on top and the fixed resistor ($R_{fixed}$) on the bottom, the voltage at the middle node ($V_{out}$) is:
V_out = V_cc * (R_fixed / (R_LDR + R_fixed))
Rearranging this algebraically to solve for the LDR's resistance yields:
R_LDR = R_fixed * ((V_cc / V_out) - 1)
Step 3: Resistance to Lux (The Power Law)
CdS photoresistors follow a logarithmic power-law relationship between resistance and illuminance: $R = A imes Lux^{-\gamma}$. By plotting the GL5528 datasheet values (10 Lux = 10kΩ, 100 Lux = 1.5kΩ), we can derive the specific constants for this sensor. The gamma ($\gamma$) slope is approximately 0.82, and the multiplier constant $A$ is roughly 66,600. Inverting this formula gives us our final Lux equation:
Lux = pow(66600.0 / R_LDR, 1.22)
Complete ESP32 Arduino Code
#include <Arduino.h>
// Pin and Hardware Definitions
const int LDR_PIN = 34; // ADC1 pin
const float VCC_MV = 3300.0; // 3.3V supply in millivolts
const float R_FIXED = 10000.0; // 10k Ohm pull-down resistor
// GL5528 Empirical Constants (Derived from datasheet log-log plot)
const float LDR_CONSTANT_A = 66600.0;
const float LDR_GAMMA_INVERSE = 1.22; // 1 / 0.82
void setup() {
Serial.begin(115200);
analogSetAttenuation(ADC_11db); // Full 0-3.3V range for ESP32 ADC
delay(1000);
}
void loop() {
// 1. Read calibrated voltage directly (bypasses raw ADC non-linearity)
int v_out_mv = analogReadMilliVolts(LDR_PIN);
// Prevent divide-by-zero if it's pitch black (V_out approaches 0)
if (v_out_mv < 10) {
Serial.println("Lux: 0.00 (Darkness threshold)");
delay(1000);
return;
}
// 2. Calculate LDR Resistance in Ohms
float r_ldr = R_FIXED * ((VCC_MV / (float)v_out_mv) - 1.0);
// 3. Convert Resistance to Lux using power-law approximation
float lux = pow((LDR_CONSTANT_A / r_ldr), LDR_GAMMA_INVERSE);
// Sanity check for direct sunlight saturation
if (lux > 100000) lux = 100000;
Serial.printf("V_out: %d mV | R_LDR: %.0f Ohms | Lux: %.2f\n", v_out_mv, r_ldr, lux);
delay(500);
}
Calibration, Interference, and Edge Cases
While the math above gets you within 20% of true illuminance, real-world environments introduce interference that raw code cannot fix. Understanding these failure modes is the difference between a weekend toy and a reliable environmental monitor.
Common Interference Sources
- 50/60Hz Mains Flicker: AC-powered LEDs and fluorescent tubes pulse at twice the mains frequency (100Hz or 120Hz). If your ADC samples at the wrong microsecond, your Lux reading will swing wildly. Fix: Oversample. Take 20 readings over a 20ms window (one full AC cycle at 50Hz) and average them before calculating Lux.
- Thermal Drift: CdS materials are highly temperature-dependent. A sensor calibrated at 20°C (68°F) will read roughly 10-15% lower resistance at 40°C (104°F) even if light levels haven't changed. If your project sits in a hot greenhouse or near a heater, you must add a thermistor and apply a temperature-compensation multiplier.
- IR Bleed: As mentioned, LDRs "see" infrared. If you point this sensor at a halogen lamp or a heat vent, the Lux reading will spike artificially high compared to a human's perception of brightness. For strict human-centric lighting metrics, you need a digital sensor with an integrated IR filter.
Sensor Selection: LDR vs. Digital Alternatives
Before committing to an LDR photoresistor sensor for a production PCB or permanent installation, evaluate whether a digital alternative better suits your constraints.
| Feature | CdS LDR (GL5528) | BH1750 (Digital I2C) | BPW34 (Photodiode) |
|---|---|---|---|
| Output Type | Analog (Requires Divider) | Digital (I2C, direct Lux) | Analog (Current, requires Transimpedance Amp) |
| IR Sensitivity | High (Skews under heat lamps) | Low (Built-in IR filter) | Very High (Broad spectrum) |
| Response Time | Slow (20-50 ms) | Fast (I2C polling rate) | Ultra-fast (Microseconds) |
| Typical Cost (2026) | $0.10 - $0.25 | $1.20 - $1.80 | $0.40 - $0.70 (plus op-amp circuit) |
| Best Use Case | Simple day/night triggers, outdoor solar trackers | Smart home auto-dimming, indoor grow tents | Laser tripwires, high-speed optical comms |
LDR_CONSTANT_A in the code until the serial output matches the phone. For deeper theory on voltage divider optimization, refer to SparkFun's Voltage Divider Guide and Adafruit's Photocell Tutorial.
By pairing the correct pull-down resistor with the ESP32's calibrated ADC and applying the logarithmic decay formula, the humble LDR transforms from a vague light/dark switch into a surprisingly capable quantitative illuminance sensor.






