A light-dependent resistor (LDR), or photoresistor, operates on the principle of photoconductivity. When photons strike the semiconductor material (typically cadmium sulfide, CdS, or lead sulfide), they transfer energy to bound electrons, promoting them to the conduction band. This increases the number of charge carriers available, which directly lowers the electrical resistance of the component. In complete darkness, an LDR exhibits high resistance (often 1MΩ to 10MΩ), but under bright sunlight, this drops to a few hundred or thousand ohms.
Because a microcontroller's analog-to-digital converter (ADC) cannot measure resistance directly, we must convert this changing light sensor ohm value into a measurable voltage. We achieve this using a passive voltage divider circuit. By pairing the LDR with a fixed pull-down or pull-up resistor, the varying resistance creates a proportional voltage drop that the ESP32 or Arduino can sample, digitize, and mathematically convert back into ohms and eventually illuminance (lux).
Wiring the LDR: Voltage Divider and Pinout
Unlike digital I2C sensors like the BH1750 (which cost around $1.50 and output processed lux values directly), a standard GL5528 analog LDR costs roughly $0.10 but outputs a raw analog resistance. To interface this with an ESP32 DevKit V1, we must build a voltage divider. The choice of your fixed resistor dictates the sensitive range of your circuit. Since the GL5528 has a resistance of roughly 8kΩ to 20kΩ at 10 lux (typical indoor room lighting), a 10kΩ fixed resistor is the optimal choice to maximize voltage swing in that specific range.
The ESP32's ADC operates on a 3.3V logic level. While you can power the voltage divider with 5V on an Arduino Uno, you must use the 3.3V pin on the ESP32. Feeding 5V into an ESP32 GPIO will permanently damage the silicon. Furthermore, the ESP32 ADC is notoriously non-linear above 2.5V, so keeping the supply at 3.3V and using a 10kΩ pull-down keeps the output voltage safely within the linear reading zone for most indoor lighting conditions.
| Component Pin | ESP32 GPIO | Notes & Supply Range |
|---|---|---|
| LDR Leg 1 | 3V3 (Supply) | Supply range: 3.3V DC (Max 3.6V) |
| LDR Leg 2 & 10kΩ Resistor Leg 1 | GPIO 34 (ADC1_CH6) | Analog input. No internal pull-up/down. |
| 10kΩ Resistor Leg 2 | GND | Common ground reference. |
The Math: Converting Raw ADC to Ohms and Lux
Reading the sensor requires a three-step mathematical translation: converting the raw ADC integer to a voltage, converting that voltage back to the light sensor ohm value, and finally applying an empirical logarithmic curve to estimate lux. According to the Adafruit Photoresistor Guide, CdS cells follow a predictable log-log relationship between resistance and illuminance.
Step 1: Raw ADC to Voltage
The ESP32 features a 12-bit ADC, yielding raw values from 0 to 4095. The voltage is calculated as: V_out = ADC_raw * (3.3 / 4095.0).
Step 2: Voltage to Ohms
Using the voltage divider formula where the LDR is connected to VCC and the fixed resistor ($R_{fixed}$) is connected to GND, we solve for $R_{ldr}$:
R_ldr = R_fixed * ((V_in / V_out) - 1.0)
Step 3: Ohms to Lux
The standard approximation for a GL5528 is Lux = 500 / (R_ldr_in_kOhms) raised to a gamma factor (typically ~1.4 for this specific chemical composition). Below is the complete, compilable Arduino-framework code for the ESP32.
#define LDR_PIN 34
#define V_IN 3.3
#define ADC_MAX 4095.0
#define R_FIXED 10000.0 // 10k Ohm pull-down resistor
void setup() {
Serial.begin(115200);
analogReadResolution(12); // Ensure 12-bit resolution on ESP32
}
void loop() {
int raw_adc = analogRead(LDR_PIN);
// Prevent division by zero in dark conditions
if (raw_adc <= 5) raw_adc = 5;
// Step 1: ADC to Voltage
float v_out = (raw_adc / ADC_MAX) * V_IN;
// Step 2: Voltage to Light Sensor Ohm value
float r_ldr = R_FIXED * ((V_IN / v_out) - 1.0);
float r_ldr_k = r_ldr / 1000.0;
// Step 3: Ohms to Lux (Empirical GL5528 approximation)
float lux = pow(500.0 / r_ldr_k, 1.4);
Serial.printf("Raw: %d | Voltage: %.2fV | Ohms: %.0f | Lux: %.1f\n",
raw_adc, v_out, r_ldr, lux);
delay(500);
}
Real-World Interference and Calibration
While the math above provides a functional baseline, real-world bench testing reveals three major interference sources that distort light sensor ohm readings:
- ESP32 ADC Non-Linearity: As documented in the Espressif ESP32 ADC Documentation, the ADC has a known deadzone near 0V (roughly 0-100mV) and saturates non-linearly above 2.5V. If your LDR is in pitch blackness, the raw reading might hover around 50 instead of 0, and the calculated ohms will artificially cap out around 2MΩ instead of the true 10MΩ dark resistance. For high-precision dark measurements, you must use an external I2C ADC like the ADS1115.
- AC Mains Flicker (100/120Hz): Incandescent and cheap LED bulbs powered by AC mains actually flash on and off 100 or 120 times per second. Because CdS photoresistors have a relatively slow response time (typically 20ms to 50ms), they naturally average out this flicker. However, if you sample the ADC too quickly in a polling loop without a capacitor, you may see minor oscillations. Adding a 100nF ceramic capacitor in parallel with the fixed resistor acts as a low-pass filter, smoothing the voltage.
- Spectral Mismatch and Temperature Drift: CdS sensors peak in sensitivity around 520nm (green light), which roughly matches the human eye but differs vastly from the sun's full spectrum or IR-heavy heat lamps. Furthermore, the resistance of CdS material drifts by roughly 0.5% per degree Celsius. If your LDR is mounted near a hot voltage regulator or in direct, baking sunlight, your baseline ohm readings will skew low, resulting in artificially high lux calculations.
Frequently Asked Questions
Why does my light sensor ohm reading fluctuate under indoor LEDs?
Modern dimmable LEDs use Pulse Width Modulation (PWM) to control brightness, often switching at frequencies between 1kHz and 3kHz. While CdS LDRs are generally slow to react, high-frequency PWM can cause the sensor's internal capacitance to interact with the rapid voltage changes, resulting in a fluctuating ohm reading. To fix this, place a 1µF electrolytic capacitor across the fixed resistor in your voltage divider to physically average the voltage before it reaches the microcontroller's ADC pin.
Can I measure a light sensor ohm value directly with a microcontroller GPIO?
No. Microcontroller GPIO pins and ADCs measure electrical potential difference (voltage), not resistance (ohms). To measure resistance, you must pass a known current through the component and measure the resulting voltage drop, which is exactly what a voltage divider does. Attempting to read an LDR directly connected to a GPIO pin without a pull-down or pull-up resistor will result in a floating pin, yielding random, unusable ADC noise.
What is the typical dark resistance vs light resistance for a GL5528?
For a standard 5mm GL5528 photoresistor, the "dark resistance" (measured after 5 seconds in complete darkness at <0.01 lux) is typically 1MΩ. The "light resistance" (measured at 10 lux, roughly equivalent to twilight or a dimly lit room) is between 8kΩ and 20kΩ. Under direct, bright sunlight (approx. 100,000 lux), the resistance will drop to roughly 1kΩ or lower. Always check your specific manufacturer's datasheet, as cheap clones often exhibit dark resistances as low as 200kΩ due to impurities in the CdS doping process.
How do I calibrate a light sensor ohm output to accurate lux?
The empirical formula provided in the code block is an approximation based on average GL5528 manufacturing batches. To calibrate your specific sensor, you need a reference commercial lux meter. Place the LDR and the commercial lux meter side-by-side under three distinct light sources (e.g., a dark room, a desk lamp, and outdoor shade). Record the actual lux and your calculated LDR ohms for each. Plot the log(lux) vs log(ohms) on a graph; the slope of this line gives you the exact gamma ($\gamma$) value for your specific component, which you can then substitute into the pow() function in your code for precise scaling.






