Automatic "salt" dispenser

Hi All,

im building little DIY project for home use. i am building a automatic dispenser and currently using Arduino Uno, 4x4 keypad, 16x2 lcd, 1kg load cell, hx711, nema 17 and a DRV8825

so far i have manage to get the nema 17 to dispense when i insert a target value, the one part i cant seem to find a straight answer on is the accuracy of the scale. if i dispense 50g of salt, i get a value that fluctuates between 50.1 and 49.9 which doesn't settle. if i change the mode from gram (g) to grain (gr) the fluctuation is even worse jumping two or three grain in either direction.

ive shortened the wiring (its now about 1cm) from the hx711 to the arduino, im using shielded mylar wire from the hx711 to the load cell, ive soldered the wires onto the hx711 instead of using pins. ive added a 100uF capacitor across the VCC and GND of the amplifier, made the GND common between the the arduino, amp and driver. im powering the Arduino and motor from a 12v 3A psu but still cant get this value to be stable.

i dont know if there is something i can do in the code that can make it more stable but i have been replying on AI to assist with the code and most of the attempts ive made to adjust the code either mess with the motor or make inserting values on the LCD extremely latent

Can anyone point me in the right direction here, not sure what else i can do?

this is the most recent code i have been using

#include <HX711.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <AccelStepper.h>

// --- PIN ASSIGNMENTS ---
#define HX711_DT  A0
#define HX711_SCK A2   
#define STEP_PIN  13   
#define DIR_PIN   A3   
#define EN_PIN    12   

// --- KEYPAD CONFIG ---
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'1','2','3','A'}, {'4','5','6','B'}, {'7','8','9','C'}, {'*','0','#','D'}
};
byte rowPins[ROWS] = {11, 10, 9, 8}; 
byte colPins[COLS] = {7, 6, 5, 4}; 

// --- OBJECT INITIALIZATION ---
HX711 scale;
LiquidCrystal_I2C lcd(0x27, 16, 2); 
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
AccelStepper auger(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

// --- GLOBAL VARIABLES ---
float targetWeight = 0;
String inputString = "";
float calibration_factor = 420.0; 

void setup() {
  Serial.begin(9600);
  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, HIGH); 

  lcd.init();
  lcd.backlight();
  lcd.print("System Ready");

  scale.begin(HX711_DT, HX711_SCK);
  if (scale.wait_ready_timeout(1500)) {
    scale.set_scale(calibration_factor);
    scale.tare();
  }

  auger.setMaxSpeed(4000); 
  auger.setAcceleration(2000);
  delay(1000);
  lcd.clear();
}

void loop() {
  char key = keypad.getKey();
  if (key >= '0' && key <= '9') { inputString += key; targetWeight = inputString.toFloat(); }
  else if (key == '*') { inputString += "."; }
  else if (key == '#') { inputString = ""; targetWeight = 0; scale.tare(); }
  else if (key == 'A' && targetWeight > 0) { runDispenser(); }

  static unsigned long lastIdle = 0;
  if (millis() - lastIdle > 400) {
    lcd.setCursor(0, 0); lcd.print("Set: "); lcd.print(targetWeight, 1); lcd.print("g  ");
    lcd.setCursor(0, 1); lcd.print("Wt : "); if(scale.is_ready()) lcd.print(scale.get_units(1), 1); lcd.print("g  ");
    lastIdle = millis();
  }
}

void runDispenser() {
  lcd.clear();
  digitalWrite(EN_PIN, LOW); // ENABLE MOTOR
  
  float current = 0;
  unsigned long lastLcdUpdate = 0;
  unsigned long lastScaleRead = 0;

  while (true) {
    // 1. STABILIZED SCALE READING (Median-style average of 3)
    if (millis() - lastScaleRead > 100) { 
      if (scale.is_ready()) {
        current = scale.get_units(3); 
      }
      lastScaleRead = millis();
    }
    
    float remaining = targetWeight - current;

    // 2. STOP CONDITION
    if (remaining <= 0.0) break; 

    // 3. ADAPTIVE SPEED (Fast, then Mid, then Trickle)
    if (remaining > 5.0) {
      auger.setSpeed(2000); 
    } else if (remaining > 1.0) {
      auger.setSpeed(600);  
    } else {
      auger.setSpeed(150);  
    }

    auger.runSpeed();

    // 4. LCD UPDATE
    if (millis() - lastLcdUpdate > 400) {
      lcd.setCursor(0, 0); lcd.print("POUR: "); lcd.print(current, 1); lcd.print("g  ");
      lastLcdUpdate = millis();
    }

    if (keypad.getKey() == 'B') break;
  }

  // --- SHUTDOWN & SETTLE ---
  auger.setSpeed(0);
  digitalWrite(EN_PIN, HIGH); 
  
  lcd.clear();
  lcd.print("Finalizing...");
  delay(1500); // Important for noise to die down
  
  float final = scale.get_units(10);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("FINAL: "); lcd.print(final, 2);
  lcd.setCursor(0,1);
  lcd.print("DONE!");
  
  delay(3000);
  inputString = ""; 
  lcd.clear();
}

For a 1kg load cell I would consider a 0.1g variation excellent! You can take several readings and take the average.

1 Like

Everything you have and have built has and error range. You cannot eliminate that, but can reduce the value for each device/function.

That sounds like perfectly normal measurement noise. Average a few values for improved stability and precision.

1 Like

Considering 15 grain to 1 gram, and your fluctuation is 3 grain, I would use "grain" as the unit of measure and absorb the "two or three" by rounding to the nearest 5 grain.

Try adding a 100nF across the VCC and Ground, 100uF gives you bulk capacitance but not high frequency decoupling.

2 Likes

Instead of an average to stop annoying fluctuations, you might like to use a low-pass filter or "leaky integrator".

It's a very simple process involving only the current reading and the newly acquired reading.

It can be tuned to respond differently.

Here's a demo

The essence of the algorithm is these lines:

  int sensorValue = analogRead(analogInPin);
  float newReading = sensorValue * 5.0 / 1024;

  leakyAverage = alpha * leakyAverage + (1 - alpha) * newReading;

With alpha controlling the rate of convergence, a value between 0.0 and 1.0, typically closer to 1.0.

"Most of the old value plus a bit of the new value".

a7

There are many "smoothing / averaging" algorithms on the WWW, search "Arduino smoothing".

Google "Arduino smoothing"

thanks for the quick responses, i hoping to get it accurate with 0.01 of a grain.

thank for this, i will looking into this

That's less than one milligram. I can't imagine application where 1kg load cell is used for sub mg dosing. How would your mechanical part be in line with this accuracy?

1 Like

All depends on the accuracy of your load cell but I doubt if you will find a 1kg loadcell with that kind of accuracy.
I think you should be happy that you achieved 0.1g

Interesting project. Is there ANY mechanical connections/movements involved that might have errors in movement/reactions that can cause the project to have less accuracy than you desire? What does your stepper motor actually do and what uncertainty is there in it’s operation? Are you using something other than full steps? Because full steps are the only movements that give identical movements.

And that's not accounting for air currents - unless it's under a bell jar.