olkal / HX711_ADC

Arduino library for the HX711 24-bit ADC for weight scales
MIT License
234 stars 124 forks source link

Slow Reading Updates #94

Closed ajkochev closed 2 years ago

ajkochev commented 2 years ago

I'm new to Arduino coding so I might be doing something wrong. I've used a lot of your example code and read over your documentation but cannot find an answer.

My code works, but it can take upwards of 5 or 7 seconds after the weight is put on the scale before I get an accurate reading. I can live with a 1 or 2 second delay for an accurate reading but 5 to 7 is too much. I've tried upping the config.h samples line to 32. I'm also using the WeightScale.update() all over my code as well. Still cannot seem to get the reading to be accurate any faster.

I am using a cheap HX711 board and have a Sparkfun one on the way. Hoping this helps as well. Any help is appreciated.

ajkochev commented 2 years ago

` // Version 0.5-2-2022 (Version)-(Month)-(Year) Released // Created by Anthony J. Kochevar for N Scale Working Railcar Weight Station on Thingiverse.com // This code is public domain and can be copied, modified and freely distributed.

include

include // Library for HX711 Sensor and Board: https://github.com/olkal/HX711_ADC

include // Library used: https://github.com/johnrickman/LiquidCrystal_I2C

// Define I2C Address - change if reqiuired. Default is usually 0x27. LiquidCrystal_I2C lcd(0x27,20,4); // Initalize I2C Library for LCD with 16x2(use 20,4) screen.

// Set Button Inputs int TareCalButton = 6; int WeightButton = 7; int SettingsButton = 8;

boolean initCal = false; int timeDelay = 3000; //Set LDC display time per line. 3000 = 3 seconds. Use only 1500, 3000 and 5000 as values. See Settings button in loop function.

unsigned long startTime = 0; unsigned long startButton = 0; unsigned long endButton = 0;

float grams = 0;

boolean secondLine = false; // User to set if second line should be displayed boolean backlightOn = true;

//pins: const int HX711_dout = 4; //mcu > HX711 dout pin const int HX711_sck = 5; //mcu > HX711 sck pin

//HX711 constructor: HX711_ADC WeightScale(HX711_dout, HX711_sck); // Init of library. "WeightScale" will be the name of the scale.

// F() used in serial and LCD output as strings do not need saved in RAM.

void setup() { Serial.begin(57600); lcd.begin(16,2); lcd.backlight(); delay(2000); Serial.println(F("Model Railroad Railcar Weight Scale by Anthony J. Kochevar.")); Serial.println(F("Version 0.5-2-2022")); // Change Version to match top comments. lcd.clear(); lcd.setCursor(0,0); lcd.print(F(" Model Railroad ")); lcd.setCursor(0,1); lcd.print(F(" Weight Scale ")); delay(5000); lcd.clear(); lcd.setCursor(0,0); lcd.print(F(" By AJKochevar")); lcd.setCursor(0,1); lcd.print(F("ver 0.5-2-2022")); // Change Version to match top comments. delay(5000); lcd.clear(); lcd.setCursor(0,0); lcd.print(F("Starting up...")); Serial.println(F("Starting up..."));

pinMode(TareCalButton, INPUT_PULLUP);  // Tare and Callibration Button
pinMode(WeightButton, INPUT_PULLUP);  //  Weight Button
pinMode(SettingsButton, INPUT_PULLUP);  // Settings and Cancel Button

delay(2000);  // Power up stabilizing time of 2 seconds.
WeightScale.begin();
unsigned long stabilizingtime = 2000; // Preciscion right after power-up can be improved by adding a few seconds of stabilizing time.  2000 = 2 Seconds
WeightScale.start(stabilizingtime, true);  // Initalize scale.  Parameter "true" will also tare the scale.

//  Start up error checks for scale
if (WeightScale.getSPS() < 7) {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(F("Sampling to low "));
  lcd.setCursor(0,1);
  lcd.print(F("check wiring."));
  Serial.println(F("!!Sampling rate is lower than specification, check wiring from Arduino to HX711 and pin designations."));
  while(1);  //  Lock Aurduino to require a reboot
}
else if (WeightScale.getSPS() > 100) {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(F("Sampling to high"));
  lcd.setCursor(0,1);
  lcd.print(F("check wiring."));
  Serial.println("!!Sampling rate is higher than specification, check wiring from Arduino to HX711 and pin designations.");
  while(1);  //  Lock Aurduino to require a reboot

} if (WeightScale.getTareTimeoutFlag() || WeightScale.getSignalTimeoutFlag()) { lcd.clear(); lcd.setCursor(0,0); lcd.print(F("Timeout, ")); lcd.setCursor(0,1); lcd.print(F("check wiring.")); Serial.println(F("Timeout getting signal, check wiring from Arduino to HX711 and pin designations.")); while(1); // Lock Arduino to require a restart. } else { WeightScale.setCalFactor(1.0); // Initalize callibration value callibrate_scale(); } }

void tare_scale() { lcd.clear(); Serial.println(F("Tareing... Clear the track and press button.")); startTime = millis(); secondLine = false; while (digitalRead(TareCalButton) == HIGH) { // Wait for track to clear and tare button pressed when done. lcd.setCursor(0,0); lcd.print(F("Tareing... ")); if ((millis() <= startTime + timeDelay) && (secondLine == false)) { lcd.setCursor(0,1); lcd.print(F("Clear the track ")); } else if ((millis() > startTime + timeDelay) && (secondLine == false)) { secondLine = true; startTime = millis(); } if ((millis() <= startTime + timeDelay) && (secondLine == true)) { lcd.setCursor(0,1); lcd.print(F("and press button")); } else if ((millis() > startTime + timeDelay) && (secondLine == true)) { secondLine = false; startTime = millis(); } if (digitalRead(SettingsButton) == LOW) { // Can cancel tare with settings button. Serial.println(F("Tareing cancelled...")); lcd.setCursor(0,0); lcd.print(F("Tareing ")); lcd.setCursor(0,1); lcd.print(F("cancelled... ")); delay(timeDelay); loop(); } } Serial.println(F("Tareing scale...")); lcd.setCursor(0,0); lcd.print(F("Tareing ")); lcd.setCursor(0,1); lcd.print(F("scale... "));

 // Perform tare
 WeightScale.update();
 WeightScale.tareNoDelay();
 WeightScale.tare();  
 delay(timeDelay);

 if (WeightScale.getTareStatus() == true) {
   Serial.println(F("Tareing complete."));
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(F("Tareing"));
   lcd.setCursor(0,1);
   lcd.print(F("complete."));
   delay(timeDelay);
   loop(); 
 }
 else {
   Serial.println(F("Tareing failed."));
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(F("Tareing"));
   lcd.setCursor(0,1);
   lcd.print(F("failed."));
   delay(timeDelay);
   tare_scale(); 
 }

}

void callibrate_scale() { lcd.clear(); Serial.println("Callibrating... Clear the track and press button."); startTime = millis(); secondLine = false; while (digitalRead(TareCalButton) == HIGH) { // Wait for track to clear and tare button pressed when done. lcd.setCursor(0,0); lcd.print(F("Callibrating... ")); if ((millis() <= startTime + timeDelay) && (secondLine == false)) { lcd.setCursor(0,1); lcd.print(F("Clear the track ")); } else if ((millis() > startTime + timeDelay) && (secondLine == false)) { secondLine = true; startTime = millis(); } if ((millis() <= startTime + timeDelay) && (secondLine == true)) { lcd.setCursor(0,1); lcd.print(F("and press button")); } else if ((millis() > startTime + timeDelay) && (secondLine == true)) { secondLine = false; startTime = millis(); } if ((digitalRead(SettingsButton) == LOW) && (initCal == false)) { // Startup callibration needed to get inital values. Callibration cannot be cancelled on startup. Serial.println(F("Startup Callibration cannot be cancelled.")); lcd.setCursor(0,0); lcd.print(F("Startup ")); lcd.setCursor(0,1); lcd.print(F("Callibration ")); delay(timeDelay); lcd.setCursor(0,0); lcd.print(F("cannot be ")); lcd.setCursor(0,1); lcd.print(F("cancelled... ")); delay(timeDelay); } else if ((digitalRead(SettingsButton) == LOW) && (initCal == true)) { // Can cancel with Settings button if startup callibration already done. Serial.println(F("Callibration cancelled...")); lcd.setCursor(0,0); lcd.print(F("Callibration ")); lcd.setCursor(0,1); lcd.print(F("cancelled... ")); delay(timeDelay); loop(); } } Serial.println(F("Tareing first...")); lcd.setCursor(0,1); lcd.print(F("Tareing first..."));

   WeightScale.update();
   WeightScale.tareNoDelay();
   WeightScale.tare();
   delay(2000);

   if (WeightScale.getTareStatus() == true) {
     Serial.println(F("Tare complete."));
     lcd.setCursor(0,1);
     lcd.print(F("Tare complete.  "));
     delay(timeDelay); 
   }
   else {
     Serial.println(F("Tareing failed."));
     lcd.setCursor(0,1);
     lcd.print(F("Taring failed.  "));
     delay(timeDelay);
     callibrate_scale(); 
   }

   delay(timeDelay);
   Serial.println(F("Callibrating..."));
   Serial.println(F("Place 20 gram weight on scale and press button."));
   startTime = millis();
   secondLine = false;
   while (digitalRead(TareCalButton) == HIGH) {  // Wait for 20 gram weight to be placed on track and tare button pressed after.
    lcd.setCursor(0,0);
    lcd.print(F("Callibrating... "));
    if ((millis() <= startTime + timeDelay) && (secondLine == false)) {
      lcd.setCursor(0,1);
      lcd.print(F("Place 20 gram   "));
    }
    else if ((millis() > startTime + timeDelay) && (secondLine == false)) {
      secondLine = true;
      startTime = millis();
    }
    if ((millis() <= startTime + timeDelay) && (secondLine == true)) {
      lcd.setCursor(0,1);
      lcd.print(F("and press button"));
    }
    else if ((millis() > startTime + timeDelay) && (secondLine == true)) {
      secondLine = false;
      startTime = millis();
    }
   }

   Serial.println(("Calibrating now..."));  
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(F("Callibrating    "));
   lcd.setCursor(0,1);
   lcd.print(F("now...          "));

   // Perform Actual callibration.
   WeightScale.update();
   WeightScale.refreshDataSet();  
   float CalibrationValue = WeightScale.getNewCalibration(20);  //  Use 20 gram weight.  Change number if a different weight is used for callibration.
   WeightScale.setCalFactor(CalibrationValue);

   delay(timeDelay);

   startTime = millis();
   secondLine = false;
   Serial.println(("Remove 20 gram weight and press button."));
   while (digitalRead(TareCalButton) == HIGH) {  // Wait for 20 gram weight to be removed on track and tare button pressed after.
    lcd.setCursor(0,0);
    lcd.print(F("Callibrating... "));
    if ((millis() <= startTime + timeDelay) && (secondLine == false)) {
      lcd.setCursor(0,1);
      lcd.print(F("Remove 20 gram  "));
    }
    else if ((millis() > startTime + timeDelay) && (secondLine == false)) {
      secondLine = true;
      startTime = millis();
    }
    if ((millis() <= startTime + timeDelay) && (secondLine == true)) {
      lcd.setCursor(0,1);
      lcd.print(F("and press button"));
    }
    else if ((millis() > startTime + timeDelay) && (secondLine == true)) {
      secondLine = false;
      startTime = millis();
    }
   }

   WeightScale.update();
   WeightScale.tare();
   delay(2000);

   Serial.println(F("Callibration complete."));
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(F("Callibration"));
   lcd.setCursor(0,1);
   lcd.print(F("complete."));
   delay(timeDelay);
   if (initCal == false) {
     lcd.clear();
     lcd.setCursor(0,0);
     lcd.print(F("Startup done..."));
     Serial.println(F("Startup done."));
     initCal = true; // Set startup callibration to true so future callibrations can be cancelled.
     delay(timeDelay);
   }
   loop();
  }

void get_weight() { lcd.clear(); lcd.setCursor(0,0); lcd.print(F("Railcar Weight:")); lcd.setCursor(0,1); delay(1000); // 1 second pause to stableize weight reading

 //  Get weight on scale
 for (int getWeight =0; getWeight < 20; getWeight ++) {
   WeightScale.update();
   grams = WeightScale.getData();
 }   
 if (grams <= 0) {  //  Used to avoid negitive on display.
  grams = 0;
 }
 float ounces = (grams*(0.035));

 Serial.println(F("Railcar Weight:"));
 Serial.print(grams, 1);  //Prints grams with one decimal place.
 Serial.println("  grams");
 Serial.print(ounces, 1);  //Prints ounces with one decimal place.
 Serial.println("  ounces");
 startTime = millis();
 secondLine = false;
 lcd.setCursor(0,1);
 lcd.print(grams, 1);  //Prints grams with one decimal place.
 lcd.print(" gr");
 lcd.setCursor(9,1);
 lcd.print(ounces ,1);
 lcd.print(" oz");  //Prints ounces with one decimal place.
 while (digitalRead(WeightButton) == LOW) {   // Keeps weight on LCD display while weight button is pressed
    }
 delay(timeDelay);  // Keep weight on display a few seconds after button released
 loop();

}

void loop (){ lcd.clear(); Serial.println(F("Scale Ready... place rail car on track and press button.")); lcd.setCursor(0,0); lcd.print(F("Scale Ready...")); boolean TareButtonPress = false; boolean SettingsButtonPress = false; startTime = millis(); startButton = millis(); secondLine = false;

// Infinite loop waiting for and monitoring button presses
while (1) {   
  if ((millis() <= startTime + timeDelay) && (secondLine == false)) {
    lcd.setCursor(0,1);
    lcd.print(F("Place railcar   "));
  }
  else if ((millis() > startTime + timeDelay) && (secondLine == false)) {
    secondLine = true;
    startTime = millis();
  }
  if ((millis() <= startTime + timeDelay) && (secondLine == true)) {
    lcd.setCursor(0,1);
    lcd.print(F("and press button"));
  }
  else if ((millis() > startTime + timeDelay) && (secondLine == true)) {
    secondLine = false;
    startTime = millis();
  }

  //  Read if Tare button is pressed. If pressed and held for 2 to 5 seconds, start Tare function.
  //  If button is pressed for 5 seconds or more start Calibration function.
  //  These timers are to help prevent accidental calling ot the Tare or callibration function by the user
  if ((digitalRead(TareCalButton) == LOW) && (TareButtonPress == false)) { 
    startButton = millis();
    endButton = millis();
    TareButtonPress = true;
    }
  else if ((digitalRead(TareCalButton) == HIGH) && (TareButtonPress == true)) {
    if (endButton < startButton + 2000) {
      TareButtonPress = false; 
    }
    if ((endButton >= startButton + 2000) && (endButton < startButton + 5000)) {  //  Call Tare function if button pressed for over 2 seconds and less than 5 seconds.
      TareButtonPress = false;
      tare_scale();
    }
    else if (endButton >= startButton + 5000) {  //  Call Calibration function if button pressed for over 5 seconds.
      TareButtonPress = false;
      callibrate_scale();
    }
  }

  // Check weight button and start weight function if pressed
  WeightScale.update();  //  Often update weight data in while loop to keep data current
  if (digitalRead(WeightButton) == LOW) {
    get_weight();
  }

  //  Set LCD Backlight on and off if settings button pressed for less than 2 seconds
  //  If pressed for more than 2 seconds cycle through setting 1.5, 3 and 5 seconds LCD line pause settings.
  if ((digitalRead(SettingsButton) == LOW) && (SettingsButtonPress == false)) { 
    startButton = millis();
    endButton = millis();
    SettingsButtonPress = true;
    }
  else if ((digitalRead(SettingsButton) == HIGH) && (SettingsButtonPress == true)) {
    if (endButton < startButton + 2000) {  // Turn on or off LCD backlight if button pressed for less than 2 seconds
      SettingsButtonPress = false;
      if (backlightOn == true) {  
        lcd.noBacklight();
        backlightOn = false;
        delay(500);
      }
      else if (backlightOn == false) {
        lcd.backlight();
        backlightOn = true;
        delay(500);
      } 
    }
    if ((endButton >= startButton + 2000) && (endButton < startButton + 5000)) {  //  Cycle through the three LCD display pause times if settings button is pressed for more than 2 seconds
      SettingsButtonPress = false;
      lcd.clear();
      Serial.println(F("LCD second line pause set to:"));
      lcd.setCursor(0,0);
      lcd.print(F("LCD time set to:"));
      if (timeDelay == 1500) {
        timeDelay = 3000;
        Serial.println(F("3 seconds"));
        lcd.setCursor(0,1);
        lcd.print(F("3 seconds  "));
        delay(timeDelay);
        loop();
      }
      if (timeDelay == 3000) {
        SettingsButtonPress = false;
        timeDelay = 5000;
        Serial.println(F("5 seconds"));
        lcd.setCursor(0,1);
        lcd.print(F("5 seconds  "));
        delay(timeDelay);
        loop();
      }
      if (timeDelay == 5000) {
        SettingsButtonPress = false;
        timeDelay = 1500;
        Serial.println(F("1.5 seconds  "));
        lcd.setCursor(0,1);
        lcd.print(F("1.5 seconds  "));
        delay(timeDelay);
        loop();
      }
    }
    if (endButton >= startButton + 5000) {  // Possible future routine for settings button
      SettingsButtonPress = false;
    }

  }
  WeightScale.update();  //  Always update weight data in while loop to keep data current
  endButton = millis();  //  Always set endButton time in the main while loop for button beening pressed and held.
}

} `

olkal commented 2 years ago

Hi! Ordering a quality board from Sparkfun is a good idea, but I doubt it will be any faster. I'l try to help you.

First of all: never call loop() from anywhere in your code. You are creating a new loop inside itself over and over again (recursion) eating up all available RAM. If you vant to exit from a function you should call return.

There is a lot of code here but I have only looked at the function get_weight():

It's possible to speed up the HX711 itself by connecting an external 20mhz crystal, but I don't think you will need to do that.

Also, you use delay a lot in your code. Some places it might be okay, especially for testing, but it can make your code sluggish. If you need something to happen in a sequence with a delay in between you may consider using some kind of task scheduler. There are libraries for this but you can also make it yourself, see the simple example below.

//example code

int taskCounter = 0;
long taskTimer = 0;

void taskMachine() {
  if (taskTimer < millis()) {
    switch (taskCounter) {
      case 1:
        task1();
        taskTimer = 2000 + millis();
        taskCounter++;
        break;
      case 2:
        task2();
        taskTimer = 3000 + millis();
        taskCounter++;
        break;
      case 3:
        task3();
        taskCounter = 0;
        break;
    }
  }
}

void task1() {
  Serial.println("This is task 1. Wait 2 seconds, then do task 2");
}

void task2() {
  Serial.println("This is task 2. Wait 3 seconds, then do task 3");
}

void task3() {
  Serial.println("This is task 3. Done.");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  taskCounter = 1;
}

void loop() {
  // check if there are any tasks to do
  if (taskCounter) {
    taskMachine();
  }

  // do something else in the loop while waiting
  static long next_update = 0;
  if ((millis() > next_update) && taskCounter && (taskTimer > 0)) {
    Serial.println(taskTimer - millis());
    next_update = millis() + 250;
  }
}

Result:

This is task 1. Wait 2 seconds, then do task 2
2000
1749
1498
1247
996
745
494
243
This is task 2. Wait 3 seconds, then do task 3
2993
2742
2491
2240
1989
1738
1487
1236
985
734
483
232
This is task 3. Done.
ajkochev commented 2 years ago

Thank you for the help. Changing to the refreshDataSet() helped a lot. I'm new to Arduino. I'm hoping you can help me further to get this project working. I would be really greatful. I really like this library and it's the only one I've been able to get working with this project.

As for callingloop() I triedreturn but it broke what is being displayed on the LCD at times. I need, once a function ends, to return to the start of loop to get the screen setup correctly. Could I move everything in loop to its own function and just have loop call this function to prevent the memory issue?

I am using millis() in my code at times to wait while doing other things. My project is likely to be used mostly by older gentlemen, text needs to pause at times and not proceed so they know what to do at times. I built in an adjustable setting so they can speed up the delays or slow them down more.

I got my code working on the cheap HX711 board with your help. I tried the Sparkfun board, but on boot up I get the "Sample to high" error in my code. I removed the rate connection on the back of the board, I think I set it to open. No error on bootup but weight readings are not accurate. I think I'm not understanding "samples" in the project and how to get them set correctly.

olkal commented 2 years ago

Hi! When you exit from a function called from the loop you will continue in the loop where you left, you can not jump and start on top. But this is easy to solve; just put the code for correcting the LCD etc into a function and call that function before you return to the loop. Like this:

void myFunction() {
  //do something...
  //write to the LCD etc...
  doBeforeReturningToLoop();
}

void doBeforeReturningToLoop() {
  lcd.clear();
  Serial.println(F("Scale Ready... place rail car on track and press button."));
  //and so on...
}

void loop() {
  if (someCondition) {
    myFunction();
  }
}

The sparkfun board shall have the rate jumper closed for 10SPS rate, you should close it again. If you run the test.ino sketch you should get "HX711 measured sampling rate HZ:" value of 10-11. If not I think there is something else going on, double check your wiring.

ajkochev commented 2 years ago

Thank you oklal for all your help on this project! I got everything working really well now.
Making the changes to the sketch to use returns and not call loop seemed to get everything stable. I got my Sparkfun board to work too. I kept the jumper open, so I think I'm operating at 80 sps.

I added the following to my code just before reading the data:

while (WeightScale.update()) {}; // Update scale weight data until smooth. WeightScale.refreshDataSet();

Reading weight now only takes a partial second and is always perfectly accurate. This wasn't the case sometimes.

Thanks again for all the help to an Arduino noob.

olkal commented 2 years ago

That's great!

Just to explain the trade-off between speed and accuracy:

HX711 hardware setting: The HX711 can operate at LOW rate 10SPS (samples/second) or HIGH rate 80SPS. LOW rate is more accurate but obviously slower than HIGH rate. The HIGH rate will increase the microcontroller's workload as it will need to read the conversion values from the HX711 8 times more often to keep up.

Library file config.h setting: #define SAMPLES defines the size of the library data set used for calculating the rolling average output value. A high value is more accurate but slower. A high value also takes up more dynamic memory from the microcontroller.

Unless you need very fast settling time you will usually have best results by setting the HX711 rate setting to LOW/10SPS, and then adjust #define SAMPLES value in config.h to achieve an acceptable balance between speed and accuracy. This way you are basically moving a lot of the workload from the microcontroller to the HX711.

But if you now have it working and are happy with both accuracy and speed, perhaps it's better to keep it as it is.

olkal commented 2 years ago

Issue considered solved.