Cheap load cell measurement and display

This program was written to check out the HX711 bridge interface board and a low cost YZC-133 Load Cell and a 128 x 32 OLED display.

The program is pretty basic requiring no external library to read the HX711 output.
The display uses the SSD1306Ascii by Bill Greiman (text only, no graphics).

I created my own code to read the HX711 output because the libraries I tested seemed to hide the raw counts from the HX711. I wanted to see the raw counts to be sure the scaling and ability to read negative counts was functional. And it seems to be my preferred approach.

I used the on chip EEPROM to save the tare and scale during power down. It was probably a waste of time to save the tare because the sensor has a significant zero offset drift with temperature. So you will need to use the tare input before every use.

For the scale, I used a 200 Gram very accurate weight I had. For others I suggest finding a suitable weight and stopping by your local USPS office and ask them to weigh it for you. The change the

  • 200 grams
  • 7.054792 oz
    in the calculations to whatever your weight is.

Sketch uses 10860 bytes (35%) of program storage space. Maximum is 30720 bytes.
Global variables use 375 bytes (18%) of dynamic memory, leaving 1673 bytes for local variables. Maximum is 2048 bytes.

/*
copywrite JohnRob 2022

2022-09-01  v001
2022-09-06	v002	Added display and display calculations.
2022-09-15	v003	added tare and gScale inputs and calculations.
2022-10-18  v005.1  Tare (K1) and scale (K2) function.
					Scale has no display prompt simply put the 200gr
					on the loadcell then press K2.
2022-10-19  v005.2  Added more comments.					
_____________________________________________________________________

This program reads a YZC-133 Load Cell uisng HX711 breakout board
displaying grams and ounces on a 128x32 OLED.

The zero (tare) offset is stored in on chip EEPROM 
The gain is calculated from the counts resulting from a 200gram mass.
Gain is also stored in on chip EEPROM.
The Tare and Scale inputs use internal pullup and are initiated
when the inputs are grounded.

Processor target = Pro Mini

HX711 outputs 24 bits:   -8,388,608 to 8,388,607

Channel A can be programmed with a gain of 128 or 64,
corresponding to a full-gScale differential input
voltage of ±20mV or ±40mV
The sensor excitation on our board was 4.07Vdc

Zero drift seems to be mostly due to temperature of sensor.  Scale seems
reasonably stable.

After reading the HX711 we add "filler" data to convert the 24 bit two's compliment
to a 32 bit two's compliment value, which the compiler understands.  This way we
don't have to deal with a 24 bit conversion to a float.

https://www.exploringbinary.com/twos-complement-converter/

Notes on YZC-133 5kg
	Rated Output: 1.0 ±  0.15mV / V
	if Vexcitation = 4.07V
	     then: 5kg = 1mv * 4.07 = 4.07mv = 5kg.

	@gain = 128 then we are using only 1/5 of our range.
	8,388,607 *4.07 / 20 = ± 1,707,081

todo:
	ADD WDT as powering via our test board often requires a reset before program starts!

*/

/*
	Pin Assignments:
		2 	Input			HX711 Data Pin
		3 	Output  		HX711 Clock Pin
		8 	Input Pullup 	Initiate Tare
		9 	Input Pullup 	Initiate gScale (pgm assumes gScale is based on 200mg mass)
*/

#define uint unsigned int
#define int32 int32_t
#define uint32 uint32_t

#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include <EEPROM.h>

//#define period 3000
#define I2C_ADDRESS 0x3C
#define _pin_data 2
#define _pin_slk 3
#define _pin_tare 9    // K1
#define _pin_gScale 8  // K2
#define _countsTOoz 7.054792
#define period 3000

/*
#define gTareMinLimit 50000
#define gTareMaxLimit 60000
#define g200MinLimit 130000
#define g200MaxLimit 140000
*/

SSD1306AsciiWire oled;

//-- Declare Globals ------------------------------------------------------------

const int32_t g200CountInit = 133000;  // counts at 200 grams
const int32_t g000CountInit = 56000;
int32_t g000Count;
int32_t g200Count;  // counts at 200 grams

uint g000length = sizeof(g000Count);
byte gEEbaseAddress = 0;

uint32_t gStartMillis;
uint32_t gCurrentMillis;
char disp[14];
uint widthDisp;

//-- Function Prototpyes ------------------------------------------------------------

int32_t getValue(void);
void oledInit(void);

//-------------------------------------------------------------------------------
void setup() {
  pinMode(_pin_data, INPUT);
  pinMode(_pin_slk, OUTPUT);
  pinMode(_pin_tare, INPUT_PULLUP);
  pinMode(_pin_gScale, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 0);  // force ED off else it flickers sometimes

  oledInit();

  // if EEPROM has not been written to, save default values.
  byte value = EEPROM.read(0);
  if (value != 11) {
    EEPROM.put(gEEbaseAddress, 11);
    EEPROM.put(gEEbaseAddress + 1, g000CountInit);
    EEPROM.put(gEEbaseAddress + g000length + 1, g200CountInit);

    // initialize counts on very first startup (when EEPROM empty)
    g000Count = g000CountInit;
    g200Count = g200CountInit;
  } else {
    g000Count = EEPROM.get(gEEbaseAddress + 1, g000Count);
    g200Count = EEPROM.get(gEEbaseAddress + g000length + 1, g200Count);
  }

  oled.clear();
  oled.print("  setup v005.2");
  delay(3000);
  oled.setCursor(2 * 8, 0);
  oled.print(g000Count);
  oled.clearToEOL();
  oled.setCursor(2 * 8, 2);
  oled.print(g200Count);
  delay(1000);

  oled.SSD1306Ascii::setCursor(1 * 8, 0);
  oled.print("grams = ");

  oled.SSD1306Ascii::setCursor(1 * 8, 2);
  oled.print("ounces = ");

}  // --- setup ---------------------------------------------------------------------


//------------------------------------------------------------------------------------

void loop() {

  gCurrentMillis = millis();
  if (gCurrentMillis - gStartMillis >= period) {  //cannot use "millis()" in if statement
    gStartMillis = gCurrentMillis;

    int32_t measCounts = getValue();
 // calculations:
    float weightGrams = (200.0 / (g200Count - g000Count)) * (measCounts - g000Count);
    float weightOz = (7.054792 / (g200Count - g000Count)) * (measCounts - g000Count);
    

    //dtostrf(float_value, min_width, num_digits_after_decimal, where_to_store_string)
 
// display grams on line 1
    dtostrf(weightGrams, 3, 1, disp);
    uint dispLengthChar = strlen(disp);
    uint widthDispPix = oled.fieldWidth(dispLengthChar);
    oled.setCursor(8 * 8, 0);  // should be after units.
    oled.clearToEOL();
    oled.setCursor( 120 - widthDispPix, 0); //dispLengthChar * 8, 0);
    oled.print(weightGrams);

// display ounces on line 2
    dtostrf(weightOz, 3, 1, disp);
    dispLengthChar = strlen(disp);
    widthDispPix = oled.fieldWidth(dispLengthChar);
    oled.setCursor(9 * 8, 2);  // should be after units.
    oled.clearToEOL();
    oled.setCursor( 120 - widthDispPix, 2); //dispLengthChar * 8, 0);
    oled.print(weightOz);

 }
  // Tare function: ---------------------------------------------

  if (digitalRead(_pin_tare) == 0) {
    delay(20);
    if (digitalRead(_pin_tare) == 0) {
      oled.print("... reading tare");
      g000Count = getValue();
      EEPROM.put(gEEbaseAddress + 1, g000Count);
    }
  }

  if (digitalRead(_pin_gScale) == 0) {
    delay(20);
    if (digitalRead(_pin_gScale) == 0) {
      oled.print("... reading Scale");
      g200Count = getValue();
      EEPROM.put(gEEbaseAddress + g000length + 1, g200Count);
    }
  }

}  // --- loop ------------------------------------------------



//--- Functions ----------------------------------------------------------------------

int32 getValue() {
  byte data[3];
  while (digitalRead(_pin_data)) {}  // wait for data pin to go low.

  for (byte j = 3; j--;) {
    for (char i = 8; i--;) {
      digitalWrite(_pin_slk, HIGH);
      bitWrite(data[j], i, digitalRead(_pin_data));
     // add 25th clock pulse to put next reading at gain 128
     //  logic analyzer shows no delay is req'd to meet HX711 input req.
      digitalWrite(_pin_slk, LOW);
    }
  }

  digitalWrite(_pin_slk, HIGH);
  digitalWrite(_pin_slk, LOW);

  // Replicate the most significant bit to pad out a 32-bit signed integer
  uint filler;
  if (data[2] & 0x80) {
    filler = 0xFF;
  } else {
    filler = 0x00;
  }

  // Construct a 32-bit signed integer
  int32 value = (static_cast<unsigned int32>(filler) << 24
                 | static_cast<unsigned int32>(data[2]) << 16
                 | static_cast<unsigned int32>(data[1]) << 8
                 | static_cast<unsigned int32>(data[0]));

  return value;
}


void oledInit(void) {
  Wire.begin();
  Wire.setClock(100000L);

  oled.begin(&Adafruit128x32, I2C_ADDRESS);

  oled.setFont(ZevvPeep8x16);  // Screen = 4 (0,2,4,6) lines X 16 characters (0 to 15)
  uint widthDisp = oled.displayWidth();

  oled.clear();
  oled.displayRemap(true);    // rotate display 180 deg.
}

// --- eof ------------------------------------------------------------------

What is your question?

Don't understand the comment. Please restate.

The bogde library doesn't hide them: its read() returns the signed raw reading.

It also protects the read sequence from corruption by a rogue interrupt and has provisions that make it reliable for use with fast processors (such as ESPs, Teensy, etc.) and with FreeRTOS code.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.