What The Tech

Under Pressure

Weight Measurement with Load Cells

Liam Davies

Issue 51, October 2021

This article includes additional downloadable resources.
Please log in to access.

Log in

We take a look at these powerful weight sensors and how you can use one in your next microcontroller project.

When describing the physical characteristics of real-world objects, makers have access to a plethora of sensors and measurement electronics. With everything from spectrometers for measuring the light composition of light sources to moisture sensors for measuring how damp soil is, modern mass-manufacturing has bought these versatile sensors into the reach of consumer purchasing.

In this What The Tech, we’re taking a look at Load Cells and their potential multitude of applications in your own projects. Before we introduce load cells, we need to introduce their building blocks: strain gauges.

What’s a strain gauge?

The terms ‘strain gauge’ and ‘load cell’ tend to get thrown around a lot when talking about weight measurement, however, there is a difference between them.

A strain gauge consists of a snake-like arrangement of very thin conductive wire. From one terminal to the other of the wire setup, its resistance varies based on the tension or compression in the direction of the long conductors. It’s worth noting that, generally, strain gauges are insensitive to lateral forces – forces that act perpendicular to the primary axis of the gauge.

Provided that the strain gauge does not buckle or snap and therefore cause an open circuit, the very slight changes in the length of the wires causes a change in resistance. It’s worth noting that in normal operation, the physical shape doesn’t visually change (enough to notice, anyway), and for this reason, the changes in the resistance are very small!

To convert these small changes in resistance to a useable output, we must dramatically amplify any readings we get from the strain gauge. The simplest method one may think of is to use a known resistor in series with the strain gauge to act as a voltage divider. Unfortunately, amplifying this signal with respect to ground would also be amplifying a large DC voltage present, in addition to the very small signal we need.

Wheatstone Bridge

The Wheatstone bridge is the answer to this problem, which consists of resistors connected between four wires, typically drawn in a diamond pattern. Physically, it is wired as two voltage dividers connected in parallel, with the two signal outputs connected to each. Besides the two signal wires, there are two excitation wires that have a constant voltage applied to them, typically 5V or 12V.

Before we look at how this is applied to the load cell, the Wheatstone bridge itself is a very powerful circuit configuration, especially when measurement equipment wasn’t as accurate as it is today. The Wheatstone bridge was very effective at deducing unknown resistances. Looking at the Wheatstone diagram, we need to set the two opposing resistors R1 and R3 in our network to known, fixed values and then place an galvanometer in the centre of the bridge.

The lower right resistor Rx is our unknown test resistance, and the lower left resistor is a variable resistor R2. If current is flowing through the centre galvanometer, we know that the voltage on each side of the bridge is at different levels. So, we adjust the variable resistor until no current flows through the bridge. Once we have achieved balance, we know from using resistor divider equations that R2/R1 = Rx/R3. Since we know resistors R1, R2 and R3, we can solve for Rx using this formula.

While it may seem overcomplicated to some extent, galvanometers (and nowadays, the modern ammeter) can be set up to detect extremely small non-zero currents. That is, rather than the exact magnitude of the current, we only want to know if any current is flowing at all, which would mean the bridge is not balanced. This unknown resistor could be any variable resistor, such as temperature, soil moisture, a Light Dependent Resistor (LDR) or in our case, a strain gauge. It’s a similar process to using a set of balance scales (the old type, with a pivot in the middle) – we must continually add weights to the higher side until both sides are balanced. Because we know what weights we added, the unknown object’s weight on the other side can be deduced.

While some strain gauges are configured as a single, unknown resistance in combination with three known ones, bar-style load cells use four strain gauges in a Wheatstone configuration. This may not be immediately intuitive as to why load cells are connected this way. After we did some thinking and tinkering, it actually makes a lot of sense as to why this method is so effective.

The Load Cell

Let’s imagine that we apply a small, constant force to the end of the load cell in the downwards direction. The bar flexes downwards by a very small amount, causing two of the strain gauges to compress and two to stretch. This, therefore, causes the resistances in the gauges to change proportionally, raising the voltage on one side of the Wheatstone bridge and dropping it in the other. The relative voltage between the sides has grown larger since we applied the weight. Applying more weight to our hypothetical load cell, the strain gauges stretch and compress even more (depending on their location) and produces an even larger voltage.

When we talk about a ‘larger’ voltage, we’re talking about millivolts which, on a scale we typically use for digital electronics, is not much at all. That’s why we need an amplifier that can effectively transform these tiny changes in voltage into larger, digital readings that a microcontroller can use.

Amplification

For sake of explanation, we’re using the common HX711 load cell amplifier available across the internet. There are many breakout boards to easily experiment with the functionality of this little powerhouse of a chip. There are many other ICs available with different specifications.

Load Cell Amplifier from Sparkfun
Grove Load Cell Amplifier by Seeed Studio

We’ve created a simple block diagram to show how these load cell amplifiers work. The HX711 has a differential input, which means that signals are not amplified with respect to ground, but instead, the difference between the signals are.

The signal is then dramatically amplified. The HX711 chip has a selectable gain of 32, 64 and 128 which corresponds to a 32x, 64x and 128x larger signal than the input. So, as an example, if our load cell generates a voltage of 5mV across the Wheatstone bridge, the HX711 would amplify this to 0.32V with a gain of 64.

Analog voltages are finicky to work with, especially when precision is desired to such a high degree. So, the HX711 includes a 24-bit analogue to digital converter so our microcontroller can read the data without risking interference with any of the sensitive analogue circuitry.

For the exact sensitivity, consult the datasheet of the load cell that you plan to use. Sensitivity is expressed in mV/V, which describes the voltage (in millivolts) that is generated between the signal wires for every volt of excitation voltage.

For instance, the 500g load cell we’re using has a sensitivity of 0.7mV/V, so for a 5V excitation voltage and a full load (500g), 0.7mV x 5V = 3.5mV is generated between the signal wires.

Hands On: Arduino-based Load Cell Circuit

Parts Required:JaycarAltronicsPakronics
1 x Arduino Mega or compatibleXC4420Z6241PAKR-A0036
1 x Load Cell 50kg--SS114990100
1 x Load Cell 500g--SS314990000
1 x Load Cell Amplifier HX711--SS101020712
M3 and M6 bolts, and prototyping hardware is also required.

Let’s get our load cell hooked up to the amplifier board and the Arduino so we can do some testing.

Slotting the 500g and 50kg load cell we sourced for this project into the amplifier can be done interchangeably, with no code changes. As discussed, load cells are just a network of resistors so the amplifier doesn’t know any better. It’s important that the Arduino tares and calibrates the readings for each one, though.

To connect everything up, you’ll need to connect the four wires from your load cell into the amplifier board, which consist of two excitation wires (5V and Ground in our case) and two signal wires. Some load cells also have a shielding connection that prevents excess noise from working its way into the analog signals.

The amplifier itself needs 5V, Ground and two data wires, one for clock and one for data. Check the Fritzing for more detailed connection information. We also 3D printed some small discs to appropriately direct all weight force through the stress points of the load cells, which are attached with M3 and M6 screws for the 500 and 50kg load cell respectively.

Our methodology of analysing the performance of the load cells will first be done by taring and calibration with a reference weight. By then leaving the cells loaded with a constant weight for a couple of minutes, we can deduce the ‘creep’ of the cell – i.e. how much the readings drift. We can also have a look at the noise characteristics of the amplifier output. This was all done by copy-pasting the data from the Serial Monitor into Rstudio, a development environment for the statistics-orientated R programming language.

Speedy Sampling

We found a way of changing the default 10 samples per second to 80 samples per second when we were skimming through the amplifier’s datasheet. In the case of the HX711 chip, we need to pull up pin 15 to VCC, so we broke the surrounding traces on the board and soldered in a 10kΩ pulldown resistor with a jumper header to VCC to switch between the sample rates. This works great for getting 8x the amount of data, however, we test didn’t the difference between the rates in terms of noise and signal quality.

The Code

Getting a basic code system working with our Load Cell system is actually very simple. Depending on your amplifier chip, you may need to import and adapt libraries to suit. However, the HX711 is by far the most common chip, available in most Arduino-compatible modules.

#include "HX711.h"
#include <Arduino.h>
HX711 scale;
uint8_t dataPin = 6;
uint8_t clockPin = 7;
float data_calibrate = 1;
const float calibrate_weight = 39;
unsigned long lastTime = 0;
int samples = 15;
float cur_reading = 0;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  scale.begin(dataPin, clockPin);
  scale.set_gain(64);
  scale.set_scale();
  scale.tare(30);
}

This setup code essentially just tells the HX711 library to start talking to the amplifier chip, which can be connected to the Arduino on pins 6 and 7. Most digital pins should work for Data and Clock. We can also set up some variables to use in our main program loop, including the number of samples and the current averaged reading. You’ll also need to pop in your reference weight here, in our case a 39g battery that we measured with an accurate set of kitchen scales.

By default, this code tares and sets the scale of the load cell when the Arduino first turns on, so if taring is desired, simply reset the Arduino or type ‘t’ into the serial monitor.

void loop() {
  if (scale.wait_ready_timeout(1000)) {
    float reading = scale.get_units(10);
    averager(reading);
    Serial.print(cur_reading);
    Serial.print(", ");
    Serial.println(reading);
  } else {
      Serial.println("HX711 not found.");
  }
  if (Serial.available() > 0) {
    // read the incoming byte:
    byte incomingByte = Serial.read();
    if(incomingByte == 't') {
      scale.tare(30);
      cur_reading = scale.get_units(10);
    }
    if(incomingByte == 's') {
      data_calibrate = scale.get_value(10) / calibrate_weight;
      scale.set_scale(data_calibrate);
      cur_reading = scale.get_units(10);
    }
  }
}
float averager(float input) {
  cur_reading = (cur_reading * (samples - 1) + input) / samples;
  return cur_reading;
}

This is our main loop of code here, and while there is a bit to read through, it mostly just revolves around waiting for the HX711 to send data and processing input from the serial monitor.

When we do receive data, we print both the reading we receive from the HX711 library and our current rolling average value. The serial monitor code waits until the user enters a single character ‘t’ for tare and ‘s’ for scale. The ‘t’ command sets the scale back to 0, while the ‘s’ tells the scales to set the scale based on the calibration weight.

One thing to be aware of is that the HX711 module does not output calibrated data. That is, it just spits out the voltage it reads on the input pins in the form of a 24-bit number. It’s our job as programmers to calibrate and interpret this data. These load cells are just resistor networks so it’s intuitive that the amplifier has no idea what the cell’s weight sensitivity is.

The easiest way to turn the raw output values of the sensor into useable weight data (e.g. in kg) is to complete a simple two-point calibration. One of these points is the ‘tare’, which is just saving a value into memory that is subtracted from every output reading. This is also handy for dealing with weighing objects inside containers or additional hardware attached to the top of the load cell. The other point is using a known weight to calibrate the scaling factor of the load cell. When we find this scaling factor, we divide future readings by this value and subtract the tare weight to get the output.

Limitations

As with most Arduino-based electronics, there is usually a modest upper limit to the performance that can reasonably be achieved. Naively, one may see a claimed 24-bits of measurements and would hope to be blown away by the potential accuracy. If a load cell has a 500g range, and the load cell could successfully achieve 16,777,216 (2 ^ 24 bits) steps of resolution, this would equate to a whopping 2.98 x 10^-8kg resolution. To put that into perspective, the amplifier would be able to tell if a single human eyelash was placed on top of it.

Oh, if only Arduino technology was that good! Unfortunately, many of the lower (least-significant) bits of the amplifier’s readings are riddled with noise. Beyond using a better amplifier, or designing your application circuit to minimise nearby sources of electrical noise as much as possible, there isn’t a lot we can do about this. Usually, code on the microcontroller side uses multiple readings and takes the average, which should have lower levels of noise. Anyhow, the upshot is that usually the first 20 or 21 bits are known as the ‘effective’ bits, bits where changes in mass can successfully be detected in the ADC’s output as signal rather than noise.

And, to make matters worse, other factors besides reading-to-reading noise also significantly affect the performance of the load cell readings. Thermal drift is a significant factor, which, as the name suggests, slightly changes the resistance of the strain gauges as the ambient temperature changes. This causes readings to be thrown off over a longer period of time. It’s worth noting that Wheatstone-configuration load cells, in some respects, mitigate this problem since all resistors are affected by the temperature changes similarly, so the voltage across the legs shouldn’t be affected.

500g Load Cell Datasheet Sample Values

Luckily, these problems can be somewhat designed for as load cell datasheets include figures expressed in %FS that specify the full-scale deviation of a characteristic. For example, a ±0.05%FS/3min figure means that the reading is expected to drift ±0.05% of the full scale reading over three minutes. Other deviations include non-linearity, hysteresis, and repeatability.

Nonetheless, load cells should be calibrated in an environment as close to their end-use environment as possible to reduce these issues. For example, if an unknown weight is desired to be measured, an exactly known weight (of a similar magnitude, preferably) should be used to calibrate the scales beforehand.

Rather than worry about these engineering issues, many load-cell manufacturers include calibration data that allows developers to correct for reading offsets with functions – sometimes listed as a fourth-degree curve (polynomial). Of course, this is way out of the scope of this article, but for more accurate applications, these datasheets should be followed to the tee.

RESULTS

We left the 500g cell running for a couple of minutes with the Arduino serial monitor open, after calibrating it with a 39g reference weight. We collected two datasets, one for the Raw readings and one for the Averaged readings. It’s worth noting that the ‘Raw’ dataset is not technically raw, as we’re telling the HX711 to take the average of 10 readings and then output that. However, we also calculate a rolling Average based off the last 20 readings to further smooth out the response – this is our ‘Averaged’ dataset.

Weight Creep over Time

The first graph we made displays the averaged readings in blue, and the raw readings in red. Again, these are only partially raw since they are already averaged by the library. The main purpose of this graph was to visually show how creep can affect readings over time. This could be due to temperature changes, hysteresis in the load cell or several other factors. Either way, disregarding noise, it’s apparent that the average weight reading slowly increases over a couple of minutes.

Next up, let’s discuss the noise of the load cell. The Raw red readings are obviously quite noisy, so after 15 average readings, the blue Averaged line is much better. This can be seen easier in the boxplot, where the centre grey box depicts the middle 50% of readings. The smaller this box and the ‘whiskers’ surrounding it are, the more tightly packed the data is. However, the median value (the bold black line) is slightly skewed in the Averaged dataset, which is further from the correct 39g value than the Raw dataset is. This is a good demonstration of why data processing needs to be done carefully, because it leads to slower and (sometimes) misleading responses to data.

Where To From Here?

Hopefully, this short guide and explanation will inspire you to make some clever weight sensing gadgets. Because these sensors are quite accurate, it’s possible to pull off some very respectable performance with minimal part costs.

As a personal note, I am currently helping develop a small set of coffee scales for a university project, using a 500g load cell to measure the pour time and characteristics of espresso brewing. Because sampling can be done quite quickly, very precise measurements of the duration and dose amounts can be made. The data is then relayed over an ESP32 to display information on a smartphone.

That’s at the small end of the scale, there are, of course, much larger ranges of full-scale measurements that can be sourced in terms of load cells. Up into the hundreds and thousands of kilograms are possible, but, of course, these load cells become quite expensive.

Those who are experienced with force transducers such as these load cells may note that this discussion is on the simplistic side. We have purposely skipped over some more technical parameters (such as compensation resistors) that, in a laboratory or industrial context, would be critical to reliable operation, but in a DIY electronics context, are not vital to a working system.

For more ideas, remember that load cells can not only be used to measure weight! Since a load cell is a force transducer, any object capable of applying a linear force can be measured. RC planes and quadcopters can be used in conjunction with load cells to measure thrust force and the power-to-weight ratio. Another application is in virtual sports such as simulation racing, where load cells are used in brake pedals because of the direct proportionality between applied force and desired braking force (as opposed to a simple potentiometer). From a more scientific perspective, using momentum and kinematic physics equations, one could also calculate impulse, kinetic energy and the initial velocity of a flying object impacting a load cell. So, grab yourself a couple of load cells and an amplifier and get experimenting!

Liam Davies

Liam Davies

DIYODE Staff Writer