Reading a typical light sensor description in a datasheet often leaves makers wondering why a $0.50 photoresistor and a $5.00 digital I2C chip can both claim to "measure ambient light." The reality is that lux measurement is highly dependent on the sensing element, the internal signal conditioning, and how the sensor handles invisible infrared (IR) radiation. This guide cuts through the marketing copy to give you the exact wiring, raw-to-unit math, and a concrete decision tree to select the right sensor for your next Arduino or ESP32 build.

The Physics: How Ambient Light Sensors Actually Work

Analog light dependent resistors (LDRs), typically made of cadmium sulfide (CdS), operate on the principle of photoconductivity. When photons strike the CdS material, they excite electrons into the conduction band, causing the sensor's electrical resistance to drop inversely with light intensity. While cheap and simple, CdS cells are notoriously slow to react (tens of milliseconds) and contain toxic heavy metals, making them less ideal for modern precision electronics.

Digital ambient light sensors (ALS) like the BH1750FVI and TSL2591 use silicon photodiodes based on a PN junction. Incoming light generates a proportional photocurrent, which an internal integrating ADC converts into a digital value over a specific time window. Crucially, these digital ICs feature dual photodiodes—one sensitive to visible light and one to infrared—allowing the internal logic to subtract the IR component and output a highly accurate, human-eye-calibrated lux value directly via I2C.

Analog vs. Digital: What the Output Actually Is

The most common mistake when interfacing light sensors is conflating analog resistance with digital lux registers. Here is exactly what comes out of the signal pin:

  • LDR (Analog): The output is purely a variable resistance. To read this with a microcontroller, you must build a voltage divider circuit with a fixed resistor to convert the resistance change into a 0–3.3V analog signal. Your MCU's ADC then reads this voltage as a raw integer (e.g., 0–4095 on a 12-bit ESP32 ADC).
  • BH1750 / TSL2591 (Digital): The output is a 16-bit digital integer transmitted over the I2C bus. This raw number represents the sensor's internal count, which you then divide by a scaling factor to arrive at physical lux.
⚠️ Common Interference Sources:
  • 60Hz Mains Flicker: AC-powered LEDs and fluorescents pulse at 100/120Hz. Raw analog LDRs will pick up this ripple, causing erratic readings unless you add a heavy RC low-pass filter (e.g., 10kΩ and 10µF) to smooth the voltage.
  • IR Bleed: Sunlight and incandescent bulbs emit massive amounts of infrared light. Cheap analog sensors and basic phototransistors cannot distinguish between visible and IR light, leading to wildly inflated "lux" readings outdoors. Digital sensors with active IR rejection (like the TSL2591) solve this mathematically.
  • EMI on Analog Lines: Running an analog LDR signal wire longer than 6 inches near AC mains or switching relays will induce noise. Keep analog traces short or switch to I2C digital sensors.

Wiring and Pinout Reference

Below is the hardware integration spec-sheet-table for the three most common maker light sensors. Always verify your specific breakout board's onboard voltage regulator; raw ICs have strict logic-level limits.

Sensor Model Interface VCC Supply Range Logic High Level I2C Address / Pin
TEMT6000 (Breakout) Analog Voltage 3.3V – 5.0V N/A (Analog 0-VCC) ADC Pin (e.g., A0)
BH1750FVI (GY-302) I2C Digital 3.0V – 5.0V Min 1.3V (SCL/SDA) 0x23 (ADDR=L) / 0x5C (ADDR=H)
TSL2591 (Adafruit 1980) I2C Digital 3.3V – 5.0V Min 2.1V (3.3V logic safe) 0x29 (Fixed)

Numbered Wiring Steps for I2C Sensors (BH1750/TSL2591):

  1. Power: Connect sensor VCC to the ESP32/Arduino 3.3V pin. While 5V tolerant on most breakout boards, 3.3V ensures the I2C pull-ups don't exceed the MCU's GPIO limits.
  2. Ground: Connect sensor GND to MCU GND. Ensure a solid common ground to prevent I2C bus lockups.
  3. Data (SDA): Connect sensor SDA to MCU SDA (ESP32 GPIO 21, Arduino Uno A4).
  4. Clock (SCL): Connect sensor SCL to MCU SCL (ESP32 GPIO 22, Arduino Uno A5).
  5. Pull-ups: If your breakout board lacks them (check for 4.7kΩ resistors near the pins), add 4.7kΩ external pull-up resistors from SDA and SCL to 3.3V.

Raw-to-Lux Math: Scaling and Calibration

You cannot use the raw ADC or I2C register values directly in home automation logic (like turning on lights at < 50 lux). You must apply scaling math.

1. BH1750FVI Math

The BH1750 operates in different resolution modes. In the default Continuous High-Resolution Mode (120ms integration time), the sensor outputs a 16-bit value where each count represents 1.2 lux.

// Raw I2C read yields a 16-bit integer (e.g., raw_counts = 25000)
float lux = raw_counts / 1.2;
// Example: 25000 / 1.2 = 20,833 lux (Direct sunlight)

Calibration Note: If you place a glass or plastic diffuser over the sensor, you must multiply the final lux value by a transmission compensation factor (usually between 1.1 and 1.5, determined empirically with a reference lux meter).

2. TSL2591 Math (High Dynamic Range)

The TSL2591 uses two channels: CH0 (Full Spectrum: Visible + IR) and CH1 (IR only). The math subtracts the IR component to approximate human eye response. The Adafruit TSL2591 Library handles this via the following internal logic:

// Counts Per Lux (CPL) depends on integration time and gain
float CPL = (integration_time_ms * gain_factor) / 408.0;

// Lux calculation (simplified)
float lux = ((channel0 - channel1) * (1.0 - (channel1 / channel0)) * 53.0) / CPL;

3. Analog LDR (TEMT6000 / CdS) Math

Because LDRs are non-linear, converting voltage to lux requires a Steinhart-Hart style logarithmic curve or a lookup table. For a standard voltage divider (10kΩ fixed resistor to ground, LDR to VCC):

int adc_raw = analogRead(A0);
float voltage = adc_raw * (3.3 / 4095.0); // ESP32 12-bit
float resistance_ldr = 10000.0 * (voltage / (3.3 - voltage));
// Approximate Lux (highly dependent on specific LDR part number)
float lux = 500.0 / pow(resistance_ldr / 1000.0, 1.4); 

Decision Tree: Which Light Sensor Should You Buy?

Do not default to the cheapest part on the BOM. Use this decision-tree-table to select the exact component for your application constraints.

Application Requirement Constraint / Environment Recommended Sensor
Basic Day/Night Detection Just need to know if the sun is up or down; budget is < $1.00. TEMT6000 or standard 5mm CdS LDR
Indoor Smart Lighting / Screen Dimming Needs accurate human-eye lux mapping; indoor LED lighting; budget < $2.50. BH1750FVI (GY-302 Module)
Outdoor Weather Station / Grow Lights Exposed to direct sunlight (100k+ lux) and deep shadows; requires IR rejection. TSL2591
Ultra-Low Light (Starlight) Need to measure < 0.1 lux for astronomy or dark-sky compliance. TSL2591 (Max gain, 600ms integration)
🏆 The Default Pick: BH1750FVI (GY-302 Module)

If your project doesn't strictly require outdoor high-dynamic-range or sub-$0.10 pricing, buy the BH1750FVI. At roughly $1.50 to $2.00 per module, it provides 90% of the performance of the TSL2591 with a fraction of the software complexity. It natively rejects 50/60Hz flicker, outputs a linear lux value with a simple division by 1.2, and operates flawlessly on both 3.3V and 5V logic buses without level shifters. For indoor home automation, plant monitoring, and display dimming, it is the undisputed benchmark.

Sources: Rohm Semiconductor BH1750FVI Datasheet, Adafruit TSL2591 Learning Guide, Vishay TEMT6000 Optoelectronics Data Sheet.