How Photosensors Convert Light to Electrical Signals
Photosensors rely on the photoelectric effect or photoconductivity to translate photon strikes into measurable electrical signals. In analog phototransistors like the Vishay TEMT6000, incoming light hits the base-collector junction, generating electron-hole pairs that act as base current. This allows a proportional collector current to flow through an external load resistor, creating a variable voltage drop. In digital ambient light ICs like the Rohm BH1750FVI, an integrated photodiode array feeds an internal transimpedance amplifier and a precision ADC, outputting a pre-scaled binary lux value directly over an I2C bus.
The critical distinction for embedded builders is the output signal format. Analog photosensors output a continuous voltage (or current) that requires your microcontroller's ADC to digitize, meaning you must handle scaling, reference voltages, and noise filtering in software. Digital photosensors handle the analog-to-digital conversion internally, outputting discrete integer data. Conflating these two architectures—such as wiring an analog phototransistor output to a digital I2C pin, or expecting a raw voltage from a digital IC—will result in unreadable buses or damaged GPIO pins.
Hardware Specs and ESP32 Wiring Pinouts
Before wiring your breadboard, you must select the right sensor for your environment. Basic Cadmium Sulfide (CdS) LDRs like the GL5528 are cheap but suffer from severe memory effects and slow response times. Modern embedded designs default to silicon phototransistors or dedicated I2C ICs.
| Part Number | Type | Supply Range | Output Type | Spectral Peak | Lux Range | Approx Cost |
|---|---|---|---|---|---|---|
| GL5528 | CdS LDR | N/A (Passive) | Variable Resistance | 540 nm (Green) | 10 - 10,000 | $0.15 |
| TEMT6000 | NPN Phototransistor | 2.5V - 5.5V | Analog Voltage | 570 nm (Yellow-Green) | 1 - 1,000 | $1.20 |
| BH1750FVI | Digital I2C IC | 2.4V - 3.6V | Digital (I2C) | 470 nm (Blue-Green) | 1 - 65,535 | $1.85 |
| TSL2591 | High-Dynamic I2C | 2.7V - 3.6V | Digital (I2C) | Broadband (IR+Vis) | 0.0001 - 88,000 | $4.50 |
When interfacing with a 3.3V microcontroller like the ESP32 DevKit v1, you must respect the logic levels and supply ranges. The BH1750 is strictly a 3.3V device; feeding it 5V will destroy the internal I2C pull-ups. The TEMT6000 can tolerate 5V, but if you power it at 5V, you must use a voltage divider on the analog output to prevent feeding 5V into the ESP32's 3.3V-tolerant ADC pins.
| Sensor Pin | TEMT6000 (Analog) | BH1750FVI (Digital I2C) | Notes & Constraints |
|---|---|---|---|
| VCC / VIN | ESP32 3V3 | ESP32 3V3 | Keep TEMT6000 at 3.3V to match ADC max input. |
| GND | ESP32 GND | ESP32 GND | Ensure common ground plane; avoid daisy-chaining grounds. |
| OUT / SDA | ESP32 GPIO 34 (ADC1_CH6) | ESP32 GPIO 21 (SDA) | GPIO 34 is input-only on ESP32. No internal pull-up needed. |
| N/C / SCL | Not Connected | ESP32 GPIO 22 (SCL) | Add 4.7kΩ external pull-ups to 3.3V on SDA/SCL lines. |
| ADDR | Not Connected | GND (or VCC) | Tie to GND for I2C address 0x23; tie to VCC for 0x5C. |
Raw ADC to Lux Math and Calibration
A raw ADC reading is useless for physical decision-making. You must convert the microcontroller's integer output into standard SI units (Lux). The math diverges entirely based on whether your photosensor outputs a voltage or a digital register value.
Analog Output Math (TEMT6000)
The TEMT6000 outputs a variable voltage based on the current flowing through the phototransistor and the onboard load resistor. According to the Vishay TEMT6000 datasheet, the typical light current responsivity is 0.02 µA per Lux.
Assuming a standard breakout board with a 10,000Ω (10kΩ) load resistor and a 3.3V supply:
- Calculate Voltage per Lux: $V_{out} = I_{current} \times R_{load}$. At 1 Lux, $I = 0.02 \mu A$. $V_{out} = (0.02 \times 10^{-6}) \times 10,000 = 0.0002V$ (or 0.2mV) per Lux.
- Invert for Lux: $\text{Lux} = \frac{V_{out}}{0.0002} = V_{out} \times 5000$.
- Convert to Millivolts: If your microcontroller reads in millivolts (mV), $\text{Lux} = \frac{mV}{1000} \times 5000 = mV \times 5$.
The ESP32's ADC is notoriously non-linear, particularly in the 0-0.1V and 3.1-3.3V ranges. Never use the raw analogRead() 12-bit integer (0-4095) with a simple voltage divider formula. Instead, use the ESP-IDF or Arduino core analogReadMilliVolts() function. This function reads the factory-calibrated eFuse data on your specific ESP32 silicon to correct the non-linear curve, returning a highly accurate millivolt reading.
// ESP32 Arduino Core Implementation
int adcPin = 34;
int millivolts = analogReadMilliVolts(adcPin);
float lux = millivolts * 5.0;
Serial.printf("Light Level: %.1f Lux\n", lux);
Digital Output Math (BH1750FVI)
The BH1750 handles the transimpedance amplification and ADC conversion internally. It outputs a 16-bit unsigned integer via I2C. However, the raw register value is not exactly Lux; it requires a scaling factor dictated by the Rohm BH1750FVI datasheet.
The standard resolution mode (Measurement Time = 69ms) requires dividing the raw I2C payload by 1.2 to yield true Lux.
// Wire.h I2C Read Implementation
Wire.requestFrom(0x23, 2);
uint16_t rawVal = (Wire.read() << 8) | Wire.read();
float lux = rawVal / 1.2;
Interference Sources and Signal Conditioning
Photosensors do not operate in a vacuum. Real-world environments introduce optical and electrical noise that will corrupt your readings if left unaddressed.
| Interference Type | Affected Sensors | Physical Cause | Mitigation Strategy |
|---|---|---|---|
| 50/60Hz Mains Flicker | All (Analog worse) | AC driven LEDs and fluorescents pulse at 100/120Hz. | Software: Average 20 samples over 20ms. Hardware: Increase BH1750 integration time. |
| IR Bleed (Sunlight) | TEMT6000, LDRs | Silicon phototransistors are naturally sensitive to near-IR (800-950nm). | Use sensors with integrated IR-rejection filters (BH1750) or add an external IR-cut optical filter. |
| Analog Trace EMI | TEMT6000 | High-impedance analog traces act as antennas for switching regulators and WiFi antennas. | Keep analog wires under 2 inches. Use twisted-pair wiring. Add a 100nF ceramic capacitor between OUT and GND. |
| I2C Bus Capacitance | BH1750, TSL2591 | Long wires add parasitic capacitance, rounding the I2C square waves and causing NACK errors. | Use 4.7kΩ pull-ups for runs <10cm. Drop to 2.2kΩ pull-ups for runs up to 30cm. Keep SDA/SCL away from motor leads. |
Handling the ESP32 ADC Deadzone
If your TEMT6000 is reading exactly 0 Lux in a dimly lit room, you have likely hit the ESP32 ADC deadzone. The ESP32 ADC cannot accurately resolve voltages below ~75mV. Because the TEMT6000 outputs 0.2mV per Lux, anything below ~375 Lux will read as 0 on the ESP32. If your application requires dim-light sensing (e.g., a nightlight trigger at 50 Lux), the TEMT6000 paired with an ESP32 is the wrong hardware choice. Switch to the BH1750, which can resolve down to 1 Lux digitally, or amplify the TEMT6000 signal using an op-amp circuit before feeding it to the microcontroller.






