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.

Table 1: GL5528 LDR Resistance vs. Illuminance (at 25°C)
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
Pro-Tip for ESP32 Users: Avoid using ADC2 pins (GPIO 4, 12-15, 25-27) if you plan to use WiFi, as the WiFi driver disables ADC2 during operation. Stick to ADC1 pins (GPIO 32-39).

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
Calibration Check: To calibrate your specific LDR batch, download a free Lux meter app on your smartphone. Place the phone's ambient light sensor and your LDR under the exact same diffuse light source (avoid point-source shadows). Record the app's Lux and your serial monitor's resistance. Adjust the 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.