How a Photoresistor Sensor Actually Works

A photoresistor sensor, technically known as a Light Dependent Resistor (LDR) or photocell, relies on the photoconductivity of semiconductor materials like cadmium sulfide (CdS) or lead sulfide (PbS). When photons with sufficient energy strike the semiconductor lattice, they excite bound electrons into the conduction band, creating electron-hole pairs. This increase in charge carriers drastically lowers the electrical resistance of the material, allowing more current to flow for a given applied voltage. In complete darkness, a typical CdS cell exhibits resistance in the megaohm range, while under bright sunlight, it drops to a few hundred ohms.

Crucially, a bare photoresistor sensor does not output a digital signal, a direct current, or a standalone voltage; it is strictly a passive, two-terminal variable resistor. To interface it with a microcontroller, you must construct a resistive voltage divider circuit that converts the changing resistance into a proportional analog voltage. This analog voltage is then read by the microcontroller’s Analog-to-Digital Converter (ADC), meaning the raw output is entirely dependent on your chosen supply voltage and the fixed resistor value in your divider network.

Table 1: Common CdS Photoresistor Sensor Specifications (at 25°C)
Part Number Dark Resistance (MΩ) 10 Lux Resistance (kΩ) Response Time (ms) Spectral Peak (nm) Max Voltage (VDC)
GL5516 0.5 5 - 10 20 / 30 540 150
GL5528 1.0 10 - 20 20 / 30 540 150
GL5539 3.0 30 - 90 25 / 40 540 150
GL5549 5.0 45 - 140 30 / 50 540 150
⚠️ RoHS & Material Warning: Standard high-sensitivity CdS photoresistors contain cadmium, a restricted heavy metal under RoHS directives. For commercial products requiring RoHS compliance, you must switch to alternative ambient light sensors (ALS) like the OPT3001 or TSL2591, which use digital I2C interfaces and silicon photodiodes instead of CdS chemistry.

Wiring the Photoresistor Sensor to ESP32 and Arduino

Because the LDR is just a variable resistor, we use a voltage divider to create a measurable analog voltage. The optimal value for the fixed pulldown resistor ($R_{fixed}$) is the geometric mean of the LDR's minimum and maximum expected resistances: $R_{fixed} = \sqrt{R_{dark} \times R_{bright}}$. For a GL5528 used indoors, a 10 kΩ fixed resistor provides the best voltage swing across typical room lighting conditions.

Table 2: Wiring Pinout and Supply Ranges
Component Pin Arduino Uno (5V Logic) ESP32 DevKit V1 (3.3V Logic) Notes
LDR Leg 1 5V 3V3 Supply range: 3.3V to 5V max. Do not exceed 5V.
LDR Leg 2 A0 (Analog In) GPIO 34 (ADC1_CH6) Must use an ADC-capable pin. Node connects to $R_{fixed}$.
$R_{fixed}$ (10kΩ) Leg 1 A0 (Analog In) GPIO 34 Forms the voltage divider junction.
$R_{fixed}$ (10kΩ) Leg 2 GND GND Common ground reference.

Physical Wiring Steps

  1. De-energize the board: Disconnect the USB cable from your ESP32 or Arduino before wiring.
  2. Insert the LDR: Place the two legs of the GL5528 photoresistor sensor into separate rows on your breadboard. Polarity does not matter; LDRs are non-polarized.
  3. Insert the Fixed Resistor: Place one leg of the 10 kΩ resistor in the same row as one leg of the LDR. Place the other leg in an empty row.
  4. Wire the Junction: Run a jumper wire from the shared LDR/Resistor junction row to your microcontroller's ADC pin (A0 for Uno, GPIO 34 for ESP32).
  5. Wire Power and Ground: Connect the free LDR leg to 3.3V (or 5V). Connect the free 10 kΩ resistor leg to GND.
  6. Verify: Use a multimeter in continuity mode to ensure the ADC pin is not shorted to VCC or GND before applying power.

Converting Raw ADC Readings to Lux (The Math)

Reading the raw ADC value is only the first step. To get a meaningful physical unit (Lux), you must reverse the voltage divider math, calculate the LDR's current resistance, and apply the sensor's logarithmic light-response curve.

1. Raw ADC to Voltage

For a 12-bit ESP32 ADC with a 3.3V reference, the raw reading (0-4095) converts to voltage via: $V_{out} = \frac{ADC_{raw}}{4095} \times 3.3$. For a 10-bit Arduino Uno, use 1023 and 5.0V.

2. Voltage to Resistance

Using the standard voltage divider formula where the LDR is on top (connected to VCC) and the fixed resistor is on the bottom (connected to GND), we solve for $R_{LDR}$:

R_LDR = R_fixed * ((V_cc / V_out) - 1)

3. Resistance to Lux

CdS photoresistors follow a power-law relationship between resistance and illuminance. The standard approximation formula is:

Lux = 10 * (R_10 / R_LDR) ^ (1 / gamma)

Where R_10 is the resistance at 10 Lux (typically 15 kΩ for a GL5528) and gamma is the slope of the log-log curve (typically ~0.7 for standard CdS cells).

Complete ESP32 Arduino Code

// ESP32 Photoresistor Sensor (GL5528) Lux Calculation
// Hardware: LDR on top (3V3), 10k fixed resistor on bottom (GND), ADC on GPIO 34

const int LDR_PIN = 34;
const float V_CC = 3.3;
const float R_FIXED = 10000.0; // 10k Ohm pulldown
const float R_10 = 15000.0;    // GL5528 typical resistance at 10 Lux
const float GAMMA = 0.7;       // Log-log slope for CdS cells
const int ADC_MAX = 4095;      // 12-bit resolution

void setup() {
  Serial.begin(115200);
  analogReadResolution(12); // Explicitly set 12-bit for ESP32
  pinMode(LDR_PIN, INPUT);
}

void loop() {
  // Read raw ADC and convert to millivolts for better ESP32 accuracy
  int raw_adc = analogRead(LDR_PIN);
  float v_out = (raw_adc * V_CC) / ADC_MAX;
  
  // Prevent division by zero in dark conditions where V_out approaches 0
  if (v_out < 0.05) {
    Serial.println("Lux: 0.00 (Darkness)");
    delay(1000);
    return;
  }

  // Calculate LDR Resistance
  float r_ldr = R_FIXED * ((V_CC / v_out) - 1.0);
  
  // Calculate Lux
  float lux = 10.0 * pow((R_10 / r_ldr), (1.0 / GAMMA));
  
  Serial.print("Raw ADC: ");
  Serial.print(raw_adc);
  Serial.print(" | Voltage: ");
  Serial.print(v_out, 2);
  Serial.print("V | R_LDR: ");
  Serial.print(r_ldr, 0);
  Serial.print(" Ohms | Lux: ");
  Serial.println(lux, 2);
  
  delay(500);
}

Calibration, Edge Cases, and Interference Sources

While the math above gets you into the ballpark, real-world bench testing reveals several interference sources and hardware quirks that require calibration.

💡 Pro-Tip: The ESP32 ADC Non-Linearity
The ESP32’s internal ADC is notoriously non-linear at the extremes. Readings below ~100mV (ADC < 120) and above ~3.1V (ADC > 3900) will saturate and yield inaccurate resistance calculations. If your application requires high precision in very bright or very dark conditions, use the ESP-IDF adc_calibration API or add an external I2C ADC like the ADS1115.

Common Interference Sources

  • 50/60Hz Mains Flicker: Incandescent and fluorescent bulbs pulse at twice the AC line frequency (100Hz or 120Hz). Because CdS cells have a relatively slow response time (20-30ms), they naturally average this out, but fast-sampling code might catch the ripple. Add a 100nF ceramic capacitor in parallel with the fixed 10kΩ resistor to create a hardware low-pass filter.
  • Temperature Drift: CdS photoresistors exhibit a temperature coefficient. In darkness, resistance drops as temperature rises (negative tempco). In bright light, resistance increases with temperature (positive tempco). If your sensor is mounted near a heat-generating component (like a voltage regulator), your dark-room baseline will drift.
  • Spectral Mismatch (IR Contamination): CdS cells peak in sensitivity around 540nm (green light), closely matching the human eye. However, they remain highly sensitive to near-infrared (IR) light. If you place the sensor near an incandescent bulb or a heat source emitting IR, it will read a much higher Lux value than a human perceives. Modern white LEDs emit very little IR, making them ideal test sources for calibration.
  • Dielectric Absorption / Memory Effect: If a CdS cell is kept in total darkness for several hours, its resistance can climb significantly higher than its rated "dark resistance" and take several minutes to stabilize when first exposed to light. Always allow a 5-minute stabilization period after power-on before taking baseline calibration readings.

By selecting the correct geometric mean pulldown resistor, filtering out high-frequency noise with a parallel capacitor, and applying the logarithmic power-law math, the humble GL5528 photoresistor sensor remains a highly effective, sub-$0.50 solution for basic ambient light tracking in DIY embedded projects.