Digital weighing scale: HX711, load cells and the two-point calibration
- Build Logs
- 30 Jul, 2026
A load cell produces a signal so small that the interesting part of this project is not measuring weight — it is amplifying microvolts without drowning them in noise. That is what the HX711 is for, and once you understand what it is doing, the calibration stops feeling like magic numbers.
What you will need
| Part | Qty | Approx. |
|---|---|---|
| HX711 Load Cell Amplifier | 1 | ₹100 |
| 1kg Load Cell or 20kg Load Cell | 1 | ₹137–156 |
| Arduino Uno R3 (compatible) | 1 | ₹418 |
| 16×2 LCD with I2C | 1 | ₹149 |
Roughly ₹800. You will also want a set of known weights — a kitchen scale and a bag of rice works, but calibration weights are better.
Choose the capacity to match the job. A load cell is most accurate in the upper part of its range, so a 20 kg cell weighing a 50 g object is using about 0.25% of its span and the reading will be poor. Weighing kitchen quantities? Use the 1 kg. Weighing a person? 50 kg half-bridge cells, four of them.
Stock note: our load cells are down to one or two of each at the moment. The HX711 boards are fine.
Why you cannot just read it with analogRead()
A load cell is four strain gauges in a Wheatstone bridge. Flex the metal and two gauges stretch while two compress, unbalancing the bridge and producing a differential voltage.
How small? A typical cell is rated around 1 mV/V. At 5 V excitation, full scale — the entire 1 kg — is about 5 mV. One gram is therefore about 5 microvolts.
An Arduino's 10-bit ADC on a 5 V reference resolves about 4.9 mV per step. Your entire measurement range is one step. This is not a matter of averaging harder; the signal is three orders of magnitude below the noise floor.
The HX711 solves it with a 24-bit ADC and a programmable-gain amplifier sitting right next to the bridge, communicating digitally. That is why it is a separate board, and why the wires between the cell and the HX711 should be as short as you can make them.
Wiring
| From | To | Note |
|---|---|---|
| Load cell red | HX711 E+ | excitation |
| Load cell black | HX711 E− | |
| Load cell white | HX711 A− | signal |
| Load cell green | HX711 A+ | |
| HX711 DT | Uno D3 | |
| HX711 SCK | Uno D2 | |
| HX711 VCC / GND | Uno 5V / GND | |
| LCD SDA / SCL | Uno A4 / A5 |
Wire colours vary between manufacturers. If your readings go down as you add weight, swap white and green — that is all it means.
The mounting is half the accuracy
This is the part no code can fix. A bar load cell is designed to be a cantilever: one end rigidly clamped, the other end loaded, and nothing touching in between.
Bolt it flat to a table and it cannot flex, so it reads almost nothing. Let the free end rest on something and part of your load bypasses the gauges. Use spacers at both ends so the bar can bend freely, and make the fixed end genuinely rigid — a wobbly clamp shows up as a reading that changes when you lean on the bench.
Load it in the centre of the platform, too. Off-centre loading on a single bar cell introduces a torque the cell was not designed to measure, and you will see the same object weigh differently depending on where you put it.
Two-point calibration
The maths is simpler than it looks. The HX711 gives you a raw signed integer. It is linear in applied force, so you need exactly two facts: the raw value at zero load (the offset), and how many raw counts one gram produces (the scale factor).
#include "HX711.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define DT 3
#define SCK 2
HX711 scale;
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Measured for YOUR cell. Do not copy these.
long OFFSET = 0;
float SCALE_FACTOR = 1.0;
void calibrate() {
lcd.clear(); lcd.print("Remove weight");
delay(4000);
scale.set_scale(); // scale factor 1 for now
scale.tare(); // captures the offset
OFFSET = scale.get_offset();
lcd.clear(); lcd.print("Place 500g");
delay(8000);
// read_average returns raw counts; tare already removed the offset
float reading = scale.get_value(20);
const float KNOWN_GRAMS = 500.0;
SCALE_FACTOR = reading / KNOWN_GRAMS; // counts per gram
scale.set_scale(SCALE_FACTOR);
Serial.print(F("OFFSET=")); Serial.println(OFFSET);
Serial.print(F("SCALE_FACTOR=")); Serial.println(SCALE_FACTOR, 4);
}
void setup() {
Serial.begin(9600);
lcd.init(); lcd.backlight();
scale.begin(DT, SCK);
calibrate();
}
Run this once, write down the two numbers it prints, then hard-code them so the scale is usable immediately at power-on rather than demanding a calibration ritual every time:
void setup() {
scale.begin(DT, SCK);
scale.set_offset(OFFSET); // your measured value
scale.set_scale(SCALE_FACTOR); // your measured value
}
void loop() {
// Averaging 10 samples costs ~125ms at the HX711's default 10Hz
// and is the difference between a stable display and a flickering one.
float grams = scale.get_units(10);
// Below about a gram the reading is noise, so do not display it as data.
if (fabs(grams) < 1.0) grams = 0.0;
lcd.setCursor(0, 0);
lcd.print(grams, 1);
lcd.print(" g ");
delay(200);
}
Calibrate with a weight near your working range. Calibrating with 10 g and then weighing 900 g multiplies any small error in that 10 g measurement by ninety. Use something in the upper half of what you actually intend to weigh.
Why your reading drifts
Leave the scale on with nothing on it and the number will slowly wander. Three causes, in order of size:
Temperature. Strain gauges are sensitive to it, and so is the HX711's own reference. A cell that warms up from a nearby LCD backlight, or a room that heats up through the day, drifts measurably. Commercial scales compensate for this; yours does not.
Creep. Leave a heavy load on for an hour and the metal deforms very slightly, so the reading changes with the load unmoved. It also means the zero does not immediately return when you remove it.
Supply noise. The excitation voltage is the reference for the whole measurement, so ripple on 5 V appears directly in your reading. This is the reason a scale behaves better on a clean adapter than on a busy USB hub.
The practical mitigation is a tare button: press it, zero the current reading, and drift becomes irrelevant for the next few minutes. That is exactly what the tare button on a kitchen scale is for.
Things that go wrong
Readings go down as weight goes up. Swap the white and green signal wires.
Value is stuck, or wildly random. Almost always DT and SCK swapped, or a bad ground. The HX711 is not I2C or SPI — it is a bit-banged two-wire protocol, so the pin order genuinely matters.
Barely responds to weight. The cell cannot flex. Check the mounting before you touch the code.
Same object weighs differently in different spots. Off-centre loading on a single bar cell. Expected.
Reading jitters by several grams. Increase the averaging, shorten the wires between cell and HX711, and get it off USB power.
Never returns to zero. Creep, or something is fouling the platform. Add a tare button.
Where to take it next
A tare button and a unit-switch button turn this from a demo into something genuinely usable in a kitchen. Beyond that, four 50 kg half-bridge cells wired as a full bridge gives you a person-scale platform, which is a much harder mechanical problem and a considerably more impressive result.
Logging is the other direction: send readings to the NodeMCU dashboard and you can plot how much your dog actually weighs over a month.
Ask us which cell suits your load before ordering — capacity choice is the decision that most affects how good your scale ends up being.