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://postimg.org/image/n315i8d23/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.
/*
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);
}
// 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/qvg2k836j/