If you are building an automated greenhouse, a smart streetlamp, or a display dimmer, you need to measure ambient light. But when you ask "how does a light sensor work," the answer depends entirely on the semiconductor physics inside the package. A bare photoresistor behaves fundamentally differently than a calibrated digital lux meter, and treating them as interchangeable will wreck your microcontroller's readings.
This guide breaks down the sensing principles, output signal types, and raw-to-unit math for the three most common light sensors on the maker bench: the Cadmium Sulfide LDR, the TEMT6000 phototransistor, and the BH1750FVI digital I2C sensor. We will wire them to an ESP32, convert raw ADC counts into physical lux values, and filter out the AC mains noise that ruins most beginner projects.
The Physics: How Does a Light Sensor Work?
At the silicon level, light sensors rely on the internal photoelectric effect. When photons with sufficient energy strike a semiconductor material, they excite electrons from the valence band into the conduction band. In passive sensors like Cadmium Sulfide (CdS) photoresistors, this increased charge carrier density lowers the material's electrical resistance. The brighter the light, the more conductive the path becomes. However, these passive materials are slow to react and highly sensitive to temperature drift.
Active sensors, like photodiodes and phototransistors, use a PN junction. Photons striking the depletion region generate a small photocurrent proportional to the light intensity. Modern digital sensors, such as the BH1750, take this a step further by integrating a photodiode, a transimpedance amplifier, and an analog-to-digital converter (ADC) onto a single die. This allows the chip to output a calibrated digital value via I2C, completely bypassing the microcontroller's noisy internal ADC.
Sensor Selection and Output Signals
Before writing any code, you must understand what the sensor's output actually is. You cannot plug a variable resistor directly into a GPIO pin and expect a reading; you must condition the signal. Below is a data-dense comparison of the three standard sensor classes to help you select the right part for your bill of materials.
| Sensor Type | Example Part | Output Signal Type | Supply Range | Lux Range | Typical Price (2026) |
|---|---|---|---|---|---|
| CdS Photoresistor | GL5528 | Analog (Variable Resistance) | N/A (Passive) | 10 - 10,000 lux | $0.10 |
| Phototransistor | TEMT6000 | Analog (Current Source) | 2.5V - 5.5V | 1 - 1,000 lux | $0.85 |
| Digital I2C Lux IC | BH1750FVI | Digital (16-bit I2C Bytes) | 2.4V - 3.6V | 1 - 65,535 lux | $1.50 |
| High-Dynamic I2C IC | TSL2591 | Digital (Dual-Diode I2C) | 2.7V - 3.6V | 0.00018 - 88,000 lux | $4.50 |
Wiring Pinouts and Raw-to-Unit Math
The ESP32 DevKit V1 features a 12-bit ADC (0-4095 raw counts) and hardware I2C. Below is the unified wiring table for interfacing all three sensor types to a single 3.3V ESP32 board.
| ESP32 Pin | BH1750FVI (Digital) | TEMT6000 (Analog) | GL5528 LDR (Divider) |
|---|---|---|---|
| 3V3 | VCC | VCC (via 10kΩ to collector) | VCC (via LDR) |
| GND | GND, ADDR | GND (via 10kΩ pull-down) | GND (via 10kΩ fixed resistor) |
| GPIO 21 (SDA) | SDA (add 4.7kΩ pull-up) | - | - |
| GPIO 22 (SCL) | SCL (add 4.7kΩ pull-up) | - | - |
| GPIO 34 (ADC1_CH6) | - | Signal (across 10kΩ) | Signal (midpoint of divider) |
Converting Raw Signals to Physical Lux
Getting a number from the microcontroller is only step one. You must apply output signal math to convert that raw reading into a physical unit (lux). According to the SparkFun TEMT6000 Hookup Guide, the phototransistor passes roughly 20 µA at 1000 lux.
1. TEMT6000 Math (Current to Voltage to Lux):
With a 10,000Ω pull-down resistor on GPIO 34, the voltage at the pin is V_out = I_photo * 10,000. The ESP32 12-bit ADC reads this voltage. Assuming a 3.3V reference:
Voltage = (Raw_ADC / 4095.0) * 3.3Current_uA = (Voltage / 10000.0) * 1,000,000Lux = Current_uA * 2.0(Approximation based on Vishay datasheet slope)
2. BH1750 Math (Digital Bytes to Lux):
The BH1750 handles the ADC internally. As detailed in the Adafruit BH1750 Overview, the sensor returns two bytes (High and Low). The raw-to-unit math is handled in software:
Raw_16bit = (High_Byte << 8) | Low_ByteLux = Raw_16bit / 1.2
3. GL5528 LDR Math (Voltage Divider):
LDRs do not output lux linearly. You first calculate the resistance of the LDR using the voltage divider formula: R_ldr = R_fixed * ((V_cc / V_out) - 1). To get lux, you must map this resistance against the specific manufacturer's log-log curve, usually requiring a polynomial regression in your code.
Calibration, Interference, and Real-World Gotchas
Theoretical math rarely survives the workbench. When deploying light sensors in the real world, you will encounter three major interference sources that require hardware or software mitigation.
1. AC Mains Flicker (100Hz/120Hz Ripple)
Incandescent, fluorescent, and cheap LED bulbs do not emit steady light; they pulse at twice the AC mains frequency (120Hz in North America, 100Hz in Europe). If your ESP32 samples the TEMT6000 at the wrong millisecond, your lux reading will swing wildly. The Fix: Add a 10µF electrolytic capacitor in parallel with your 10kΩ pull-down resistor to create a low-pass hardware filter, or implement a software moving-average filter sampling at least 20 times per AC cycle.
2. ESP32 ADC Non-Linearity
The ESP32's internal ADC is notoriously non-linear at the extremes of its range. Readings below 0.1V (approx. 100 raw counts) and above 3.1V (approx. 3800 raw counts) are highly inaccurate and prone to noise. As noted in the Espressif ADC API Documentation, you must configure the ADC attenuation correctly. The Fix: Set your ADC attenuation to ADC_11db for the full 0-3.3V range, and design your pull-down resistors so your expected operating voltage sits squarely in the 0.5V to 2.5V sweet spot.
3. Infrared (IR) Contamination and Cosine Response
Silicon photodiodes are naturally sensitive to infrared light. If you place a TEMT6000 near a heat source or in direct sunlight, the IR spectrum will artificially inflate your lux reading, as human eyes (and standard lux definitions) do not perceive IR. Furthermore, light sensors suffer from cosine response errors; light hitting the sensor at a 60-degree angle will read significantly lower than light hitting it dead-on. The Fix: For precise indoor applications, use the BH1750, which features an integrated IR-rejection filter and a built-in cosine correction lens. If using a bare TEMT6000, mount it inside a 3D-printed shroud with a PTFE (Teflon) diffuser cap to scatter light evenly and block off-axis IR bleed.






