Load cell - smoothing and using values in loop

Hi all,

I’m working on a project to use a load cell to determine whether a stepper motor should turn in one direction or the other depending on whether the load value has changed since the last reading. I’m using the load cell code http://cerulean.dk/words/?page_id=42, which is working fine and giving me a good amount of sensitivity in the load cell (able to detect changes of 100grams or so). Hardware setup is two cells in a half wheatstone bridge, both output wires going into an INA25p diff amp and then through an rc filter. For the time being the stepper isn’t connected and I’m using the serial print to simulate it.

The problem is that when I use the above code in a function (checkWeight) and then try to make the stepper move the analog value seems to become cumulative and any output from the motors is garbage because of this. There’s also an issue with the analog values and load being slightly inconsistent, probably due to rounding of the float values to 1 decimal place. I did this to allow an easier comparison of past and current values, but any advice on how to solve this would be great!

Code is as follows:

/*     
     Using INA125A to amplify signal from load cells & an
     H-Bridge to control a bi-polar stepper
  */

//Stepper Declarations     
#include <Stepper.h>

const int stepsPerRevolution = 50;    // change this to fit the number of steps per revolution
const int enable12 = 9;               //bridge 1 and 2 enable (pin 1 on the 16 pin IC) 
const int enable34 = 10;              //bridge 3 and 4 enable (pin 9 on the 16 pin IC)

const int LOOPS    = 5;
const int LOOPSTEP = 5;

int distanceForward = 0; // distance moved since last weight change

Stepper myStepper(stepsPerRevolution, 2, 3, 4, 5);  //declares a stepper motor with coil A connected to out 1 and out 2 (arduino pin 2 and 3)
      							                                //and coil B connected to out 3 and out 4 (arduino pin 4 and 5)

int stepCount = 0;         // number of steps the motor has taken
 
//WEIGHT DECLARATIONS
float loadA = 0.4; // kg
int analogvalA = 77.8; // analog reading taken with load A on the load cell

float loadB = 1.3; // kg 
int analogvalB = 86.8; // analog reading taken with load B on the load cell

// Upload the sketch again, and confirm, that the kilo-reading from the serial output now is correct, using your known loads
float analogValueAverage = 0;

// How often do we do readings?
long time = 0; // 
int timeBetweenReadings = 3000; // We want a reading every 200 ms;
int currentLoop = 0;

float currentLoad = 0;
float previousLoad = 0;
  
void setup()
{
  
  Serial.begin(9600);
  pinMode(enable12, OUTPUT);    //Set the enable12 pin to Output
  pinMode(enable34, OUTPUT);    //Set the enable34 pin to Output  
  digitalWrite(enable12, HIGH); //Set the enable12 pin HIGH to turn the bridges on
  digitalWrite(enable34, HIGH); //Set the enable34 pin HIGH to turn the bridges on  
  myStepper.setSpeed(20);         // set the speed of the motor to 20 RPMs
  

  Serial.println ("Weight start up....");
  
	
}


void loop (){
  checkWeight();
 /* delay (timeBetweenReadings);

   {
  if (currentLoad == previousLoad)  //If current load is the same as previous move the stepper forward and add to total
  {
    myStepper.step(LOOPSTEP);
    distanceForward = distanceForward + LOOPSTEP;
    Serial.println("Stepper moved forward: ");
    Serial.println(LOOPSTEP);
    Serial.println("Total moved forward: ");
    Serial.println(distanceForward);
  }
  else                               //Otherwise move stepper backwards the total it has moved forwards
  { myStepper.setSpeed(40);
    myStepper.step(-distanceForward);
    Serial.println("Stepper moved back: ");}
     Serial.println(-distanceForward);
     distanceForward = 0;
   }*/
  
   
}
  

void checkWeight(){

  float analogValue = analogRead(0);

  // running average - We smooth the readings a little bit
  analogValueAverage = 0.9*analogValueAverage + 0.1*analogValue;

  // Is it time to print? 
  if(millis() > time + timeBetweenReadings){
    float load = analogToLoad(analogValueAverage);

    currentLoad = load;

    Serial.println("analogValue: ");
    Serial.println(analogValueAverage);
    Serial.println("             currentLoad: ");
    Serial.println(currentLoad,1);
    Serial.println("             previousLoad: ");
    Serial.println(previousLoad,1);
    time = millis();
    
   previousLoad = currentLoad;
    
  }
   
}

float analogToLoad(float analogval){

  // using a custom map-function, because the standard arduino map function only uses int
  float load = mapfloat(analogval, analogvalA, analogvalB, loadA, loadB);
  return load;
  
}

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Once this is solved I want to add a RTC module to run the program every hour, rather than relying on the delay function, but that’s another problem for the future!

Cheers,

Your averaging routine is averaging all the results since the dawn of time. You really don't want to do that. What you want is what is called a rolling average, or running average, which averages the last X readings (where X is determined by you - bigger X = smoother readings but slower change).

You need to store the past X values in an array, and create an average of that array. For every new reading you need to drop the oldest reading from the array and add the newest.

I made a library a bit back to do it:

http://hacking.majenko.co.uk/libs/average

Feel free to help yourself - use it, butcher it, use it as a starting point for your own code, whatever ;)

Fantastic, I'll try that out. The strange thing is that when the code is as above the values seem to be pretty consistent, but as soon as I add the commented out sections in the loop it starts acting crazy.

I'll upload the new code once I have butchered it!

The first time thru CheckWeight(), AnalogAverageValue is equal to 0.0 This causes it to slowly "wind up" as you take samples. You should prime it with a call to AnalogRead() instead of setting it to 0.

Afremont, that's solved the cumulative error, thanks so much. I still need to implement the running average to make sure the results are consistent, will upload new code asap.

If you only want some smoothing, and don't need to have a precise mathematical average of the last N readings, then an easier way to achieve this is to use a decaying average, for example:

float previousAverage, rawValue, newAverage;
...

newAverage = (0.9 * previousAverage) + (0.1 * rawValue);

You can vary the amount of smoothing by altering the 0.9 and 0.1, just make sure they add up to one.

If you're using integer values rather than floats then you can achieve a similar effect by scaling:

int previousAverage, rawValue, newAverage;
...

newAverage = ((9 * previousAverage) + (1 * rawValue)) / 10;

Again, make sure that the sum of the two weights divided by the scaling factor equals one.

Thanks for all the advice, I'm getting relatively good values from the sensor now, but still with a little drift in the region of 0.1 difference between readings. Majenko - I'm not 100% sure how to implement your library for the rolling average. I've tried to put it into the code, but the results are odd (incrementing again), so I'm still using the original averaging in the code I borrowed. The checkWeight function is below, with the failed use of the rolling average commented out.

void checkWeight(){

  float analogValue = analogRead(0);
  float analogValueAverage = analogRead(0);


  // running average - We smooth the readings a little bit
    analogValueAverage = 0.9*analogValueAverage + 0.1*analogValue;
     
     //rollingaverage?
    
   //analogValueAverage = rollingAverage(averageArray, 10, analogValue);
     
  
  // Is it time to print? 
  if(millis() > time + timeBetweenReadings){
    float load = analogToLoad(analogValueAverage);
    
    currentLoad = load;

    Serial.println("analogValue: ");
    Serial.println(analogValueAverage);
    Serial.println("             currentLoad: ");
    Serial.println(currentLoad,1);
    Serial.println("             previousLoad: ");
    Serial.println(previousLoad,1);
    time = millis();
    
   previousLoad = currentLoad;
    
  }

As I need to get 100% reliable readings (i.e. the weight on the scale produces the same output each and every time) would it better to average the load itself over the course of say ten readings?

Thanks for all the help - I just got my RTC and am working out how to use this to control when the readings are taken, but it seems to be relatively simple. More updates when I have something a bit more functional!

I don’t know if you have figured all this out yet, but I will throw my 2 cents in anyway.

I have used this code for years and I have found no easier and more efficient way to do it.
All you need is the Smoother function from this, but I included the change logic as this is what I use 100% of the time for analog readings.

// Smoother Calc Variables
const int numReadings = 30;   //Number samples used for smoother() calc, the larger this number, the smoother the end result
int readings[numReadings];    //Read the analog input into current indexed position
int sIndex = 0;               //The index of the current reading
float sTotal = 0;             //Hold the running total for the Smoother averaging calc
float sAverage= 0;            //The averaged value from the Smoother calc

// Change Variables
float t;                      //Global t var, can be any value that needs a rolling average
float tChange;                //Holder used to compare against the global t var
boolean Change = false;       //Set if t changes

void setup() {

  //Initialize all the index readings used in Smoother() to 0: 
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
  readings[thisReading] = 0;    

}

void loop() {

  Smoother();   //Call the Smoothing function

  if (tChange != t){    //Compare used to control loop timing
    /*
    Update only program parts that are affected by value change, such as displays, to help minimize program loop times.
    Not necessary, but a good idea since the Smoother calcs are very fast and display logic tends to be very slow.
    It is also used to control serial writes, only write when you need to if updates depend on a change in the smoothed value.
    */
    tChange = t;       //Set tChange = to t to reset the compare for next program cycle. 
    Change = false;    //Reset the change bit for the next program cycle.  
  }
}

void Smoother(void){

  //Subtract the last reading:
  sTotal= sTotal - readings[sIndex];         
  //Read from the sensor  
  readings[sIndex] = t; 
  //Add the reading to the sTotal
  sTotal= sTotal + readings[sIndex];       
  //Advance to the next position in the array
  sIndex = sIndex + 1;                    

  //Are we at the end of the array?
  if (sIndex >= numReadings)              
    //Wrap around to the beginning 
    sIndex = 0;                           

  //Calculate the average
  sAverage= sTotal / numReadings;
  
  //Compare used for program loop control (detect change from average, make other things happen only on change)
  if (sAverage!= t)
    Change = true;
}

Regards

Thanks for your reply and sorry about the delay in responding - this project was on the back burner for a while.

I'll have a look at the code and try to use it to make it all work and will let you know how I get on,

Cheers