Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
John Bradnam
Published © GPL3+

Electronic Scales

A 3D printed set of scales that uses a 5kg load sensor and HX711 module.

IntermediateFull instructions provided12 hours1,437
Electronic Scales

Things used in this project

Hardware components

Microchip ATtiny1614 Microprocessor
×1
Battery, 3.7 V
Battery, 3.7 V
+ right angle socket
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
6mm shaft + button top
×1
SparkFun Load Cell Amplifier - HX711
SparkFun Load Cell Amplifier - HX711
×1
5kgm Load Cell
with four 12mm M4 Flat head (countersunk) boles
×1
OLED Display 128x32 0.91 inches with I2C Interface
DIYables OLED Display 128x32 0.91 inches with I2C Interface
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

HX711_Test_V1.ino

C/C++
/**************************************************************************
 HX711 Scales With Deep Sleep

 Concept: Indrek Luuk - https://circuitjournal.com/four-wire-load-cell-with-HX711
 
 Schematic & PCB at https://www.hackster.io/john-bradnam/electronic-scales-c97f1d
 
 2022-12-13 John Bradnam (jbrad2089@gmail.com)
   Create program for ATtiny1614

 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 20MHz
  millis()/micros(): "Enabled (default timer)"
  Programmer: jtag2updi (megaTinyCore)

  ATTiny1614 Pins mapped to Ardunio Pins
 
              +--------+
          VCC + 1   14 + GND
  (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
        1 PA5 + 3   12 + PA2 9  (MISO)
  (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
        3 PA7 + 5   10 + PA0 11 (UPDI)
  (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
  (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
              +--------+
  
 **************************************************************************/

#include <U8g2lib.h>
#include <HX711_ADC.h>
#include <EEPROM.h>
#include <avr/sleep.h>

#define SHUTDOWN_TIME 15000  //Timeout before sleep
#define MEASUREMENT_TIME 200 //Set measurement time in milliseconds

#define TXD_PIN 5     //PB2
#define RXD_PIN 4     //PB3
#define SCA_PIN 6     //PB1
#define SCL_PIN 7     //PB0
#define CLK_PIN 10    //PA3
#define DAT_PIN 9     //PA2
#define TRIM_PIN 3    //PA7 - Not used
#define SWITCH_PIN 1  //PA5

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

#define LONG_PRESS_TIME 3000
#define MEASURE_INTERVAL_TIME 500
unsigned long measureTimeout;
unsigned long sleepTimeout;
bool updateScreen = false;

HX711_ADC LoadCell(DAT_PIN, CLK_PIN);
bool tareSet = false;

//EEPROM handling
//Uncomment next line to clear out EEPROM and reset
//#define RESET_EEPROM
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
  uint32_t magic;
  bool calibrated;
  float calibrationValue;
} EEPROM_DATA;

EEPROM_DATA EepromData;      //Current EEPROM settings

char line1[32]; 
char line2[32]; 

//-------------------------------------------------------------------------
// Initialise Hardware
void setup(void)
{
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN),switchInterrupt,CHANGE);

  u8g2.begin();
  showSplashScreen();

  readEepromData();

  LoadCell.begin();
  //LoadCell.setReverseOutput();          //uncomment to turn a negative output value to positive
  unsigned long stabilizingtime = 2000;   // preciscion right after power-up can be improved by adding a few seconds of stabilizing time
  boolean _tare = true;                   //set this to false if you don't want tare to be performed in the next step
  LoadCell.start(stabilizingtime, _tare);
  if (LoadCell.getTareTimeoutFlag() || LoadCell.getSignalTimeoutFlag()) 
  {
    displayLines("HX711", "Timeout");
    delay(3000);
    goToSleep();  
  }
  else 
  {
    LoadCell.setCalFactor(1.0); // user set calibration value (float), initial value 1.0 may be used for this sketch
  }
  while (!LoadCell.update());

  if (EepromData.calibrated)
  {
    LoadCell.setCalFactor(EepromData.calibrationValue);
  }

  measureTimeout = millis() + MEASURE_INTERVAL_TIME;
  sleepTimeout = millis() + SHUTDOWN_TIME;

  goToSleep();  
}

//--------------------------------------------------------------------
// Handle pin change interrupt when SWITCH is pressed
void switchInterrupt()
{
}

//--------------------------------------------------------------------
// Main program loop
void loop(void)
{
  if (millis() < sleepTimeout)
  {
    if (!EepromData.calibrated)
    {
      calibrate(false); //start calibration procedure
    }
    else if (!tareSet)
    {
      calibrate(true); //Set Tare zero
    }
  
    long downTime = testButtonPress();
    if (downTime > 0 && downTime < LONG_PRESS_TIME)
    {
      //zero the scale, wait for tare to finish
      LoadCell.tare();
      sleepTimeout = millis() + SHUTDOWN_TIME;
    }
    else if (downTime > 0)
    {
      //Do a full calibration
      calibrate(false); //start calibration procedure
    }
  
    if (EepromData.calibrated)
    { 
      if (LoadCell.update())
      {
        updateScreen = true;
      }
      if (millis() >= measureTimeout)
      {
        line1[0] = '\0';
        line2[0] = '\0';
        float weight = LoadCell.getData();
        int grams = round(weight);
        int ounces = round(weight / 28.35);
        sprintf(line1,"%dg",grams);
        sprintf(line2,"%doz",ounces);
        displayLines();
        measureTimeout = millis() + MEASURE_INTERVAL_TIME;
        updateScreen = false;
      }
    }
  }
  else
  {
    goToSleep();
  }
}

//--------------------------------------------------------------------
// Show splash screen

void showSplashScreen()
{
  //Splash screen 1
  displayLines("ATtiny1614", "HX711 Scales");
  delay(5000);
}

//--------------------------------------------------------------------
// Display line buffers

void displayLines()
{
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_helvB12_tr); // helvetica bold
  int width = u8g2.getStrWidth(line1);
  u8g2.drawStr((128-width)>>1,14,line1);
  width = u8g2.getStrWidth(line2);
  u8g2.drawStr((128-width)>>1,29,line2);
  u8g2.sendBuffer();
}

//--------------------------------------------------------------------
// Display line buffers

void displayLines(char const* line1, char const* line2)
{
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_helvB12_tr); // helvetica bold
  int width = u8g2.getStrWidth(line1);
  u8g2.drawStr((128-width)>>1,14,line1);
  width = u8g2.getStrWidth(line2);
  u8g2.drawStr((128-width)>>1,29,line2);
  u8g2.sendBuffer();
}

//--------------------------------------------------------------------
// Calibrate the HX711
//  tareOnly - false to do tare and 1kgm weight

void calibrate(bool tareOnly)
{
  displayLines("Set Tare Zero", "Press button");
  waitForButtonPress();
  displayLines("Setting...", "");
  LoadCell.tare();
  displayLines("Tare Zero Set", "");
  tareSet = true;
  delay(2000);

  if (!tareOnly)
  {
    displayLines("Set 1kgm weight", "Press button");
    waitForButtonPress();
    displayLines("Setting...", "");
    
    LoadCell.refreshDataSet(); //refresh the dataset to be sure that the known mass is measured correct
    EepromData.calibrationValue = LoadCell.getNewCalibration(1000); //get the new calibration value
    EepromData.calibrated = true;
    LoadCell.setCalFactor(EepromData.calibrationValue);
    writeEepromData();

    //sprintf(line2,"%d",(int)round(EepromData.calibrationValue));
    //displayLines("Calibration", line2); //97
    displayLines("Calibration", "Complete");
    delay(5000);
  }
  measureTimeout = millis();
  sleepTimeout = millis() + SHUTDOWN_TIME;
}

//--------------------------------------------------------------------
// Wait for button to be pressed

void waitForButtonPress()
{
  bool pressed = false;
  while (!pressed)
  {
    if (digitalRead(SWITCH_PIN) == LOW)
    {
      delay(10);  //Debounce
      if (digitalRead(SWITCH_PIN) == LOW)
      {
        //wait until release
        while (digitalRead(SWITCH_PIN) == LOW);
        pressed = true;
      }
    }
  }
}

//--------------------------------------------------------------------
// Test if button has been pressed
//  returns 0 - not pressed or time button has been pressed in mS

long testButtonPress()
{
  if (digitalRead(SWITCH_PIN) == LOW)
  {
    delay(10);  //Debounce
    if (digitalRead(SWITCH_PIN) == LOW)
    {
      long startTime = millis();
      //wait until release
      while (digitalRead(SWITCH_PIN) == LOW);
      return (millis() - startTime);
    }
  }
  return 0;
}

//--------------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData(void)
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  EEPROM.put(EEPROM_ADDRESS,EepromData);
}

//--------------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData(void)
{
#ifndef RESET_EEPROM
  EEPROM.get(EEPROM_ADDRESS,EepromData);
  if (EepromData.magic != EEPROM_MAGIC)
  {
#endif  
    EepromData.magic = EEPROM_MAGIC;
    EepromData.calibrated = false;
    EepromData.calibrationValue = 1.0;
    writeEepromData();
#ifndef RESET_EEPROM
  }
#endif  
}

//--------------------------------------------------------------------
//Shut down OLED and put ATtiny to sleep
//Will wake up when LEFT button is pressed
void goToSleep() 
{
  u8g2.clearBuffer();         // clear the internal memory
  u8g2.sendBuffer();          // transfer internal memory to the display
  //u8g2.setPowerSave(true);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
  sleep_enable();
  sleep_mode();                         // System actually sleeps here
  sleep_disable();                      // System continues execution here when watchdog timed out
  //u8g2.setPowerSave(false);
  //wait until release
  while (digitalRead(SWITCH_PIN) == LOW);
  showSplashScreen();
  measureTimeout = millis();
  sleepTimeout = millis() + SHUTDOWN_TIME;
  tareSet = false;
}

Credits

John Bradnam
147 projects • 181 followers
Thanks to Indrek Luuk.

Comments