|
Sensor DIY in Practice: BH1750 Auto-Dimming + HX711 High-Precision Scale Build

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:

FeatureBH1750Photoresistor
Output TypeDigital I2CAnalog voltage
Measurement Range1–65535 LuxDepends on circuit design
Accuracy±20%Poor
Wavelength ResponseClose to human eyeInconsistent
CalibrationFactory calibratedRequires 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

ComponentModelQtyUnit PriceTotal
Light SensorBH1750FVI module1¥4.5¥4.5
Main BoardArduino Nano1¥12¥12
LED5mm white LED3¥0.3¥0.9
Current-limiting Resistor220Ω3¥0.1¥0.3
Breadboard400-hole1¥5¥5
Jumper WiresMale-to-male 20cm10¥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:

BH1750Arduino Nano
VCC5V (module with voltage regulator)
GNDGND
SCLA5 (I2C clock)
SDAA4 (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:

  1. I2C initialization: lightSensor.begin() takes the measurement mode and address. CONTINUOUS_HIGH_RES_MODE provides 1 Lux resolution.
  2. Exponential moving average filter: The ALPHA coefficient balances response speed and stability, preventing LED flicker.
  3. 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

ComponentModelPriceNotes
HX711 Module24-bit ADC¥8Version with voltage regulator
Load Cell50kg cantilever¥25Choose 1kg–50kg by capacity
Main BoardArduino Nano¥15Or ESP32
OLED Display0.96” I2C¥12Shows weight
Jumper WiresMale-to-female¥5
Breadboard400-hole¥8
Total-¥73Lower 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:

  1. Connect the load cell’s four wires to the HX711 module’s E+, E-, A+, A- terminals.
  2. Connect HX711 VCC to Arduino 5V, GND to GND.
  3. Connect HX711 DT pin to Arduino D2 (data).
  4. Connect HX711 SCK pin to Arduino D3 (clock).
  5. 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).

  1. Remove all items from the scale platform, power on and wait 5 seconds for warm-up.
  2. Serial output prompts “Remove all weights, preparing to tare”, wait 3 seconds to stabilize.
  3. Call scale.tare() to complete zeroing.
  4. Place a weight of known mass (e.g., 100g) on the platform.
  5. Enter the actual weight (in grams) via serial input.
  6. 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

  1. HX711 chip bulk price drops to ¥6/piece.
  2. Load cells in batches of 10+ can be negotiated to ¥20/piece.
  3. Swap the main board for an ESP8266 (¥12/piece) with built-in WiFi for IoT expansion.
  4. 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

ComparisonBH1750 Auto-DimmingHX711 Scale
Sensor TypeDigital light24-bit ADC weighing
InterfaceI2CCustom serial protocol
Signal ProcessingExponential moving averageMulti-sample averaging + filtering
Core ChallengePWM mapping curveCalibration factor calculation
Typical Cost¥25¥50
Precision±20% Lux0.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:

  1. The BH1750 outputs digital light values via I2C — more stable and accurate than photoresistors, and PWM enables auto-dimming.
  2. The HX711’s 24-bit ADC amplifies millivolt-level sensor signals by tens of thousands of times — the key to high-precision weighing.
  3. Both projects rely on calibration — BH1750 is factory-calibrated, while HX711 requires manual calibration factor calculation.
  4. 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!