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 ------------------------------------------------------------------