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.

Table 1: Light Sensor Specifications and Output Types
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
Bench Note on Outputs: The GL5528 outputs resistance (requiring a voltage divider). The TEMT6000 outputs current (requiring a pull-down resistor to create a voltage). Only the BH1750 and TSL2591 output a true digital data stream. Never conflate analog voltage conditioning with digital protocol interfacing.

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.

Table 2: ESP32 Wiring and Supply Pinout
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.3
  • Current_uA = (Voltage / 10000.0) * 1,000,000
  • Lux = 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_Byte
  • Lux = 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.

Calibration Protocol: Never trust the raw datasheet multipliers for final production. Buy a $40 dedicated digital lux meter (or use a calibrated smartphone ambient light sensor app), place it directly next to your ESP32 sensor under your target lighting conditions, and record 10 data points. Use those points to generate a custom calibration multiplier in your firmware.