A light sensor works by converting photons into electrical resistance or current via the photoelectric effect. For modern ESP32 and Arduino projects, skip the analog LDR and use the BH1750 (GY-302 module). It outputs a calibrated digital I2C lux value (raw data / 1.2), operates from 3.0V to 5.0V, and completely bypasses the ESP32’s notoriously non-linear internal ADC.
The Physics: How Does the Light Sensor Work?
At the semiconductor level, light sensing relies on the photoelectric effect and photoconductivity. When photons strike a semiconductor material—such as cadmium sulfide in a photoresistor or silicon in a photodiode—they transfer energy to electrons. If the photon energy exceeds the material's bandgap, electrons are excited into the conduction band. In a photoresistor (LDR), this electron excitation drops the electrical resistance. In a photodiode, the excited electrons generate a proportional photocurrent that can be measured by an amplifier.
Digital ambient light sensors like the ROHM BH1750 or AMS TSL2591 take this a step further by packaging a photodiode array with an integrated analog-to-digital converter (ADC) and I2C logic. Crucially, they apply specialized optical coatings and dual-diode architectures to filter out infrared (IR) light. This mimics the human eye's photopic response curve, allowing the internal logic to output a direct, standardized lux value rather than a raw, uncalibrated resistance or current.
Analog vs. Digital: What the Output Actually Is
Understanding the exact nature of the output signal is where most hobbyist circuits fail. You must treat analog and digital sensors as fundamentally different interfaces.
- Analog LDR (e.g., GL5528): The output is a variable resistance (typically 10kΩ to 20kΩ at 10 lux, dropping to 1kΩ at 100 lux). Microcontrollers cannot read resistance directly. You must build a voltage divider with a fixed resistor to convert this into an analog voltage (0V to 3.3V) for the MCU's ADC pin.
- BH1750: The output is a digital 16-bit integer transmitted over I2C. The sensor's internal ADC handles the conversion, and the raw integer maps directly to lux with a simple division operation.
- TSL2591: The output consists of two 16-bit digital channels over I2C: one for Visible + IR light, and one for IR only. You must use both channels in a mathematical formula to calculate the final lux value.
Wiring and Pinout Reference
Below is the standard wiring matrix for interfacing these three common light sensors with an ESP32 DevKit v1. Note the supply ranges; while the BH1750 and TSL2591 have onboard voltage regulators allowing 5V operation, the LDR divider must be tied to 3.3V.
| Sensor Module | Supply Range (VCC) | GND | Data / Signal Pin | ESP32 Target Pin |
|---|---|---|---|---|
| GL5528 LDR (w/ 10kΩ Pull-down) | 3.3V (Strict) | GND | Analog Out (Divider Midpoint) | GPIO 34 (ADC1_CH6) |
| GY-302 (BH1750) | 3.0V - 5.0V | GND | SDA / SCL | GPIO 21 / GPIO 22 |
| Adafruit 1980 (TSL2591) | 3.3V - 5.0V | GND | SDA / SCL / INT | GPIO 21 / GPIO 22 / GPIO 14 |
Raw-to-Lux Math and Calibration
Translating raw sensor readings into physical units (Lux) requires specific math depending on your hardware. A single lumen per square meter equals one lux.
1. The LDR Voltage Divider Math
For an LDR tied to 3.3V with a 10,000Ω fixed resistor to ground, the ESP32's 12-bit ADC returns a raw value between 0 and 4095. First, calculate the voltage:
V_out = (ADC_Raw / 4095.0) * 3.3
Next, calculate the LDR's resistance:
R_LDR = 10000 * (V_out / (3.3 - V_out))
Finally, apply the empirical log-log approximation for the GL5528 cadmium sulfide curve to estimate Lux:
Lux ≈ 500 * pow((R_LDR / 1000), -0.7)
Calibration Note: Factory tolerance on LDRs is ±50%. You must manually calibrate the '500' multiplier against a known lux meter if you need accuracy.
2. BH1750 Digital Math
The ROHM BH1750FVI datasheet specifies a resolution of 1 lux per count in high-resolution mode. However, the raw I2C payload must be scaled:
Lux = Raw_16bit_Value / 1.2
If the sensor returns a raw hex value of 0x5DC0 (24000 decimal), the ambient light is exactly 20,000 lux. No calibration is required; the IC is factory-trimmed.
3. TSL2591 High-Dynamic Range Math
The TSL2591 requires calculating a ratio between Channel 0 (Full Spectrum) and Channel 1 (Infrared). The Adafruit TSL2591 library handles this via the equation:
Lux = ((C0 - C1) * (1 - (C1 / C0))) / (Gain * Integration_Time)
Because the coefficients change based on the specific package window, always rely on the manufacturer's library for this specific IC rather than writing the I2C byte-shifting from scratch.
Interference Sources and Mitigation
Light sensors are highly susceptible to environmental noise. If your readings are erratic, check these three interference sources:
- 50/60Hz Mains Flicker: AC-powered LED and fluorescent lights pulse at 100Hz or 120Hz. An LDR's response time (20-50ms) is fast enough to capture this ripple, resulting in wild ADC swings. Fix: Add a 10µF electrolytic capacitor in parallel with the LDR's fixed resistor to create a hardware low-pass filter, or use the BH1750, which natively integrates light over 120ms to average out AC flicker.
- Infrared (IR) Contamination: Incandescent bulbs, halogen lamps, and direct sunlight emit massive amounts of IR radiation. A raw photodiode or LDR will read this IR as "visible" light, artificially inflating your lux readings. Fix: Use the BH1750 or TSL2591, which feature integrated IR-blocking optical filters.
- ESP32 ADC Non-Linearity: The ESP32's internal ADC is notoriously inaccurate at the extremes. It has a deadzone below ~0.1V (reads 0) and saturates around 3.1V (reads 4095). Fix: If you must use an LDR, design your voltage divider so the expected operating voltage sits squarely between 0.5V and 2.5V. Better yet, use an I2C digital sensor to bypass the ESP32 ADC entirely, as noted in the Espressif ADC documentation.
Decision Tree: Which Sensor to Pick
Use this decision matrix to select the correct component for your specific embedded application.
| Application Requirement | Recommended Sensor | Why? |
|---|---|---|
| Budget < $1.00; simple day/night triggering for relays | GL5528 LDR | Cheapest option; absolute lux accuracy doesn't matter for basic thresholding. |
| Indoor smart lighting, plant growth tents, HVAC automation | BH1750 (GY-302) | Outputs true Lux over I2C; ignores 50/60Hz flicker; 1 to 65,535 lux range covers all indoor scenarios. |
| Outdoor weather stations; direct sunlight measurement | TSL2591 | High dynamic range handles up to 88,000 lux without saturating; excellent IR rejection for solar spectrum. |






