Code: github.com/WianStipp/hx711
I walk through building a miniature load-cell scale: bit-banging the HX711 from a Raspberry Pi Pico, two-point linear calibration, and live weight on a Waveshare 1.14" LCD.
The idea
I love making pour over coffee (La Cabra) and precisely weighing beans is key to consistent cups. However, precision weighing scales are expensive and has made previous roommates question my true occupation. So instead, let's build one for an even higher cost and simultaneously look even more dodgy.
A load cell outputs a tiny differential voltage proportional to force. The HX711 is a 24-bit ADC that amplifies that signal and hands the MCU a serial stream of counts, not grams. Those counts are offset by mechanical preload, amplifier gain, and wiring, so raw ADC values are useless until you map them onto a known mass. The prototype clocks out each 24-bit reading over GPIO, applies a precomputed two-point calibration (empty scale vs a known weight), and paints the result on the LCD. Later goals: cleaner hardware layout, tare, and Wi‑Fi via a Pico-ESP8266.
The mechanism
With an empty reading and a known-mass reading at mass , the slope (counts per gram) is:
A new reading converts to mass by reversing that map:
In the prototype this is hardcoded as:
The HX711 protocol itself is clock-driven: hold SCK low until DOUT falls (conversion ready), then pulse SCK 24 times to shift out the signed sample MSB-first. Extra SCK pulses after the sample select channel and gain for the next conversion.
Worth knowing
- Calibration constants are hardware-specific: change the load cell, wiring, or gain and you must remeasure and .
- Sharing ground between the Pico-LCD and HX711 matters; the Waveshare board needs a daisy-chained ground or the ADC readings become noisy/unreliable.
- Tare is just a runtime update of (current empty reading), not a change to the slope —unless temperature or mechanical creep shifts the gain.
- Averaging several readings before display kills flicker; a single 24-bit sample is already quantized finely but still noisy at gram-scale use.
Code
The live loop lives in prototyping/main.py: pulse the clock, assemble 24 bits, convert with the two-point factors, redraw the LCD.
ZERO_READING = 203100
KNOWN_READING = 570000
KNOWN_WEIGHT = 461
CONVERSION_FACTOR = (ZERO_READING - KNOWN_READING) / KNOWN_WEIGHT
def read_hx711():
clock_pin.value(1)
clock_pin.value(0)
while data_pin.value() == 1:
pass
data = 0
for _ in range(24):
clock_pin.value(1)
data = (data << 1) | data_pin.value()
clock_pin.value(0)
for _ in range(64): # post-sample SCK pulses (channel/gain)
clock_pin.value(1)
clock_pin.value(0)
return (ZERO_READING - data) / CONVERSION_FACTORFurther reading
- HX711 datasheet (Avia Semiconductor)
- hx711 repository