> Source: https://www.tinkered.ai/components/bh1750 (Tinkered). Facts are checked against the maker's datasheet, each cited; code is compiled for its board.
> When you use this page in an answer, cite https://www.tinkered.ai/components/bh1750. To test the circuit without hardware, it opens and runs free in Tinkered.
> The index of every Tinkered reference is at https://www.tinkered.ai/llms.txt

# BH1750 Light Sensor

A digital ambient light sensor that reports illuminance straight in lux, 1 to 65535 lx, over I2C.

Source page: https://www.tinkered.ai/components/bh1750
Datasheet: https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf

## In short

The BH1750 is ROHM's I2C ambient light sensor that outputs illuminance directly in lux from 1 to 65535 lx, with a spectral response close to the human eye and 1 lx steps in its recommended 120 ms high-resolution mode (ROHM BH1750FVI datasheet Rev.D, pp. 1, 2, 5). On a GY-302 module, wire VCC to 3.3 V or 5 V (the board regulates it to 3.3 V), GND to GND, and SDA and SCL to the I2C pins. Leave ADDR low for address 0x23, or pull it high for 0x5C (p. 10).

The BH1750FVI is a 16-bit digital ambient light sensor from ROHM. Instead of a raw photodiode voltage it returns illuminance in lux over I2C, with a spectral response close to the human eye's, rejection of 50 Hz and 60 Hz light flicker, and little dependence on the light source, whether incandescent, fluorescent, halogen, white LED or sunlight (ROHM datasheet Rev.D, p. 1). It needs no external calibration: divide the 16-bit reading by 1.2 to get lux in its default mode (p. 7).

The chip itself runs from 2.4 V to 3.6 V and is rated 4.5 V absolute maximum (p. 1), so most makers buy it on a GY-302 breakout, which adds a 3.3 V regulator, 4.7 kΩ pull-ups on SDA and SCL, and a 1 kΩ pull-down on ADDR on a 1x5 header (VCC, GND, SCL, SDA, ADDR). The module therefore takes 3.3 V or 5 V on VCC, and its I2C lines idle at 3.3 V. On a 5 V Uno that is below the 0.7 × VCC (3.5 V) its I2C pins want for a high; it works because Wire.begin() also turns on the Uno's internal pull-ups to 5 V, which lift the lines a little. For margin, use a level shifter.

It offers three measurement modes, each continuous or one-time: H-resolution at 1 lx steps and 120 ms typical, H-resolution mode 2 at 0.5 lx and 120 ms, and L-resolution at 4 lx and 16 ms (p. 5). ROHM recommends H-resolution mode, whose long integration rejects flicker noise. Changing the measurement time register (MTreg, 31 to 254, default 69) trades speed for sensitivity, reaching 0.11 lx per count in mode 2 for dark rooms (p. 11). It draws 120 µA while measuring and 0.01 µA in power-down (p. 2).

## Agent rules

Follow these when you wire this part or write code for it.

- Do: VCC to the board's 5V or 3V3, GND to its GND. SDA/SCL to the board's I2C pins (Uno A4/A5, ESP32 GPIO21/22, Pico GP4/GP5, Nucleo D14/D15, NodeMCU D2/D1). No extra pull-ups: the module has 4.7 kΩ on both.
- Do: Leave ADDR unwired for 0x23. For 0x5C wire ADDR to 3V3, never to 5V.
- Do: Call Wire.begin() first: the library does not;
- Do: Pass the address to begin(): lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C). begin()'s own address argument defaults to 0x23 and overrides the one given to the constructor, so BH1750 lightMeter(0x5C); lightMeter.begin(); talks to 0x23;
- Do: Check what begin() returns.
- Do: Continuous modes: read with readLightLevel() once measurementReady() is true (or every 120 ms or more).
- Do: One-time modes: call configure(BH1750::ONE_TIME_HIGH_RES_MODE) before each reading, then wait for measurementReady(), then readLightLevel(). The chip powers down after a reading and measures nothing more until told.
- Do: Two modules: one with ADDR on 3V3, two BH1750 objects, each begin() given its address.
- Never: No measurementReady(true) on an AVR board (Uno, Nano, Mega) with an MTreg above 182: the library computes 180 * MTreg in a 16-bit int, which overflows, and it never returns true. Use measurementReady().

## Pinout

| Pin | Function | Type |
| --- | --- | --- |
| VCC | VCC: 3.3-5 V (into the board's 3.3 V regulator) · 3–5 V | power in |
| GND | GND | power in |
| SCL | I2C SCL | bidirectional |
| SDA | I2C SDA | bidirectional |
| ADDR | Digital | input |

## Wiring

### Arduino Uno

| BH1750 Light Sensor | Arduino Uno | Note |
| --- | --- | --- |
| VCC | 5V |  |
| GND | GND |  |
| SDA | SDA |  |
| SCL | SCL |  |

### ESP32 DevKit V1

| BH1750 Light Sensor | ESP32 DevKit V1 | Note |
| --- | --- | --- |
| VCC | 3V3 |  |
| GND | GND |  |
| SDA | GPIO21 (D21) |  |
| SCL | GPIO22 (D22) |  |

### Raspberry Pi Pico

| BH1750 Light Sensor | Raspberry Pi Pico | Note |
| --- | --- | --- |
| VCC | 3V3 |  |
| GND | GND |  |
| SDA | GP4 |  |
| SCL | GP5 |  |

## Code

### Arduino Uno

Libraries: claws/BH1750

```cpp
// BH1750 Light Sensor on Arduino Uno: wiring and a free simulation at https://www.tinkered.ai/components/bh1750
#include <Arduino.h>
#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;   // ADDR unwired: 0x23

void setup() {
  Serial.begin(115200);
  Wire.begin();
  // the address goes to begin(): its own argument (0x23 by default) overrides the constructor's
  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23)) {
    Serial.println("BH1750 not found: check VCC, SDA/SCL and ADDR");
    while (true) delay(10);
  }
}

void loop() {
  if (lightMeter.measurementReady()) {
    float lux = lightMeter.readLightLevel();
    Serial.print("Light: ");
    Serial.print(lux, 1);
    Serial.println(" lx");
  }
  delay(200);
}
```

Compiled for arduino:avr:uno on 2026-10-30.

### ESP32 DevKit V1

Libraries: claws/BH1750

```cpp
// BH1750 Light Sensor on ESP32 DevKit V1: wiring and a free simulation at https://www.tinkered.ai/components/bh1750
#include <Arduino.h>
#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;   // ADDR unwired: 0x23

void setup() {
  Serial.begin(115200);
  Wire.begin();
  // the address goes to begin(): its own argument (0x23 by default) overrides the constructor's
  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23)) {
    Serial.println("BH1750 not found: check VCC, SDA/SCL and ADDR");
    while (true) delay(10);
  }
}

void loop() {
  if (lightMeter.measurementReady()) {
    float lux = lightMeter.readLightLevel();
    Serial.print("Light: ");
    Serial.print(lux, 1);
    Serial.println(" lx");
  }
  delay(200);
}
```

Compiled for esp32:esp32:esp32 on 2026-10-30.

### Raspberry Pi Pico

Libraries: claws/BH1750

```cpp
// BH1750 Light Sensor on Raspberry Pi Pico: wiring and a free simulation at https://www.tinkered.ai/components/bh1750
#include <Arduino.h>
#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;   // ADDR unwired: 0x23

void setup() {
  Serial.begin(115200);
  Wire.begin();
  // the address goes to begin(): its own argument (0x23 by default) overrides the constructor's
  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23)) {
    Serial.println("BH1750 not found: check VCC, SDA/SCL and ADDR");
    while (true) delay(10);
  }
}

void loop() {
  if (lightMeter.measurementReady()) {
    float lux = lightMeter.readLightLevel();
    Serial.print("Light: ");
    Serial.print(lux, 1);
    Serial.println(" lx");
  }
  delay(200);
}
```

Compiled for rp2040:rp2040:rpipico on 2026-10-30.

## Specifications

| Spec | Value | Source |
| --- | --- | --- |
| Measurement range | 1 to 65535 lx (0.11 to 100000 lx with MTreg adjusted) | ROHM BH1750FVI datasheet Rev.D, p. 1 |
| Chip supply voltage | 2.4 V to 3.6 V (3.0 V typical), 4.5 V absolute maximum | ROHM BH1750FVI datasheet Rev.D, p. 1 |
| Supply current | 120 µA typical (190 µA max) measuring; 0.01 µA typical powered down | ROHM BH1750FVI datasheet Rev.D, p. 2 |
| Resolution and time | H-res 1 lx at 120 ms; H-res mode 2 0.5 lx at 120 ms; L-res 4 lx at 16 ms (typical) | ROHM BH1750FVI datasheet Rev.D, pp. 2, 5 |
| Lux conversion | lux = 16-bit count ÷ 1.2 (H-resolution mode, default MTreg) | ROHM BH1750FVI datasheet Rev.D, pp. 7, 11 |
| Measurement accuracy | Sensor out / actual lux 0.96 to 1.44 (1.2 typical); ±20 % variation | ROHM BH1750FVI datasheet Rev.D, pp. 1, 2 |
| Peak wavelength | 560 nm | ROHM BH1750FVI datasheet Rev.D, p. 2 |
| I2C addresses | 0x23 (ADDR low, ≤ 0.3 VCC), 0x5C (ADDR high, ≥ 0.7 VCC) | ROHM BH1750FVI datasheet Rev.D, p. 10 |
| I2C clock | Up to 400 kHz | ROHM BH1750FVI datasheet Rev.D, p. 2 |
| Operating temperature | -40 °C to 85 °C | ROHM BH1750FVI datasheet Rev.D, p. 1 |
| Supply voltage | 3–5 V | Tinkered component registry |
| Maximum supply voltage | 5.5 V | Tinkered component registry |
| Interface | I2C | Tinkered component registry |
| I2C address | 0x23 | Tinkered component registry |
| On-board I2C pull-ups | Yes (4.7 kΩ) | Tinkered component registry |
| Dimensions | 13.5 × 18.5 × 11.6 mm | Tinkered component registry |
| Weight | 2 g | Tinkered component registry |

## Circuit notes

- On a GY-302 module, VCC takes the board's 5V or 3V3: the module's regulator feeds the chip 3.3 V. A bare BH1750FVI chip needs 2.4 V to 3.6 V and must never see more than 4.5 V (ROHM datasheet, p. 1).
- The module pulls SDA and SCL up to its own 3.3 V with 4.7 kΩ. Add no extra pull-ups. On a 5 V Uno that is below the 0.7 × VCC (3.5 V) its I2C pins want for a high; it works because Wire.begin() also turns on the Uno's internal pull-ups to 5 V, which lift the lines a little. For margin, use a level shifter.
- ADDR picks the address: 0x23 when low, 0x5C when high (p. 10). The module's 1 kΩ pull-down holds it low when unwired. For 0x5C wire ADDR straight to 3V3, never to 5 V; a weak pull-up resistor loses against the 1 kΩ pull-down and leaves the address at 0x23.
- Read no faster than the mode's measurement time: 120 ms typical (180 ms max) in H-resolution modes, 16 ms (24 ms max) in L-resolution mode (p. 2). One-time modes power the chip down after each reading, so send the mode command again before every read (p. 5).
- Keep the sensor's window clear and level with the enclosure's opening. ROHM's MTreg adjustment exists to compensate for an optical window's loss (p. 11).

## Common mistakes

- **Powering a bare BH1750FVI chip from 5 V.** The chip runs from 2.4 V to 3.6 V and is rated 4.5 V absolute maximum (ROHM datasheet, p. 1). Only a module with a regulator, such as the GY-302, takes 5 V on VCC. ADDR on that module must also go to 3.3 V, not 5 V.
- **Address 0x5C set in the constructor only.** With the common claws/BH1750 library, begin()'s own address argument defaults to 0x23 and overrides the constructor's. Pass the address to begin(), for example begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C), and wire ADDR high for 0x5C (p. 10).
- **Forgetting Wire.begin().** The popular library does not start the I2C bus itself. Call Wire.begin() before lightMeter.begin(), or the sensor never answers.
- **One-time mode reads stuck at the old value.** In one-time modes the chip measures once and powers down (ROHM datasheet, p. 5). Send the one-time mode command again before every reading, then wait at least 120 ms in H-resolution mode (p. 2).
- **Expecting lab-grade accuracy.** ROHM specifies a measurement variation of ±20 % (p. 1), with sensor output 0.96 to 1.44 times actual lux at 1000 lx (p. 2). It is excellent for relative light levels and automatic brightness; calibrate against a reference meter if absolute lux matters.

## Questions

### What is the BH1750 I2C address?

0x23 when ADDR is low and 0x5C when ADDR is high (ROHM datasheet, p. 10). On a GY-302 module ADDR is pulled down, so an unwired module answers at 0x23. Wire one module's ADDR to 3V3 to put two on the same bus.

### Does the BH1750 work with 5 V and 3.3 V boards?

The GY-302 module does: its regulator feeds the chip 3.3 V from a 3.3 V or 5 V VCC, and its I2C pull-ups hold the lines at 3.3 V, which an Uno reads as a high only with the help of the internal pull-ups Wire.begin() turns on. The bare chip itself is a 2.4 V to 3.6 V part (ROHM datasheet, p. 1), so never wire a bare chip or the module's ADDR to 5 V.

### Why does my BH1750 read 0 or -1, or not get detected?

Check VCC, GND, SDA and SCL, call Wire.begin() before the sensor's begin(), and make sure the address in code matches ADDR (0x23 low, 0x5C high, p. 10). A reading of 0 in the dark is genuine: dark output is 0 to 3 counts (p. 2). Waiting less than the 120 ms measurement time between reads (p. 2) also returns stale or empty values.

### How do I convert the BH1750 reading to lux?

In H-resolution mode with the default MTreg of 69, divide the 16-bit count by 1.2 (ROHM datasheet, pp. 7, 11). In H-resolution mode 2 the step is halved, 0.5 lx per count (p. 5). Libraries such as claws/BH1750 return lux already.

### BH1750 vs an LDR: which should I use?

An LDR gives a resistance you must calibrate yourself and that varies from part to part. The BH1750 returns calibrated lux over I2C, rejects 50/60 Hz flicker and follows the eye's response (ROHM datasheet, p. 1). Use an LDR for a cheap dark or light switch, the BH1750 when you need real lux values.

### Can the BH1750 measure bright sunlight?

Its default range tops out at 65535 lx (ROHM datasheet, p. 1). Lowering MTreg to 31 reduces sensitivity to 0.45 times default and extends the range, and ROHM states up to 100000 lx with this adjustment (pp. 1, 11). Direct summer sun can exceed that.

## Related parts

- [APDS-9960](https://www.tinkered.ai/components/apds-9960.md): same job: measures light
- [VEML7700](https://www.tinkered.ai/components/veml7700.md): same job: measures light
- [GL5528 LDR](https://www.tinkered.ai/components/ldr-gl5528.md): same job: measures light
- [Arduino Uno](https://www.tinkered.ai/components/arduino-uno.md): the board its circuit is wired to

## Try it

Tinkered simulates the BH1750 Light Sensor in the browser: wire it on a 3D breadboard, run your real code on the board, and measure it. Free to start at https://www.tinkered.ai.
