LowPowerLab Forum

Hardware support => Projects => Topic started by: res_emptito on December 12, 2014, 06:00:06 PM

Title: cattle/livestock scales
Post by: res_emptito on December 12, 2014, 06:00:06 PM
Back in the summer I finished the project I earlier mentioned in a help request post.  I have a small cattle farm and needed a scale to weigh the animals.  Of course they are available for sale but at a cost of $1,000 to $1,500 which is too expensive for a small operator like me.  I built the following scale for very close to $500.  The 2 x 6 ft. deck consists of two layers of 21/32 inch plywood (the thickest available at the home improvement store at that time and place) bolted to 3 x 2 inch angle (3-in. side vertical).  The deck rests on four short body hydraulic rams - item 95979 from Harbor Freight.  I removed the fitting from each ram, extended the ram cylinder 1/2 inch (backed off 1/16 inch from maximum 7/16 inch extension), filled the cavity with oil and finally screwed in a pressure transducer.  The moteino measures the pressure, converts it to force, does a little statistical stuff and transmits result to the hand-held receiver either in view outside the corral or in the hands of a helper.  It was not too practical to have a wired connection to the display as I do not know on which side of the scale I may need to be at any given time (working with cattle is hectic) and the instrument might get damaged by the animal.  So I wanted a wireless solution, and the moteino worked nicely.  The base station/transmitter box sits under the weigh deck where it should be safe.  I've been using it, and it seems to work well.  My weights are very close or same as official weights at the auction market.

Pictures of the hardware:
http://postimage.org/image/vim4lzqjf/ (http://postimage.org/image/vim4lzqjf/)
http://postimg.org/image/n315i8d23/ (http://postimg.org/image/n315i8d23/)
http://postimg.org/image/4bz882ihn/ (http://postimg.org/image/4bz882ihn/)

The project boxes are plastic electrical junction boxes, but they came with no pre-punched holes.  They are found at home improvement stores.  I liked them because they are tough, water resistant (lids have rubber seal) and nice clean holes can be drilled in the thick plastic walls.  I made a plexi-glass cover for the receiver unit to see the display.

Constructive criticism and suggestions for improvements for others are welcome.  I was fairly inexperienced with electronics and arduino when I built this.

My current project involves a tractor monitoring system (TMS I'll call it) for an old tractor to display the rpm, engine temperature and oil pressure.  No, that will not be wireless, but my weather station project will be.  I see exciting opportunities for the universe of arduino open-source systems in agriculture.  It is fulfilling and empowering to learn this stuff.


Code: [Select]
  /* 
  This is an arduino sketch (microcontroller program) used with certain hardware to weigh livestock and
  wirelessly transmit result to hand-held display.
  This sketch is for the "base station."  Another sketch is written for the hand-held unit which receives the wirelessly
  transmitted weight figure for electronic display.
  Code that makes the moteino wirelessly communicate was taken (with minor modification) from examples provided by
  Felix Rusu, creator of the moteino from https://github.com/LowPowerLab/  <- see example code
  The rest of the following code was written by Perry T. Jennings
  */
 
    // enter wireless device parameters below .......................................................//
    // This sketch uses the library written for the moteino by Felix Rusu; see http://lowpowerlab.com/moteino/#programming
    #include <SPI.h>
    #include <RFM69.h>
    #define NODEID 2 //unique for each node on same network
    #define NETWORKID 100 //the same on all nodes that talk to each other
    #define GATEWAYID 1
    #define FREQUENCY RF69_433MHZ //Match frequency to the hardware version of the radio on your Moteino
    #define ACK_TIME 30 // max # of ms to wait for an ack
    int TRANSMITPERIOD = 100; //transmit a packet to gateway so often (in ms)
    boolean requestACK = false;  //per example code for the Moteino
    RFM69 radio;

    typedef struct {
      int  weightInt;
      boolean problemFlag;
    } Payload;
    Payload TxPayload;  // best to transmit the integer weight as a structure over the Moteino
    // wireless device parameters above .............................................................//
    #define BUTTON 4 // pin where button press zeroes the board (subtracts tare weight)
    #define POWERON 7 // pin for power-on LED light indication
    // which analog pins to connect to the pressure transducers:
    const int TRANSDUCER1PIN = 1;
    const int TRANSDUCER2PIN = 2;
    const int TRANSDUCER3PIN = 3;
    const int TRANSDUCER4PIN = 4;
    const int TRANSDUCERRATING = 500;  // pressure rating of the Dwyer pressure transducer
    // hydraulic piston diameter in millimeters - the bottom larger-diameter "wet" part, not necessarily the top part you see
    const float DIAMM = 43.0;  //per customer service reply from HarborFreight
    const int NUMSAMPLES = 10;   // how many samples to take each loop cycle of which we throw out high and low
    const float pistonArea = PI * (DIAMM/2) * (DIAMM/2)/(25.4*25.4);  // hydraulic piston area in inches conversion //
    float sample1; //one sample of pounds force on hydraulic ram no. 1
    float sample2;
    float sample3;
    float sample4;
    float samplesSum1;  //sum of NUMSAMPLES of sample1
    float samplesSum2;
    float samplesSum3;
    float samplesSum4;
    float max1;  //the maximum of all the samples1 in SamplesSum1
    float max2;
    float max3;
    float max4;
    float min1;  //the minimum of all the samples1 in SamplesSum1
    float min2;
    float min3;
    float min4;
    float minimum; //statistical test for problem
    float maximum; //statistical test for problem
    float force1;  //the average pounds force on ram no. 1 determined from NUMSAMPLES and tossing out max1 and min1
    float force2;
    float force3;
    float force4;
    float weightFloat;  //weight of the beast on the scales determined by adding the forces1...4
    // Lines below used in testing/troubleshooting.  Comment out for normal use.
    // #define SERIAL_BAUD 9600
    // float weightSum;
    // ............................................................................................. //
    void setup(void) {
      radio.initialize(FREQUENCY,NODEID,NETWORKID);  // this is wireless device setup
      pinMode(BUTTON, INPUT);
      pinMode(POWERON, OUTPUT);
      digitalWrite(POWERON, HIGH);
      // Lines below used in testing/troubleshooting.  Comment out for normal use.
      //Serial.begin(SERIAL_BAUD);
    }
    // ............................................................................................. //
    void loop(void) {
    /* offsets are pounds subtracted from readings as a means to zero the scales
    so offsets are similar to tare weight but may also include trapped pressure in the cylinders */
    static float offset1 = 0;
    static float offset2 = 0;
    static float offset3 = 0;
    static float offset4 = 0;
    static boolean zeroTheBoard = false;  //whether or not to zero out the tare weight and trapped pressure
    sample1 = getForce(TRANSDUCER1PIN);
    sample2 = getForce(TRANSDUCER2PIN);
    sample3 = getForce(TRANSDUCER3PIN);
    sample4 = getForce(TRANSDUCER4PIN);
    maximum = max(sample1, sample2);
    maximum = max(sample3, maximum);
    maximum = max(maximum, sample4);
    minimum = min(sample1, sample2);
    minimum = min(sample3, minimum);
    minimum = min(sample4, minimum);
    TxPayload.problemFlag = ratioBlownOut(maximum, minimum);
    samplesSum1 = 0;
    samplesSum2 = 0;
    samplesSum3 = 0;
    samplesSum4 = 0;
    max1 = 0;
    max2 = 0;
    max3 = 0;
    max4 = 0;
    min1 = 2000.0;
    min2 = 2000.0;
    min3 = 2000.0;
    min4 = 2000.0;
    // take NUMSAMPLES consecutive samples, with a slight delay; toss out high and low
    for (int i=0; i< NUMSAMPLES; i++) {
      sample1 = getForce(TRANSDUCER1PIN);
      max1 = max(max1,sample1);
      min1 = min(min1,sample1);
      samplesSum1 = sample1 + samplesSum1;
      sample2 = getForce(TRANSDUCER2PIN);
      max2 = max(max2,sample2);
      min2 = min(min2,sample2);
      samplesSum2 = sample2 + samplesSum2;
      sample3 = getForce(TRANSDUCER3PIN);
      max3 = max(max3,sample3);
      min3 = min(min3,sample3);
      samplesSum3 = sample3 + samplesSum3;
      sample4 = getForce(TRANSDUCER4PIN);
      max4 = max(max4,sample4);
      min4 = min(min4,sample4);
      samplesSum4 = sample4 + samplesSum4;
      delay(5);  // my transducer response time is 3 milliseconds, so this should be ample delay
    }
    force1 = (samplesSum1 - max1 - min1)/(NUMSAMPLES - 2);  // take an average
    force2 = (samplesSum2 - max2 - min2)/(NUMSAMPLES - 2);
    force3 = (samplesSum3 - max3 - min3)/(NUMSAMPLES - 2);
    force4 = (samplesSum4 - max4 - min4)/(NUMSAMPLES - 2);       
    zeroTheBoard = digitalRead(BUTTON);
    if ( zeroTheBoard )  //zero the board if button is pressed to do so
       {
        offset1 = force1;
        offset2 = force2;
        offset3 = force3;
        offset4 = force4;
        zeroTheBoard = false;
        delay(9); // button debounce protection; not really critical here
       }
    weightFloat = force1 - offset1 + force2 - offset2 + force3 - offset3 + force4 - offset4;
    TxPayload.weightInt = int(weightFloat);
    radio.sendWithRetry(GATEWAYID, (const void*)(&TxPayload), sizeof(TxPayload));  //wirelessly trasmit the weight to hand-held unit
    // Lines below used in testing/troubleshooting.  Comment out for normal use.
    /*weightSum = force1 + force2 + force3 + force4;
    Serial.print("transducer1 ");
    Serial.print(force1);
    Serial.print("  transducer2 ");
    Serial.print(force2);
    Serial.print("  transducer3 ");
    Serial.print(force3);
    Serial.print("  transducer4 ");
    Serial.print(force4);
    Serial.print("  weight ");
    Serial.println(weightSum); */
    } 
    // ...................................custom functions below.................................... //
    float getForce(int pinNo)
    /*
    at elev. zero, let A = 1023 * 4ma * R / max. analog input voltage
    at full scale, let B = 1023 * 20ma * R / max analog input voltage
    let constant K = full scale transducer pressure / (B - A)
    return pistonArea * ( inputValue - A)* K
    using a 200 ohm resistor so that 4V is top range for 20 mA = TRANSDUCERRATING psi and .8V at 4 mA = 0 psi
    thus 248 would be the analog input for the elevated zero at 4 mA =0 psi : 1023 * .8/3.3V where 3.3V is max. input to analog pin
    1240 = 1023 * 4/3.3
    .5040 = 500 (full scale pressure rating) / (1240 - 248)
    BUT, I found that the formula needed a little calibration based on four test points of known weight in a line divergent from the theorectical line.
    This function returns pounds force on one of the hydraulic pistons using formula force = area x pressure
    */
    {
      int inputValue;
      inputValue = analogRead(pinNo);
      return pistonArea*(inputValue*.5457 - 132.9);
    }
    // ............................................................................................. //
    boolean ratioBlownOut(int AnalogValA, int AnalogValB)
    /*
    AnalogRead values obtained from the four transducers will be unequal but should be within a reasonable range.
    The animal may not be centered on the board and the weight on front two hooves may differ from rear two hooves,
    but if the values vary too much then there must be a problem.  This is tested with the formula:
    delta/average > threshold using the maximum and minimum values among the four values read by analogRead.
    I played with some figures on a spreadsheet using this formula and thought that the given threshold value below was reasonable
    */
    {
      float floatValA;
      float floatValB;
      float delta;
      float aveAandB;
      const float threshold = .90;
     
      floatValA = float(AnalogValA);
      floatValB = float(AnalogValB);
      aveAandB = (floatValA + floatValB)/2;
      delta = abs(floatValA - floatValB);
      return (delta/aveAandB > threshold);
    }
Code: [Select]
// This arduino sketch receives the weight figure from the scales transmitter and displays it.
//
//
// RFM69 Library and code by Felix Rusu - felix@lowpowerlab.com
// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/
// Also using Adafruit LEDBackpack library
//
#include <RFM69.h>
#include <SPI.h>
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"
#define NODEID 1 //unique for each node on same network
#define NETWORKID 100 //the same on all nodes that talk to each other
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
#define FREQUENCY RF69_433MHZ
#define LED 9 // Moteinos have LEDs on D9
#define ACK_TIME 30 // max # of ms to wait for an ack
// or define SERIAL_BAUD 57600
#define SERIAL_BAUD 115200
#define POWERON 7 // pin for power-on LED light indication

RFM69 radio;
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
typedef struct {
  int  weight;
  boolean flag;
} Payload;
Payload RxWeight;
int the_weight;
Adafruit_7segment matrix = Adafruit_7segment();

void setup() {
  Serial.begin(SERIAL_BAUD);
  delay(10);
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  radio.promiscuous(promiscuousMode);
  pinMode(POWERON, OUTPUT);
  digitalWrite(POWERON, HIGH);
  Blink(LED,2000);
  matrix.begin(0x70);
  pinMode(LED, OUTPUT);
}

byte ackCount=0;
void loop() {
matrix.blinkRate(0);
if (radio.receiveDone())
  {
    if (radio.DATALEN != sizeof(RxWeight))
      the_weight = 7777;
    else
      {
        RxWeight = *(Payload*)radio.DATA;
        the_weight = RxWeight.weight;
        if (RxWeight.flag)
          matrix.blinkRate(2);
      }
   /* if (radio.ACK_REQUESTED)
    {
      byte theNodeID = radio.SENDERID;
      radio.sendACK();
      // When a node requests an ACK, respond to the ACK
      // and also send a packet requesting an ACK (every 3rd one only)
      // This way both TX/RX NODE functions are tested on 1 end at the GATEWAY
      if (ackCount++%3==0)
      {
        // Serial.print(" Pinging node ");
        // Serial.print(theNodeID);
        // Serial.print(" - ACK...");
        delay(3); //need this when sending right after reception .. ?
        if (radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0)) // 0 = only 1 attempt, no retries
          Serial.print("ok!");
        else Serial.print("nothing");
      }
     
    }
    Blink(LED,3); */
  }
  else
    {
      //Serial.println("radio.receiveDone false");
      the_weight = 8888;
      Blink(LED,2500);
    }
matrix.print(the_weight, DEC);
matrix.writeDisplay();
}

void Blink(byte PIN, int DELAY_MS)
{
  digitalWrite(PIN,HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN,LOW);
  delay(DELAY_MS);
}

The circuit wiring diagrams:
http://postimg.org/image/xol0u8jdn/ (http://postimg.org/image/xol0u8jdn/)
http://postimg.org/image/qvg2k836j/ (http://postimg.org/image/qvg2k836j/)
Title: Re: cattle/livestock scales
Post by: TomWS on December 12, 2014, 06:33:03 PM
Cool project!  Thanks for posting!

Do you measure the pressure at all four corners or just one and extrapolate?

UPDATE: What pressure transducer are you using?  What is the weight range of your scale?

And, if I didn't mention it, really cool project!   :)

Tom
Title: Re: cattle/livestock scales
Post by: res_emptito on December 12, 2014, 08:20:36 PM
Thanks Tom.  The system measures ten samples from each of the four corners then throws out the high and low and averages the result.  The pressure transducer is a Dwyer series 628 which is available directly from the Dwyer website.  The 500 psi transducer can measure 1,125 lbs per corner with this setup,, but you would be better to use the 300 psi model and make the necessary program code changes because the accuracy is stated in terms of full scale (FS).  I was being too conservative choosing the 500 psi model.
Title: Re: cattle/livestock scales
Post by: kobuki on December 15, 2014, 05:20:05 AM
Very cool project. Would you mind telling us how precise your scale turned out to be? What's the min/max weight it's able to measure? I'm not familiar in the field but using pressure sensors sounds like a novel idea. Is it frequently used as a way of measuring weight in standard equipment?
Title: Re: cattle/livestock scales
Post by: res_emptito on December 15, 2014, 07:04:38 PM
Thanks kobuki. In practice I think it is within 7 lbs for an approximately 700 lb heifer based on official weight comparison at the market where I sell them..  Capacity of any one corner is 1,125 lbs.   Cattle scales would need to measure up to 2,000 lbs, maybe a little more.  If I were to do this over I would use 300 psi transducers instead of 500 psi and make the necessary adjustment in the code.  As far as weighing livestock like cattle there is no minimum allowable weight for my scale.  Livestock scales for purchase typically use load cells (strain gauge), and I looked into that, but the load cells are very expensive.  So it was my idea to measure pressure, though I found out that pressure has been used in scales for some time.  I found a patent from the 1940's where pressure was used with analog/mechanical gauges and averaging of pressures, and sometime in the 1980's someone had a patent using a microprocessor perhaps similar to what I did.