Read a scale every time a hall effect detect is made at high speed

What I’m trying to do:

I have a magnet mounted to a rotating shaft that is being turned by hand and detected by a Hall Effect sensor (US1881). When the magnet is detected, I would like to record the current count as well as a load reading from a scale/load cell (HX711) to an SD card all using a nano. The data (turns and scale value) must also be printed to a small LCD screen (0802 LCD I2C). I also need the scale reading to be displayed to the LCD screen even if no magnet is being detected, ie., if I stop turning the crank/shaft for a time and then resume, or if I leave and come back and put load on it.

Other factors/the problem:

The load cell gives somewhat noisy readings, so I have implemented a running average of 32 points that seems to give me a stable reading when I am using the scale only. I tried different averaging methods/libraries and “runningaverage.h” seems to be the best. However, when I turn the crank at the highest speed that I would use in operation, ie, near 80-100 Hz, the board does not detect all of the magnet passes. I have a mechanical counter on the unit to verify the number of turns (magnet detects) put in. In reality, the speed that I turn the crank would be in the range of 40 Hz, but I want to be able to make it work for up to 80-100 Hz so that I do not lose any data points.

Things I’ve verified:

I verified that I am using the HX711 board in 80 Hz mode; when I time a void loop with just the scale reading, all the times are in the range of 10-11 ms, which is a bit faster than 80 Hz. It also seems to give me the same loop time when I increase the number of points that I am averaging, indicating that the average does not slow down the scale reading. I also increased the LCD speed to 400 kHz in case that could be a cause. I also added the “digitalWriteFast.h” library in an effort to speed things up.

What I have tried:

After reading the thread about “several things at the same time ” I tried using millis() to limit the time for the scale loop thinking it would speed things up. I currently do not have the SD card loop in there because the current code doesn’t record all the magnet detects. My current code is the in the next post.

#include "HX711.h"
#include <Wire.h>
#include "RunningAverage.h"
#include <digitalWriteFast.h>
#include "SdFat.h"
#include <SPI.h>
#include <LiquidCrystal_I2C.h>

RunningAverage myRA(32);

LiquidCrystal_I2C lcd(0x3f, 8, 2);

//Load cell setup
#define DOUT 3
#define CLK  4
HX711 scale(DOUT, CLK);
float calibration_factor = 6588;
float ounces;
float ounces_avg;
unsigned long currentMillis = 0;

unsigned long previousScaleMillis = 0; // time when button press last checked
const int scaleInterval = 4; // number of millisecs between button readings

int kount = 0;
boolean kflag = LOW;

//Hall Effect Setup
const int hallPin = 2; // read the hall effect sensor on pin 9

File logfile;
SdFat sd;

const int chipSelect = 10;

void setup()
{
  pinMode(hallPin, INPUT);
  
  Serial.begin(115200);
  lcd.begin();
  lcd.setCursor(0, 0);
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  if (!sd.begin(chipSelect)) {
    Serial.println("Card failed");
    // don't do anything more:
//    return;
  }
  Serial.println("card initialized.");

  // create a new file
  char filename[] = "LOG_00.CSV";
  for (uint8_t i = 0; i < 100; i++) {
    filename[4] = i/10 + '0';
    filename[5] = i%10 + '0';
   
    if (! sd.exists(filename)) {
      logfile = sd.open(filename, O_WRITE | O_CREAT ); 
      logfile.println("Turns, Weight (oz)");  
      break;  // leave the loop!
    }
  }
//  if (! logfile) {
//    error("couldnt create file");
//    }
    
    Serial.print("Logging to: ");
    Serial.println(filename);
    
  //Scale
  scale.set_scale();
  scale.tare();  //Reset the scale to 0
  long zero_factor = scale.read_average(); //Get a baseline reading
  scale.set_scale(calibration_factor); //Adjust to this calibration factor
  
  myRA.clear(); // explicitly start clean on Running Avg
  lcd.clear();
  lcd.print(filename);
  delay(2000);
  lcd.clear();
}

void loop() 
{
  currentMillis = millis();
  
  checkWeight();
  turnCount();
//  sdWrite();
}

void checkWeight() 
  {
  if (millis() - previousScaleMillis >= scaleInterval) 
  {
    ounces = scale.get_units()*0.035274; 
    myRA.addValue(ounces);
    ounces_avg = (myRA.getFastAverage());
    
    if (isnan(ounces_avg) || ounces_avg < 0.00)
     {
      ounces_avg = 0.00;   // set ounces to zero if the reading is NaN - Not a Number, or lower than zero
     }
     
    lcd.setCursor(0, 0);
    lcd.print(ounces_avg);
    lcd.print("oz");

    lcd.setCursor(4, 1);
    lcd.print("T");
    lcd.setCursor(0, 1);   // set the cursor to column 0, line
    lcd.print(kount);
    previousScaleMillis += scaleInterval;
  }
}

int turnCount() 
{
    if (digitalReadFast(hallPin) == HIGH)
      {
        kflag = HIGH;
      }
    if (digitalReadFast(hallPin) == LOW && kflag == HIGH)
      {
        kount++;
        kflag = LOW;
      }
  }

I also tried just doing LCD print, the magnet detect, read the scale value (with no averaging) , SD card save, at around 80-100 Hz, all in one void loop. The good part is that this configuration records everything at that speed. However with this configuration I cannot switch back to reading the scale alone without any magnet detects, along with the fact that the scale reading is not averaged. Here is this code:

#include "HX711.h"
#include <Wire.h>
#include "RunningAverage.h"
#include <digitalWriteFast.h>
#include "SdFat.h"
#include <SPI.h>
#include <LiquidCrystal_I2C.h>

//RunningAverage myRA(32);

LiquidCrystal_I2C lcd(0x3f, 8, 2);

//Load cell setup
#define DOUT 3
#define CLK  4
HX711 scale(DOUT, CLK);
float calibration_factor = 6588;
float units;
float ounces;
volatile float ounces_avg;

int kount = 0;
boolean kflag = LOW;
unsigned long Timer1 ; //dont give it a value.

//Hall Effect Setup
const int hallPin = 2; // read the hall effect sensor on pin 9

File logfile;
SdFat sd;

const int chipSelect = 10;

#define ECHO_TO_SERIAL 0 // echo data to serial port

void setup()
{
  pinMode(hallPin, INPUT);
  
  Serial.begin(115200);
  lcd.begin();
  lcd.setCursor(0, 0);
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  if (!sd.begin(chipSelect)) {
    Serial.println("Card failed");// don't do anything more:
  }
  Serial.println("card initialized.");

  // create a new file
  char filename[] = "LOG_00.CSV";
  for (uint8_t i = 0; i < 100; i++) {
    filename[4] = i/10 + '0';
    filename[5] = i%10 + '0';
   
    if (! sd.exists(filename)) {
      logfile = sd.open(filename, O_WRITE | O_CREAT ); 
      logfile.println("Turns, Weight (oz)");  
      break;  // leave the loop!
    }
  }
//  if (! logfile) {
//    error("couldnt create file");
//    }
    
    #if ECHO_TO_SERIAL
    Serial.print("Logging to: ");
    Serial.println(filename);
    #endif
  
  //Scale
  scale.set_scale();
  scale.tare();  //Reset the scale to 0
  long zero_factor = scale.read_average(); //Get a baseline reading
  scale.set_scale(calibration_factor); //Adjust to this calibration factor
  
//  myRA.clear(); // explicitly start clean on Running Avg
  lcd.clear();
  lcd.print(filename);
  delay(2000);
  lcd.clear();
}

void loop()
{
    if (digitalReadFast(hallPin) == HIGH)
    {
      kflag = HIGH;
    }
    if (digitalReadFast(hallPin) == LOW && kflag == HIGH)
    {
      units = scale.get_units(); 
      ounces_avg = units*0.035274; //non smoothed ounces. not a true avg
//      myRA.addValue(ounces);
//      ounces_avg = (myRA.getFastAverage());
    
      if (isnan(ounces_avg) || ounces_avg < 0.00)
     {
      ounces_avg = 0.00;   // set ounces to zero if the reading is NaN - Not a Number, or lower than zero
     }
    
      lcd.setCursor(0, 0);
      lcd.print(ounces_avg);
      lcd.print("oz");

      lcd.setCursor(4, 1);
      lcd.print("T");
      lcd.setCursor(0, 1);   // set the cursor to column 0, line
      lcd.print(kount);

      logfile.print(kount);
      logfile.print(", ");
      logfile.println(ounces_avg);

      kount++;
      Timer1 = millis() ;
      kflag = LOW;
    }
    if ((unsigned long)(millis() - Timer1) > 20000)
    {
    logfile.close();
    }
}

Any ideas? I would be fine if I had to forgo averaging the scale reading during recurring magnet detects and possibly post-smooth, but I would definitely need it to be averaged during the “live readout” mode when no magnet is being detected. I feel like this must be doable but I don’t have code running as efficiently as needed.

This is a case where using an ISR to capture the hall switch output would be advised - then you won't miss a count even if you are spending time talking to LCD or SDcard.

Quetzalcoatl: I verified that I am using the HX711 board in 80 Hz mode; when I time a void loop with just the scale reading, all the times are in the range of 10-11 ms, which is a bit faster than 80 Hz.

I am not familiar with the scale you are using or its library but I am aware from other Threads that similar libraries were written without any consideration for speed so they use blocking code. It may be possible to break the read into two parts - start a reading and some millisecs later go back and collect the result. If so the total Arduino time involved would be reduced to a number of microsecs leaving it a lot more time to do other useful stuff.

Plus what @MarkT has said.

...R