Embedded Development Sensor DIY in Practice: BH1750 Auto-Dimming + HX711 High-Precision Scale Build
Sensors are the most common and fun entry-level components in embedded development — they convert physical-world quantities like temperature, light, and pressure into electrical signals that an MCU can process. This article combines two classic sensor projects into one: BH1750 light sensor auto-dimming system and HX711 high-precision scale DIY. Together, the hardware costs less than ¥100, yet they cover core skills including I2C communication, 24-bit ADC, signal amplification, filtering, and calibration.
Project 1: BH1750 Light Sensor Auto-Dimming System
Why Auto-Dimming?
Fixed-brightness lighting is far from user-friendly. When ambient light changes, most people can’t be bothered to adjust manually — the result is either too bright (wasting power and glaring) or too dim (straining your eyes). A BH1750 auto-dimming system detects surrounding light intensity in real time and automatically adjusts LED or screen brightness. It’s ideal for smart desk lamps, monitor backlights, plant grow lights, automotive instrument lighting, and more.
Introduction to the BH1750 Sensor
The BH1750 is a digital ambient light sensor made by ROHM Semiconductor. Compared to analog photoresistors, it offers clear advantages:
| Feature | BH1750 | Photoresistor |
|---|---|---|
| Output Type | Digital I2C | Analog voltage |
| Measurement Range | 1–65535 Lux | Depends on circuit design |
| Accuracy | ±20% | Poor |
| Wavelength Response | Close to human eye | Inconsistent |
| Calibration | Factory calibrated | Requires manual calibration |
| Price | ~¥3–5 | ~¥0.5–1 |
Key specs: Operating voltage 3.0–3.6V (some modules with voltage regulator can accept 5V), I2C interface with default address 0x23 (switchable to 0x5C), 16-bit resolution, fastest response time 120ms.
Bill of Materials
| Component | Model | Qty | Unit Price | Total |
|---|---|---|---|---|
| Light Sensor | BH1750FVI module | 1 | ¥4.5 | ¥4.5 |
| Main Board | Arduino Nano | 1 | ¥12 | ¥12 |
| LED | 5mm white LED | 3 | ¥0.3 | ¥0.9 |
| Current-limiting Resistor | 220Ω | 3 | ¥0.1 | ¥0.3 |
| Breadboard | 400-hole | 1 | ¥5 | ¥5 |
| Jumper Wires | Male-to-male 20cm | 10 | ¥0.2 | ¥2 |
| Total | ¥24.7 |
Purchasing tip: Search “BH1750 module” on Taobao and choose a version with a voltage regulator and pull-up resistors for easier wiring.
Circuit Connections
The BH1750 uses I2C, requiring only 4 wires:
| BH1750 | Arduino Nano |
|---|---|
| VCC | 5V (module with voltage regulator) |
| GND | GND |
| SCL | A5 (I2C clock) |
| SDA | A4 (I2C data) |
LED connections (3 LEDs in parallel to D9 PWM pin): LED anode → 220Ω resistor → D9, LED cathode → GND.
BH1750 Arduino Nano
┌────────┐ ┌────────────┐
│ VCC │──────│ 5V │
│ GND │──────│ GND │
│ SCL │──────│ A5 │
│ SDA │──────│ A4 │
└────────┘ └────────────┘
LED (via 220Ω resistor)
┌────────┐ ┌────────────┐
│ + │──────│ D9 (PWM) │
│ - │──────│ GND │
└────────┘ └────────────┘
Code Implementation
Search for and install the Adafruit_BH1750 library in the Arduino IDE, or manually download it from GitHub claws/BH1750.
#include <Wire.h>
#include <BH1750.h>
#define LED_PIN 9
BH1750 lightSensor;
const int MIN_LUX = 50;
const int MAX_LUX = 1000;
const int PWM_MIN = 30;
const int PWM_MAX = 255;
float smoothedLux = 0;
const float ALPHA = 0.3; // Filter coefficient — lower = smoother
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
if (lightSensor.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23)) {
Serial.println(F("BH1750 init OK"));
} else {
Serial.println(F("BH1750 init failed, check wiring!"));
while (1);
}
lightSensor.setMeasurementTime(BH1750::MT_69MS);
}
void loop() {
if (lightSensor.hasValue()) {
float lux = lightSensor.readLightLevel();
smoothedLux = ALPHA * lux + (1 - ALPHA) * smoothedLux;
int pwmValue = calculatePWM(smoothedLux);
analogWrite(LED_PIN, pwmValue);
Serial.print("Light: ");
Serial.print(smoothedLux);
Serial.print(" Lux | PWM: ");
Serial.println(pwmValue);
}
delay(200);
}
int calculatePWM(float lux) {
lux = constrain(lux, MIN_LUX, MAX_LUX);
int pwm = map(lux, MIN_LUX, MAX_LUX, PWM_MIN, PWM_MAX);
return constrain(pwm, PWM_MIN, PWM_MAX);
}
Code highlights:
- I2C initialization:
lightSensor.begin()takes the measurement mode and address.CONTINUOUS_HIGH_RES_MODEprovides 1 Lux resolution. - Exponential moving average filter: The
ALPHAcoefficient balances response speed and stability, preventing LED flicker. - PWM mapping:
map()linearly maps Lux to the PWM range. Note that human brightness perception is logarithmic — advanced implementations can add gamma correction.
Troubleshooting: Sensor Returns 0 or a Fixed Value
Possible causes: SCL/SDA swapped, wrong I2C address, missing pull-up resistors. Use this I2C scanner sketch to confirm the address:
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin();
Serial.println("Scanning I2C...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Found device: 0x");
Serial.println(addr, HEX);
}
}
}
void loop() {}
Energy Savings
Compared to fixed-brightness lighting, auto-dimming can save 30–60% energy. For a 10W LED running 8 hours/day: fixed brightness uses 80Wh/day, while auto-dimming averages 5W for 40Wh/day — saving about 14.6 kWh/year (~¥8.8). An office with 100 lamps could save nearly ¥900 per year.
Real-World Use Cases
- Smart desk lamp: Automatically brightens during the day, dims at night, and maintains a minimum 30% brightness late at night to avoid going completely dark.
- Monitor backlight: Works with your computer, adjusting brightness based on room light to reduce eye strain and save 30–50% energy.
- Plant grow lights: Supplements natural light to maintain a constant light intensity for plants.
- Automotive instrument lighting: Automatically adjusts when entering/exiting tunnels, preventing sudden brightness changes that could distract the driver.
Smart Home Integration (MQTT)
If you swap the Arduino Nano for an ESP8266/ESP32, you can easily push light data to an MQTT server and integrate with Home Assistant or other smart home platforms:
void publishLux(float lux) {
char payload[10];
dtostrf(lux, 4, 1, payload);
mqttClient.publish("home/sensor/lux", payload);
}
This lets you monitor room lighting in real time from your phone and set automation rules (e.g., turn on lights when lux drops below 200).
Project 2: Building an HX711 High-Precision Scale
Why Choose the HX711?
A ¥30–50 kitchen scale on the market can only achieve 1g precision, while an HX711 + load cell setup can easily reach 0.1g or even 0.01g. The best part is the low cost: HX711 module ¥8, 50kg load cell ¥25, Arduino Nano ¥15 — total under ¥50.
Perfect for DIY coffee scales, jewelry scales, ingredient scales, smart warehouse weighing systems, and an excellent learning resource for ADC principles.
How the HX711 Works
The HX711 is a 24-bit ADC chip designed specifically for high-precision weighing. It integrates a low-noise programmable gain amplifier (PGA, selectable gain of 32/64/128x) and a 24-bit delta-sigma ADC (up to 80SPS).
Why 24-bit? Load cells output very weak millivolt-level signals (about 2mV/V at full scale). With a 5V supply, full-scale output is only 10mV — an Arduino’s 10-bit ADC would only resolve this into 2 steps, completely unusable. The HX711 divides 0–5V into 16.77 million steps, so 10mV maps to roughly 33,000 steps — a ten-thousand-fold improvement in precision.
Bill of Materials
| Component | Model | Price | Notes |
|---|---|---|---|
| HX711 Module | 24-bit ADC | ¥8 | Version with voltage regulator |
| Load Cell | 50kg cantilever | ¥25 | Choose 1kg–50kg by capacity |
| Main Board | Arduino Nano | ¥15 | Or ESP32 |
| OLED Display | 0.96” I2C | ¥12 | Shows weight |
| Jumper Wires | Male-to-female | ¥5 | |
| Breadboard | 400-hole | ¥8 | |
| Total | - | ¥73 | Lower in bulk |
Circuit Connections
HX711 module pins: VCC (5V/3.3V), GND, DT (data), SCK (clock). Load cells typically have 4 wires:
- Red wire: E+ (excitation positive)
- Black wire: E- (excitation negative)
- White wire: A+ (signal positive)
- Green wire: A- (signal negative)
Wiring steps:
- Connect the load cell’s four wires to the HX711 module’s E+, E-, A+, A- terminals.
- Connect HX711 VCC to Arduino 5V, GND to GND.
- Connect HX711 DT pin to Arduino D2 (data).
- Connect HX711 SCK pin to Arduino D3 (clock).
- Connect OLED display SDA to A4, SCL to A5 (sharing the I2C bus with the BH1750).
If your load cell wire colors differ, use a multimeter to measure resistance and identify them: E+/E- should be ~400Ω, A+/A- should be ~400Ω, other combinations ~300Ω.
Load Cell HX711 Arduino Nano
┌──────────┐ ┌──────────────┐ ┌────────────┐
│ E+(Red) │──────│ E+ │ │ │
│ E-(Blk) │──────│ E- │ │ │
│ A+(Wht) │──────│ A+ │ │ │
│ A-(Grn) │──────│ A- │ │ │
└──────────┘ │ VCC │ GND │──────│ 5V GND │
│ DT │ SCK │──────│ D2 D3 │
└──────────────┘ └────────────┘
Basic Weighing Code
#include "HX711.h"
#define DT_PIN 2
#define SCK_PIN 3
HX711 scale;
float calibration_factor = 420.0;
void setup() {
Serial.begin(9600);
Serial.println("HX711 Scale initializing...");
scale.begin(DT_PIN, SCK_PIN);
scale.set_scale(calibration_factor);
scale.tare();
Serial.println("Init complete, starting weighing...");
}
void loop() {
if (scale.is_ready()) {
float weight = scale.get_units(10); // Average of 10 readings
Serial.print("Weight: ");
Serial.print(weight, 2);
Serial.println(" g");
}
delay(500);
}
Complete Code with OLED Display
If you want a standalone weighing display without relying on a computer serial port, add a 0.96” OLED:
#include "HX711.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define DT_PIN 2
#define SCK_PIN 3
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
HX711 scale;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
float calibration_factor = 420.0;
float current_weight = 0;
void setup() {
Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init failed");
for (;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Scale starting up...");
display.display();
scale.begin(DT_PIN, SCK_PIN);
scale.set_scale(calibration_factor);
scale.tare();
delay(1000);
display.clearDisplay();
display.display();
}
void loop() {
if (scale.is_ready()) {
current_weight = scale.get_units(10);
display.clearDisplay();
display.setCursor(0, 0);
display.println("Current Weight:");
display.setTextSize(2);
display.setCursor(20, 25);
display.print(current_weight, 1);
display.print(" g");
display.display();
}
delay(300);
}
Calibration Procedure
Calibration is key to scale accuracy. You’ll need a weight of known mass (or an item with a known weight).
- Remove all items from the scale platform, power on and wait 5 seconds for warm-up.
- Serial output prompts “Remove all weights, preparing to tare”, wait 3 seconds to stabilize.
- Call
scale.tare()to complete zeroing. - Place a weight of known mass (e.g., 100g) on the platform.
- Enter the actual weight (in grams) via serial input.
- The code reads the raw value and calculates the calibration factor:
calibration_factor = raw_value / known_weight.
void calibrate() {
Serial.println("Remove all weights, preparing to tare...");
delay(3000);
scale.tare();
Serial.println("Place a known weight on the scale, enter actual weight (grams):");
while (Serial.available() == 0) {}
float known_weight = Serial.parseFloat();
long raw_value = scale.read_average(20);
calibration_factor = raw_value / known_weight;
Serial.print("Calibration factor: ");
Serial.println(calibration_factor);
}
Calibration tips: Calibrate with multiple different weights and average the results for better accuracy; don’t move the sensor after calibration; temperature changes affect precision — temperature compensation is needed for critical applications.
Troubleshooting
Readings always 0: Check that VCC has 5V, DT/SCK pins are correct, and load cell wire order isn’t wrong.
Readings fluctuate wildly: Power the HX711 separately to avoid sharing with motors; increase averaging with scale.get_units(20); use software exponential filtering:
float filtered = 0;
float alpha = 0.3;
void loop() {
float raw = scale.get_units(5);
filtered = alpha * raw + (1 - alpha) * filtered;
Serial.println(filtered, 2);
}
Not precise enough: Use a higher-quality sensor (stainless steel is better than aluminum alloy); ensure stable power supply with low ripple; increase sample averaging; keep away from vibration sources and secure the mechanical mounting.
Severe drift: Let the system warm up for 5 minutes before use; auto-tare periodically (every 10 seconds); choose a better-quality sensor.
Advanced: Tare Button and Multi-Range
#define TARE_BUTTON 4
void loop() {
if (digitalRead(TARE_BUTTON) == LOW) {
scale.tare();
delay(500); // Debounce
}
float weight = scale.get_units(10);
if (weight < 10) Serial.print(weight, 3); // <10g: 3 decimal places
else if (weight < 100) Serial.print(weight, 2); // <100g: 2 decimal places
else Serial.print(weight, 1); // Otherwise: 1 decimal place
Serial.println(" g");
}
Logging Data to an SD Card
If you want to log weight data to an SD card for long-term analysis, here’s how:
#include <SD.h>
File dataFile;
void setup() {
SD.begin(4);
dataFile = SD.open("weight_log.csv", FILE_WRITE);
dataFile.println("timestamp,weight");
}
void loop() {
float weight = scale.get_units(10);
dataFile.print(millis());
dataFile.print(",");
dataFile.println(weight);
dataFile.close();
delay(1000);
}
This feature is perfect for coffee extraction curve logging, pet feeding monitoring, logistics package weight archiving, and other scenarios that require long-term weight tracking.
Bulk Cost Optimization
- HX711 chip bulk price drops to ¥6/piece.
- Load cells in batches of 10+ can be negotiated to ¥20/piece.
- Swap the main board for an ESP8266 (¥12/piece) with built-in WiFi for IoT expansion.
- Replace the OLED with a segment LCD (¥3/piece) for lower power consumption.
Bulk cost for 10 sets: approximately ¥46 per unit.
Common Takeaways from Both Projects
| Comparison | BH1750 Auto-Dimming | HX711 Scale |
|---|---|---|
| Sensor Type | Digital light | 24-bit ADC weighing |
| Interface | I2C | Custom serial protocol |
| Signal Processing | Exponential moving average | Multi-sample averaging + filtering |
| Core Challenge | PWM mapping curve | Calibration factor calculation |
| Typical Cost | ¥25 | ¥50 |
| Precision | ±20% Lux | 0.1g |
Shared techniques: Both projects use software filtering (exponential moving average), both depend on correct wiring and I2C address troubleshooting, and both benefit from serial debugging to observe data. Getting both systems running teaches you the three pillars of embedded sensor development: signal acquisition, filtering, and calibration.
Advice for beginners: If you’ve never touched Arduino before, start with the BH1750 project — simple wiring (4 wires), short code, and immediate visual feedback (LED brightness changes with ambient light). It’s easy to get a sense of accomplishment. Once that’s working, move on to the HX711 scale, which involves more complex concepts like calibration, taring, and filtering. With the BH1750 foundation, the HX711 project will go much more smoothly.
Next steps: Both projects can upgrade from Arduino Nano to ESP32 for WiFi/Bluetooth capabilities. The BH1750 can feed into an MQTT smart home platform, and the HX711 can connect to a cloud database for long-term weight trend analysis. Even more interesting: combine both to create an interactive installation that responds to both ambient light and object weight, fully leveraging the potential of sensors.
Safety note: Be careful to distinguish between 5V and 3.3V power — connecting them incorrectly can destroy modules. When sharing an I2C bus among multiple devices, ensure addresses don’t conflict (BH1750 defaults to 0x23, OLED is typically 0x3C — no conflict). Always verify circuits on a breadboard before soldering to avoid wasting components on a one-time soldering mistake.
Summary
The BH1750 auto-dimming system lets your lighting truly “see” the environment — saving energy while improving comfort. The HX711 scale achieves 0.1g precision for under ¥50, making it an excellent project for learning weak-signal processing.
Key takeaways:
- The BH1750 outputs digital light values via I2C — more stable and accurate than photoresistors, and PWM enables auto-dimming.
- The HX711’s 24-bit ADC amplifies millivolt-level sensor signals by tens of thousands of times — the key to high-precision weighing.
- Both projects rely on calibration — BH1750 is factory-calibrated, while HX711 requires manual calibration factor calculation.
- Software filtering (exponential moving average) is a universal technique for sensor projects, producing smoother and more stable output.
Give it a try — put sensors to real work for you!