Microcontrollers and Multiplexing Weight Sensors

Introduction

In my quest for an automated garden, I decided I would try weighing plants to determine when they needed watering. The idea is that the plant becomes lighter as transpiration takes place. Some water, along with minerals, is used to grow the plant, while the rest of the water evaporates away. When the water has been exhausted, the plant will exhibit minimum weight, and the gardener needs to be alerted so they can water the plant.

Specifications: Four Plants, between 0Kg and 2Kg each, will be weighed. The system should be expandable to eight or more plants. I’ll be using an ESP32 microcontroller to read the weights, but any Arduino compatible microcontroller will work. I’ll be working in VS Code using the Platform.io extension.

Goals: This article attempts to explain how to use a microcontroller to take weight measurements, calibrate the scale and how to use a multiplexer to increase the number of objects that we can weigh at once.

Bill of Materials

Load Cells

HX711 and LoadcellMy research led me to load cells – which are machined pieces of metal that change their electrical resistance depending on the force applied. Load cells are cheap, readily available and come in various weight ranges. The ones I use in this article are 1Kg and 2Kg.

To read the minute changes in electrical resistance, we must use a very sensitive, low-noise amplifier and high-quality ADC to measure and pass a load cell’s resistance onto our microcontroller.

Enter the HX711 24-bit ADC for Weigh Scales

For this job, the HX711 IC fits the bill. They are cheap, sold individually or mounted on handy breakout boards with connections for the load cell and microcontroller.

And just as importantly, there are Arduino libraries for the HX711.

The HX711 uses a Wheatstone Bridge with a 24-bit ADC to accurately determine the load cell’s resistance and make it available to our microcontroller. The MC uses the HX711’s Clock and Data pins to read the ADC’s value. This is all hidden under an abstraction layer of function calls (or method calls). We can read its value from our programs without much thought about how the Clock and Data pins need to be driven.

The HX711 has two channels from the load cell:  A and B. Channel A has a gain of 128 or 64dB, while Channel B has a fixed gain of 32dB.

74HC4052N Dual 4-way Multiplexer

The HX711’s data and clock pins are typically connected to the microcontroller’s GPIO pins. Connecting four HX711 boards requires eight MC GPIO pins.

The ESP32 I’ve chosen for this project has enough GPIO pins to handle this many inputs; however, I have other plans for many of those pins. 

Instead of directly connecting four HX711s to the ESP32, I decided to use a 4-channel multiplexer, allowing my ESP32 to see each of the four HX711s while using half as many GPIO pins.

The multiplexer I chose is the 74HC4052N dual, 4-channel bi-directional multiplexer.

74HC4052_Pinout
source: Sparkfun
74HC4052 MuxDeMux

The IC features two electrically independent four-way switches. Each switch features a “common” pin that is connected to one of four pins. Switch one is labelled with common pin 1Z and can be connected to one of the pins 1Y0, 1Y1, 1Y2, or 1Y3. Switch two is marked with a common pin of 2Z, which can be connected to one of 2Y0, 2Y1, 2Y2 and 2Y3.

These chips are bi-directional, meaning you can send or receive data. In addition, they can be “switched” on the fly by setting pins S0 and S1 according to the table.

S1 S0 1Z 2Z
1Y0
2Y0
1
1Y1
2Y1
1
1Y2
2Y2
1
1
1Y3
2Y3

Both switches are controlled with the same set of S0 and S1 address pins. The S0 and S1 pins are connected to our microprocessor’s GPIO pins so we can control the switch’s pathway.

I connected each of the four HX711’s data pins to 1Y0, 1Y1, 1Y2 and 1Y3 and their clocks to 2Y0, 2Y1, 2Y2 and 2Y3. By controlling S0 and S1 via GPIO pins, I can choose which HX711 board “appears” at the 1Z and 2Z pins. For example, by setting S1 and S0 to ’10’, the ESP32 can see HX711 number 2.

The multiplexer uses four GPIO pins to control four HX711 boards, saving me four pins.

If I wanted to control eight HX711 boards, I could do one of two things. A) I could get a new multiplexer with eight channels, or B) Use a 2nd 74HC4052N.

In both cases, we will need a fifth GPIO pin from the ESP32. In the first case, the GPIO pin is used as a third address bit, S2. in the second case, the fifth GPIO pin is connected to the Enable pins of the multiplexers, inverting one.

Programming the Microcontroller

The HX711 library I used is from Github user bogde. It’s available under PlatformIO‘s library manager and can be installed easily by adding the following code to your Platform.io file in the lib_deps section:

lib_deps =
bogde/HX711 @ ^0.7.5

Bogde did a great job of keeping everything simple for us. We simply instantiate an HX711 class and call some members.

				
					#include "HX711.h"
const int LOADCELL_DOUT_PIN = 18;
const int LOADCELL_SCK_PIN = 19;

HX711 scale;

scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN, 32);
// Set gain to 32, uses B- and B+ on the HX711 Board

scale.set_scale(206);                   // Divide measurements by this number
scale.set_offset(7135);                 // Add this to each reading
long Weight1=scale.read();              // Get reading with no scale or offset applied
long Weight2=scale.get_units();         // Get reading with scale and offset applied
long Weight2=scale.get_units(5);        // Get 5 readings and average them together

				
			

Each HX711 object has an offset and a “scale” amount. When reading data from the HX711, you have access to the raw, unaltered reading, plus a corrected/calibrated amount. The raw unaltered reading is just that – a 24-bit number with no relationship to pounds or grams.

The read() function returns a LONG containing the raw, unaltered reading directly from the HX711 IC. The get_units() function will read() the value, subtract the offset, then divide by the scale.

How do you determine offset and scale? Well, the simplest thing you can do is this:

				
					void loop()
{
    Serial.println(scale.read());
}
				
			

Determine Offset:

When your scale is empty, take note of the reading from read(). It will likely bounce around a bit, but try to find a good general average. My number was about -7135. Your Offset is added to the HX711 reading to bring it as close to zero as possible.

In other words, my Offset is 7135.

You can also use the tare() function to automatically set the Offset or use tare(n) to set the Offset based on an average of n readings. So, for example, scale.tare(10) will take ten readings and set the Offset to the average of those readings.

Before using the tare() function, I recommend setting the Offset to 0 using set_offset(0); Otherwise, the tare() function uses the current Offset to calculate a new offset, leading to an erroneous scale calculation.

Determine Scale:

ScaleEquationAfter calculating Offset, place a known weight on your scale and note the new raw reading from read(). Next, calculate scale with this formula:

Note: You should know that the library used here blocks your program from continuing until the read() operation from the HX711 is complete. Nothing else must happen during this time due to the tight timing requirements of the HX711 Clock pin. If there is a slight delay while the clock operates, the chip will go into sleep mode. 

Software

				
					; for Platform.io
lib_deps =
      bogde/HX711 @ ^0.7.5
				
			
				
					#include "HX711.h"

// HX711 circuit wiring
const int LOADCELL_DOUT_PIN = 18;
const int LOADCELL_SCK_PIN = 19;
const long LOADCELL_OFFSET = 7135;
const long LOADCELL_DIVIDER = 207;
const int Mux_S0 = 5;
const int Mux_S1 = 23;          // HX711 Clock Pin

HX711 scale;

void setup() {
  Serial.begin(115200);

  pinMode(Mux_S0, OUTPUT);                            // Set S0 S1 to input/output 0
  digitalWrite(Mux_S0,0);
  pinMode(Mux_S1, OUTPUT);
  digitalWrite(Mux_S1,0);

  Serial.printf("\n\nTaring Scale\n");
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN, 32);    // Set gain to 32, uses B- and B+ on the HX711 Board
  //scale.set_gain(32);       
  scale.tare(10);                      // Set ZERO using an averaged 10 readings
  scale.set_scale(LOADCELL_DIVIDER);   // Reads out in grams, approximately (for my test setup)
}

// CALCULATING SCALE:  ( scale.read() - scale.offset ) / KnownWeight
// Example:              ( 34811 - (-7209) ) / 203g = (  34811 + 7209  )  / 203g =  206
// scale.set_scale(206);

void loop() { 

    Serial.printf("Raw:%ld Offset:%ld Scale:%f Corrected:%f\n",scale.read(), scale.get_offset(), scale.get_scale(), scale.get_units(5) );
  
}
				
			

Conclusion

Reading weight with an ESP32 or another Arduino compatible microcontroller is a simple task with the libraries provided by the community.

An HX711 is a simple IC to interface with microcontrollers using only two pins. In addition, the HX711 can be multiplexed using a 74HC4052N dual-4-way mux/demux, potentially saving GPIO pins.

For little money, it’s easy to build a microcontroller-driven weigh-scale application. Using a Wifi capable microcontroller such as the ESP32 allows us to store historical data and run a web server for configuration or reporting.

Whatever your weight needs are, a load cell with an HX711 is a solid option. And now that you know how to use one with a multiplexer, you can easily connect multiple HX711s with the ability to scale up if required.

Leave a Comment