“Precision” Scale#

How could we improve the accuracy of the scale? Besides the obvious - a better ADC - there are a few other steps we should take.

The most important is to change the ADC to use the bridge voltage as reference, rather than the built-in precision reference or supply voltage.

The voltage at the output of the bridge is

\[ v_{\textrm{bridge}} = S \times V_{\textrm{s}} \]

where \(S\) is a scale factor that is proportional to the weight and \(V_{\textrm{s}}V_{\textrm{s}}\) is the voltage across the bridge.

The output code from an ADC is

\[ D_\textrm{out} = \frac{v_\textrm{bridge}}{V_\textrm{ref}} = S \times \frac{V_\textrm{s}}{V_\textrm{ref}} \]

Let’s assume that \(V_\textrm{s}\) is the voltage of the battery powering the scale and \(V_\textrm{ref}V_\textrm{ref}\) is a constant precision reference voltage. As the battery voltage decreases over time, the ratio \(V_\textrm{s}/V_\textrm{ref}\) decreases, resulting in a gain error and consequent incorrect measured weight.

This and a host of other errors are avoided if \(V_\textrm{s}=V_\textrm{ref}\).

Integrated circuits for reading bridge outputs such as the HX711 include all the necessary circuits, including the amplifier and ADC.

Let’s modify our scale to use an HX711 breakout board. Connect the \(V_\mathrm{DD}\) and \(V_\mathrm{CC}\) pins to VUSB of the ESP32 and GND to ground. Note that although the breakout board states a minimum supply of just 2.7V and hence operation from a single cell rechargable Lithium battery, doing so requires changing the feedback resistors, \(R_1\) and/or \(R_2\). Tie DAT and CLK to two used pins of the ESP32. The code below assumes 12 and 27, respectively.

Also wire the load cell to the pins provided on the breakout board. My load cell does not have a shield, hence I left that pin unconnected. However I “twisted” the green and white wires from the load cell in an attempt to minimize electromagnic interference. Making these wires as short as possible also helps with noise.

HX711 MicroPython Driver#

The HX711 requires a driver. Several versions for MicroPython are available, I chose this one.

%%bash

url=https://raw.githubusercontent.com/robert-hh/hx711/master/hx711_gpio.py
dst=$IOT_PROJECTS/balance/code/lib/hx711.py

curl -o $dst $url
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2345  100  2345    0     0  12342      0 --:--:-- --:--:-- --:--:-- 12342

Updated Scale class#

Now update the Scale class to use the HX711 rather than the ADC in the ESP32. I call the new class ScaleHX711 to easily switch between the two versions.

Simply change

from scale import Scale

in main.py to

from scale_hx711 import ScaleHX711 as Scale

to use the new class, the code for which is below.

%%writefile $IOT_PROJECTS/balance/code/lib/scale_hx711.py
from hx711 import HX711
from machine import Pin
        
class ScaleHX711:
    
    def __init__(self, data_pin=12, clock_pin=27, scale=500/1059800):
        self._scale = scale
        pin_OUT = Pin(data_pin, Pin.IN, pull=Pin.PULL_DOWN)
        pin_SCK = Pin(clock_pin, Pin.OUT)
        self._hx711 = hx = HX711(pin_SCK, pin_OUT)
        hx.tare(20)
        hx.set_time_constant(0.25)
        
    @property
    def hx711(self):
        return self._hx711
    
    def measure(self, N=10):
        hx = self._hx711
        v = 0
        for _ in range(N):
            v += hx.get_value()
        v /= N
        return v * self._scale
    
    def tare(self, button):
        self.hx711.tare(20)

scale = ScaleHX711()

while True:
    print("read {:15.1f} grams".format(scale.measure()))
Writing /home/iot/iot49.org/docs/projects/balance/code/lib/scale_hx711.py
%connect balance -q
%rsync
Directories match

Reset the microcontroller. Use either the display or the code below to test the new setup. The resolution of my scale is now better than 0.1 gram (the first digit after the comma does not flicker). Not bad!

from scale_hx711 import ScaleHX711 as Scale

scale = Scale()

while True:
    print("read {:15.1f} grams".format(scale.measure()))
Hide code cell output
read             0.0 grams
read             0.0 grams
read             0.0 grams
read             0.0 grams
read             0.0 grams
read             0.0 grams
read           113.1 grams
read           194.8 grams
read           199.9 grams
read           200.1 grams
read           200.2 grams
read           200.2 grams
read           200.1 grams
read           200.1 grams
read           200.1 grams
read           200.1 grams
read           200.1 grams
read           272.4 grams
read           666.0 grams
read           705.4 grams
read           701.0 grams
read           700.8 grams
read           700.8 grams
read           700.8 grams
read           700.8 grams
read           700.7 grams
read           701.7 grams
read           560.6 grams
read           503.7 grams
read           500.5 grams
read           500.3 grams
read           500.3 grams
read           500.3 grams

Interrupted