Overview

The BH1750 is a digital ambient light sensor that measures light intensity in lux units. Unlike analog sensors, it uses I2C communication to provide precise, calibrated readings directly to your Arduino without additional calculations.

On the Vumbua Synapse Kit, the BH1750 is pre-connected to the I2C bus at address 0x5C, ready for you to start measuring light levels instantly.

Key Features of BH1750

• High precision: 1-65535 lux measurement range
• Low power consumption: 0.12mA in active mode
• I2C digital interface (no ADC needed)
• Built-in 16-bit AD converter
• Smallest measurement resolution: 1 lux
• Wide spectral response: Close to human eye response

Live Light Intensity

--
Measuring...

Prerequisites

Before starting, make sure you have completed:

Completed Lessons
  • Lesson 1: Blinking LED (Basic Output)
  • Lesson 2: Push Button Input (Digital Input)
  • Lesson 3: Buzzer Sound Generation
  • Lesson 4: Potentiometer (Analog Input)
  • Understanding of Serial Monitor
Hardware Requirements
  • Vumbua Synapse Kit with BH1750 sensor
  • USB Type-C cable
  • Computer with Arduino IDE
  • Various light sources for testing
On Vumbua Synapse Kit

The BH1750 is pre-connected to the I2C bus on the Vumbua board. I2C pins on ATmega328P: SDA (A4) and SCL (A5). Address is set to 0x5C via ADDR pin pull-up.

Learning Objectives

By the end of this lesson, you will be able to:

  • Understand I2C communication protocol fundamentals
  • Use the BH1750 library for light sensing
  • Read ambient light intensity in lux units
  • Configure different measurement modes
  • Create smart lighting automation systems
  • Calibrate sensor for different environments
  • Display light data on Serial Monitor
  • Implement daylight harvesting systems
  • Understand ATmega328P TWI (I2C) implementation

What is BH1750?

Digital Ambient Light Sensor

The BH1750 is a digital light sensor that converts light intensity directly to digital data using an integrated 16-bit analog-to-digital converter. It outputs values in lux - the SI unit of illuminance.

Lux represents the amount of light that falls on a surface. One lux equals one lumen per square meter.

How It Works

Internally, the BH1750 consists of:

  • Photodiode: Converts light to electrical current
  • Current-to-Voltage Converter: Amplifies the signal
  • 16-bit ADC: Digitizes the analog signal
  • I2C Interface: Communicates with microcontroller
  • Digital Signal Processor: Processes the data
Technical Specifications
Supply Voltage 2.4V - 3.6V (3.3V on Vumbua)
I2C Voltage 1.8V - 3.6V
Measurement Range 1 - 65535 lux
Resolution 1 lux (at minimum)
Accuracy ±20% (typical)
Operating Current 120μA (typical)
Interface I2C-bus (Fast Mode 400kHz)
Human Eye Response

Spectral response close to human eye sensitivity

Built-in 16-bit ADC

High-resolution digital conversion

Low Power

0.12mA active, 1μA power-down

I2C Interface

Standard 2-wire communication

Understanding I2C Protocol

I2C (Inter-Integrated Circuit) is a serial communication protocol that allows multiple devices to communicate using just two wires. It's essential for communicating with the BH1750 sensor.

I2C Communication Basics
ATmega328P
Master Device
SDA (Serial Data)
SCL (Serial Clock)
BH1750
Slave Device (0x5C)
I2C Wires

SDA (Serial Data): Bidirectional data line. Carries data between devices.
SCL (Serial Clock): Clock signal generated by master. Synchronizes data transfer.

Communication Flow
  1. Master initiates START condition
  2. Master sends slave address (0x5C)
  3. Slave acknowledges with ACK
  4. Data transfer occurs
  5. Master sends STOP condition
I2C Pull-up Resistors

I2C lines require pull-up resistors (typically 4.7kΩ) to keep them HIGH when idle. On Vumbua Synapse Kit, these resistors are already included on the board, so you don't need to add them externally.

BH1750 Pin Connections on Vumbua

The BH1750 module has 5 pins, but only 4 are used on Vumbua Synapse Kit:

VCC
Power supply (3.3V)
3.3V
GND
Ground
GND
SCL
I2C Clock line
ATmega328P A5
SDA
I2C Data line
ATmega328P A4
ADDR
I2C Address select (Pulled HIGH)
Address: 0x5C
Important: 3.3V Operation

The BH1750 operates at 3.3V, not 5V! The Vumbua board includes a 3.3V regulator and level shifting for safe operation. Never connect BH1750 directly to 5V pins.

I2C Address: 0x5C

Each I2C device has a unique address. The BH1750 can have two addresses depending on the ADDR pin:

Address Configuration
  • ADDR = LOW (0V): Address = 0x23
  • ADDR = HIGH (3.3V): Address = 0x5C

On Vumbua Synapse Kit, ADDR is pulled HIGH via resistor, so the address is 0x5C.

Finding I2C Address

Use I2C scanner code to detect connected devices:

View Scanner Code

I2C Address Format

I2C addresses are 7-bit values (0-127). The BH1750 address 0x5C in binary is:

0x5C = 0b1011100 = Decimal: 92

When sending the address on I2C, it's shifted left by 1 bit, and the LSB indicates read/write mode (0=write, 1=read).

Using BH1750 Library

The easiest way to use BH1750 is with the dedicated Arduino library:

1. Install BH1750 Library

In Arduino IDE:

  1. Go to Tools → Manage Libraries...
  2. Search for "BH1750"
  3. Install "BH1750" by Christopher Laws
  4. Alternatively, install "BH1750FVI"
2. Library Functions

Key functions provided by the library:

  • BH1750.begin(mode, addr) - Initialize sensor
  • BH1750.readLightLevel() - Read light in lux
  • BH1750.configure(mode) - Change measurement mode
3. Include Library

Start your sketch with:

#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;

I2C Scanner Code

Use this code to scan for I2C devices and verify BH1750 is detected at 0x5C:

i2c_scanner.ino
/*
  I2C Scanner for Vumbua Synapse Kit
  Scan I2C bus and detect connected devices
*/

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("\nI2C Scanner for Vumbua Synapse Kit");
  Serial.println("=================================");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  Serial.println("Scanning I2C bus...");

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      Serial.println("!");

      // Check if it's BH1750
      if (address == 0x23) {
        Serial.println("  → BH1750 (ADDR = LOW)");
      } else if (address == 0x5C) {
        Serial.println("  → BH1750 (ADDR = HIGH) ✓ Vumbua Kit");
      }

      nDevices++;
    }
  }

  if (nDevices == 0) {
    Serial.println("No I2C devices found. Check connections!");
  } else {
    Serial.println("Scan complete.");
  }

  Serial.println();
  delay(5000);
}

Basic Light Reading

Let's start with a simple program to read and display light intensity:

bh1750_basic.ino
/*
  Vumbua Synapse Kit - Lesson 5: Basic BH1750 Light Sensor
  Read ambient light intensity in lux units
  BH1750 I2C address: 0x5C
*/

#include <Wire.h>
#include <BH1750.h>

// Create BH1750 object with address 0x5C
BH1750 lightMeter(0x5C);

void setup() {
  Serial.begin(9600);
  
  // Initialize I2C bus (Wire)
  Wire.begin();
  
  // Initialize BH1750 sensor
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
    Serial.println("BH1750 Light Sensor Initialized Successfully!");
  } else {
    Serial.println("Error: BH1750 not detected. Check connections.");
    while (1); // Stop if sensor not found
  }
  
  Serial.println("Vumbua Synapse Kit - Light Intensity Monitor");
  Serial.println("============================================");
  Serial.println("Move sensor to different light conditions");
  Serial.println();
}

void loop() {
  // Read light level in lux
  float lux = lightMeter.readLightLevel();
  
  // Determine light condition
  String condition = getLightCondition(lux);
  
  // Display results
  Serial.print("Light Intensity: ");
  Serial.print(lux);
  Serial.print(" lux (");
  Serial.print(condition);
  Serial.println(")");
  
  // Wait 1 second before next reading
  delay(1000);
}

// Function to categorize light levels
String getLightCondition(float lux) {
  if (lux < 10) {
    return "Dark";
  } else if (lux < 50) {
    return "Dim";
  } else if (lux < 200) {
    return "Normal Indoor";
  } else if (lux < 1000) {
    return "Bright Indoor";
  } else if (lux < 10000) {
    return "Daylight";
  } else {
    return "Direct Sunlight";
  }
}

Testing the Program

1. Upload the Code

Upload to your Vumbua Synapse Kit. The BH1750 is already connected to the I2C bus at address 0x5C.

2. Open Serial Monitor

Go to Tools → Serial Monitor or press Ctrl+Shift+M. Set the baud rate to 9600.

You should see initialization message and continuous light readings.

3. Test Different Light Conditions

Try these experiments:

  • Cover sensor with your hand (should read below 10 lux)
  • Normal room lighting (200-500 lux)
  • Near a lamp (1000-2000 lux)
  • Near window in daylight (5000-10000 lux)
  • Direct sunlight (up to 100,000 lux but sensor max is 65535)
Light Level Reference Guide
Dark: < 10 lux
Moonless night
Dim: 10-50 lux
Night with moonlight
Normal Indoor: 50-200 lux
Living room lighting
Bright Indoor: 200-1000 lux
Office lighting
Daylight: 1000-10000 lux
Overcast daylight
Direct Sun: 10000-65535 lux
Full sunlight

BH1750 Measurement Modes

The BH1750 supports 6 different measurement modes for various applications:

Mode Resolution Measurement Time Power Usage Best For
CONTINUOUS_HIGH_RES 1 lux 120ms 120μA General purpose, indoor
CONTINUOUS_HIGH_RES_2 0.5 lux 120ms 120μA High precision indoor
CONTINUOUS_LOW_RES 4 lux 16ms 120μA Fast response, bright light
ONE_TIME_HIGH_RES 1 lux 120ms 0.1μA* Battery-powered, sleep mode
ONE_TIME_HIGH_RES_2 0.5 lux 120ms 0.1μA* Battery, high precision
ONE_TIME_LOW_RES 4 lux 16ms 0.1μA* Battery, fast reading

* Power-down current between measurements

Mode Selection Example

mode_selection.ino
/*
  BH1750 Mode Selection Demo
  Show different measurement modes
*/

#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter(0x5C);

void setup() {
  Serial.begin(9600);
  Wire.begin();
  
  Serial.println("BH1750 Mode Comparison");
  Serial.println("======================");
}

void loop() {
  // Test CONTINUOUS_HIGH_RES mode
  lightMeter.configure(BH1750::CONTINUOUS_HIGH_RES_MODE);
  delay(120); // Wait for measurement
  float luxHighRes = lightMeter.readLightLevel();
  
  // Test CONTINUOUS_LOW_RES mode
  lightMeter.configure(BH1750::CONTINUOUS_LOW_RES_MODE);
  delay(16);
  float luxLowRes = lightMeter.readLightLevel();
  
  // Test ONE_TIME_HIGH_RES mode
  lightMeter.configure(BH1750::ONE_TIME_HIGH_RES_MODE);
  delay(120);
  float luxOneTime = lightMeter.readLightLevel();
  
  // Display comparison
  Serial.print("HIGH_RES (1 lux): ");
  Serial.print(luxHighRes);
  Serial.print(" lux | LOW_RES (4 lux): ");
  Serial.print(luxLowRes);
  Serial.print(" lux | ONE_TIME: ");
  Serial.print(luxOneTime);
  Serial.println(" lux");
  
  delay(2000);
}
Choosing the Right Mode

Continuous modes are best for real-time monitoring. One-time modes are ideal for battery-powered applications because the sensor goes to sleep between measurements, saving power.

Sensor Calibration & Accuracy

While BH1750 is factory-calibrated, you can improve accuracy for your specific application:

1. Calibration Method

Compare BH1750 readings with a reference lux meter or known light sources:

  • Standard candle at 1 meter: ~1 lux
  • 60W incandescent bulb at 1 meter: ~100 lux
  • Direct sunlight: 10,000-100,000 lux
  • Overcast daylight: 1,000-2,000 lux
2. Calibration Factor

Apply a correction factor to your readings:

corrected_lux = measured_lux × calibration_factor

Typical factory calibration factor is 1.2, but can vary.

3. Averaging for Stability

Take multiple readings and average them to reduce noise:

float getAverageLux(int samples) {
  float sum = 0;
  for(int i = 0; i < samples; i++) {
    sum += lightMeter.readLightLevel();
    delay(10);
  }
  return sum / samples;
}

Calibration Code Example

calibration.ino
/*
  BH1750 Calibration Example
  Apply calibration factor for improved accuracy
*/

#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter(0x5C);

// Calibration factor - adjust based on reference measurement
const float CALIBRATION_FACTOR = 1.2;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
  
  Serial.println("BH1750 Calibrated Measurement");
  Serial.println("Calibration Factor: " + String(CALIBRATION_FACTOR));
}

float getStableLux() {
  // Take 5 readings and average
  float sum = 0;
  for (int i = 0; i < 5; i++) {
    sum += lightMeter.readLightLevel();
    delay(50);
  }
  return sum / 5.0;
}

void loop() {
  // Get raw measurement
  float rawLux = getStableLux();
  
  // Apply calibration
  float calibratedLux = rawLux * CALIBRATION_FACTOR;
  
  // Display results
  Serial.print("Raw: ");
  Serial.print(rawLux);
  Serial.print(" lux | Calibrated: ");
  Serial.print(calibratedLux);
  Serial.println(" lux");
  
  delay(1000);
}

Smart Lighting Automation

Now let's create a practical application - automatic LED control based on ambient light:

smart_lighting.ino
/*
  Vumbua Synapse Kit - Smart Lighting Automation
  Control LED brightness based on ambient light
  Automatic night light / daylight harvesting
*/

#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter(0x5C);

// LED connected to PWM pin 9
const int ledPin = 9;

// Light thresholds (in lux)
const int DARK_THRESHOLD = 50;    // Below this: LED ON full
const int BRIGHT_THRESHOLD = 200;  // Above this: LED OFF

void setup() {
  Serial.begin(9600);
  Wire.begin();
  
  // Initialize sensor
  lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
  
  // Initialize LED pin
  pinMode(ledPin, OUTPUT;
  
  Serial.println("Smart Lighting System Active");
  Serial.println("============================");
  Serial.println("DARK (<50 lux): LED ON");
  Serial.println("DIM (50-200 lux): LED proportional");
  Serial.println("BRIGHT (>200 lux): LED OFF");
  Serial.println();
}

void loop() {
  // Read ambient light level
  float lux = lightMeter.readLightLevel();
  
  int ledBrightness = 0;
  
  if (lux <= DARK_THRESHOLD) {
    // Dark: LED at full brightness
    ledBrightness = 255;
  } else if (lux >= BRIGHT_THRESHOLD) {
    // Bright: LED off
    ledBrightness = 0;
  } else {
    // Dim: Proportional brightness
    // Map lux value (50-200) to brightness (255-0)
    ledBrightness = map(lux, DARK_THRESHOLD, BRIGHT_THRESHOLD, 255, 0);
    ledBrightness = constrain(ledBrightness, 0, 255);
  }
  
  // Control LED
  analogWrite(ledPin, ledBrightness);
  
  // Calculate LED brightness percentage
  int ledPercent = map(ledBrightness, 0, 255, 0, 100);
  
  // Display status
  Serial.print("Light: ");
  Serial.print(lux);
  Serial.print(" lux | LED: ");
  Serial.print(ledBrightness);
  Serial.print(" (");
  Serial.print(ledPercent);
  Serial.println("%)");
  
  delay(500);
}
Ambient Light
--
LED Brightness
--
Advanced Automation Features

Enhance your smart lighting system with these features:

  • Hysteresis: Prevent rapid switching at threshold boundaries
  • Scheduling: Combine with real-time clock for time-based control
  • Remote Control: Add Bluetooth/WiFi for smartphone control
  • Energy Monitoring: Track energy savings from automation
  • Multi-zone Control: Control different areas independently

Real-World Applications

Home Automation
  • Automatic blinds/curtains control
  • Smart garden lighting
  • Energy-efficient room lighting
  • Security light automation
  • Wake-up light simulation
Industrial & Commercial
  • Daylight harvesting in offices
  • Greenhouse light control
  • Photography light metering
  • Display case lighting
  • Warehouse aisle lighting
Automotive
  • Automatic headlight control
  • Dashboard brightness adjustment
  • Tunnel detection for lights
  • Rain/light sensor integration
Consumer Electronics
  • Smartphone screen brightness
  • Laptop display adjustment
  • Camera exposure control
  • E-reader frontlight control

Project: Plant Light Monitor

plant_light_monitor.ino
/*
  Plant Light Monitor
  Monitor if plants are receiving adequate light
*/

#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter(0x5C);

// Plant light requirements (lux)
const int PLANT_MIN_LIGHT = 1000;  // Minimum for healthy growth
const int PLANT_MAX_LIGHT = 10000; // Maximum before damage

// LED indicators
const int LED_TOO_DARK = 2;
const int LED_GOOD = 3;
const int LED_TOO_BRIGHT = 4;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
  
  // Setup LED pins
  pinMode(LED_TOO_DARK, OUTPUT);
  pinMode(LED_GOOD, OUTPUT);
  pinMode(LED_TOO_BRIGHT, OUTPUT);
  
  Serial.println("Plant Light Monitor Active");
  Serial.println("<1000 lux: Too dark (Red LED)");
  Serial.println("1000-10000 lux: Good (Green LED)");
  Serial.println(">10000 lux: Too bright (Yellow LED)");
}

void loop() {
  float lux = lightMeter.readLightLevel();
  
  // Reset all LEDs
  digitalWrite(LED_TOO_DARK, LOW);
  digitalWrite(LED_GOOD, LOW);
  digitalWrite(LED_TOO_BRIGHT, LOW);
  
  // Control LEDs based on light level
  if (lux < PLANT_MIN_LIGHT) {
    digitalWrite(LED_TOO_DARK, HIGH);
  } else if (lux > PLANT_MAX_LIGHT) {
    digitalWrite(LED_TOO_BRIGHT, HIGH);
  } else {
    digitalWrite(LED_GOOD, HIGH);
  }
  
  // Display recommendation
  Serial.print("Light: ");
  Serial.print(lux);
  Serial.print(" lux - ");
  
  if (lux < PLANT_MIN_LIGHT) {
    Serial.println("Move plant to brighter location");
  } else if (lux > PLANT_MAX_LIGHT) {
    Serial.println("Provide some shade for plant");
  } else {
    Serial.println("Perfect light conditions! ✓");
  }
  
  delay(2000);
}

ATmega328P I2C (TWI) Implementation

The ATmega328P implements I2C through its TWI (Two-Wire Interface) hardware module:

TWI Hardware Features
  • Dual-purpose pins: PC4 (SDA) and PC5 (SCL)
  • Multi-master support: Multiple masters on same bus
  • 7-bit addressing: Supports 128 different addresses
  • Clock stretching: Slaves can slow down communication
  • Interrupt-driven: Can operate without CPU attention
Clock Speed Configuration

I2C clock speed is set by the TWI Bit Rate Register (TWBR):

SCL Frequency = CPU Clock / (16 + 2(TWBR) × Prescaler)

For 16MHz Arduino with 100kHz I2C: TWBR = 72

Arduino Wire Library

The Arduino Wire.h library abstracts TWI hardware:

  • Wire.begin() - Initialize I2C as master
  • Wire.beginTransmission(addr) - Start communication
  • Wire.write(data) - Send data
  • Wire.endTransmission() - End transmission
  • Wire.requestFrom(addr, bytes) - Request data
  • Wire.read() - Read received data
Direct Register Access

Advanced users can access TWI registers directly for maximum control:

  • TWBR: Bit Rate Register
  • TWCR: Control Register
  • TWSR: Status Register
  • TWDR: Data Register
  • TWAR: Address Register (for slave mode)

Troubleshooting Common Issues

Common BH1750 Problems

  • Solution 1: Run I2C scanner to check address (0x5C)
  • Solution 2: Check connections - SDA to A4, SCL to A5
  • Solution 3: Verify 3.3V power supply to BH1750
  • Solution 4: Add Wire.begin() before sensor init
  • Solution 5: Try alternative address 0x23 if ADDR is LOW

  • Check: Light sensor is not covered or damaged
  • Test: Shine flashlight directly on sensor
  • Verify: Correct I2C address in code (should be 0x5C for Vumbua)
  • Consider: Sensor may be in wrong measurement mode
  • Try: Reset sensor with power cycle

  • Solution 1: Use averaging function (sample 5-10 readings)
  • Solution 2: Ensure stable light source
  • Solution 3: Avoid fluorescent lights (they flicker at 50/60Hz)
  • Solution 4: Check for electrical noise near sensor
  • Solution 5: Use HIGH_RES mode instead of LOW_RES for stability

  • Calibrate: Use known light source for calibration
  • Check: Sensor viewing angle (should face light source)
  • Verify: No shadows or reflections affecting sensor
  • Consider: Different light colors affect readings
  • Test: Compare with smartphone light meter app

Advanced Debugging: Raw I2C Communication

// Manual I2C communication with BH1750
void readBH1750Manual() {
  // Power on command
  Wire.beginTransmission(0x5C);
  Wire.write(0x01); // Power on
  Wire.endTransmission();
  delay(10);
  
  // Set measurement mode
  Wire.beginTransmission(0x5C);
  Wire.write(0x10); // Continuously H-Resolution mode
  Wire.endTransmission();
  delay(120); // Wait for measurement
  
  // Read 2 bytes
  Wire.requestFrom(0x5C, 2);
  if(Wire.available() == 2) {
    uint16_t raw = Wire.read();
    raw <<= 8;
    raw |= Wire.read();
    float lux = raw / 1.2;
    Serial.print("Raw I2C: ");
    Serial.print(lux);
    Serial.println(" lux");
  }
}

Video Demonstration

Light Sensor Video Tutorial

Watch our comprehensive video guide on using the BH1750 light sensor.

Learn setup, calibration, and advanced applications with live demonstrations.

Watch on OLSPACE YouTube

What's Next?

Congratulations! You've successfully mastered digital light sensing with BH1750. Here's what you've accomplished:

  • ✓ Learned I2C communication protocol fundamentals
  • ✓ Used BH1750 library for precise light measurement
  • ✓ Mastered different measurement modes for various applications
  • ✓ Created smart lighting automation systems
  • ✓ Implemented sensor calibration for accuracy
  • ✓ Built practical projects like plant light monitor
  • ✓ Understood ATmega328P TWI (I2C) implementation

In the next lesson, you'll explore environmental sensing:

Lesson 6: Temperature & Humidity (DHT20)

Learn to measure temperature and humidity using the DHT20 sensor with I2C communication. Create weather stations, climate control systems, and environmental monitoring projects that track both temperature and relative humidity simultaneously.

Resources & Downloads