So our (My girlfriends) cat has a hyperactive thyroid. The over abundance of thyroxin means she cant regulate her weight and without keeping it in check she will waste away, eventually to nothing. Thankfully, through a controlled diet this can be kept in check. Annoyingly (sort of), i didn't get around to sorting this project until she had put on an extra kg, and her weight stabilised, so no interesting rising graphs!
First I cannibalized a £10 set of kitchen scales. I only took the strain gauges out of it, but it was still the cheapest way of buying them. Additionally they are mounted in their own individual little plastic clips, which i removed with a dremel. The white plastic clips are just glued onto a 50cm sq piece of 6mm MDF.
Each "gauge" actually has TWO strain gauges in, one on the top, one on the bottom. so this diagram represents one "unit" (of which i have four). I wired two of them up 1: V+, 3: Gnd, 2: Sense. The other two had V+ and Gnd swapped. Applying a voltage across 1 and 3 using an arduino, a varying voltage can be picked up using a multi meter between 2 and Gnd. (pressing pretty hard gives a 0.001V change)
I had to reverse engineer the wiring (as i know little about these things). Here is an image of the layout. Two of the gauges lower in resistance under pressure, two of them increase resistance under pressure. The resistance changes are tiny, so they need to be increase with an amplifier. The INA125 seems to be ideal for the job, with its own dedicated on board reference voltage, adjustable amplification and a differential amplifier (so it can amplify the difference between the increasing resistance gauges, and the reducing resistance gauges) This arrangement means that wherever the weight is place on the board the reading remains consistently accurate!
Not pretty... yet.
i then connected up the INA125 using this guide:
http://www.deferredprocrastination.co.uk/blog/2013/reading-strain-gauge-scales-with-arduino
Through trial and error (knowing what my maximum weight was desired to be, and having a container of water containing said weight) i was able to determine that the amplification i needed was set by using 40 ohms of resistance accross the INA125 amplification setting pins. I dont know what the gain is to be honest, but i do know that putting 5kg on the scales, gives a reading of 1000 on the serial output. as close as i can get to 1023 without being silly on perfecting the resistance.
I then recorded the serial output values, for half kg increments (weighed using a proper set of scales. By plotting the values against the weights i could plot a graph. Excel can then calculate the angle of the trend line. By dividing the sensor value by the ratio calculated by excel i now get a serial output of accurate kgs!
(quite smug about that idea)
Anyhoo!, it worked on the breadboard, and everyone loves soldering so i decided to bite the bullet and solder the whole setup onto my SD protoshield (for which i have been looking for a project for months). I used screw terminals for the outputs, and also a pair of screw terminals for the amplification resistors incase i want to change the amplification at all in the future. To make a 40 ohm resistor, i just cobbled together 4 x 10 ohms.
The setup tested ok, so i then went and in the remaining space tacked an RGB led which i will use for "quick visual feedback" (the colour will provide a quick indication of a number of paramaters all at the same time)
I also added a PIR sensor. I intend to use the PIR sensor to detect the approach of the cat (or Human user), wake the system up and Tare(Zero) the scales. this will eliminate the problem of potential drift! (and i had the PIR in my parts bin)
Now i just need to get stuck in to the sketch!
09/02/2014
The sketch is done. Quite pleased with it, except im having issues with calibration. the reading given by the scales just wobbles around too much! i need to work out a way of smoothing the response which doesnt impact the code.
For example: Upon detecting Infrared motion, the sketch averages the current reading for 2 seconds. (10 values) to act as a tare values. after this two seconds, it has 60 seconds where by if the weight detected increases, it replaces the value. (so records max load detected). Then it subtracts tare from the max load detected, except it pretty consistantly picks up around 70g of load even when no mass is applied at all.
maybe not an issue, as i only want it to detect masses over 2kg (so we can top up the food bowl without recording a value) however it raises questions about the consistency of the strain gauges!
I am logging both "grams" and also raw analogue input, so that i the grams is clearly rubbish, there is at least some raw data to play with in excel!.
Here is my sketch. Im off to plug it in and see how we go for a few days i think! There are only so many jugs of water i can weigh!
10/02/2014
So i debugged a few issues:
- I had connected a on board surface mounted LED light on the protoshield, to the same pin as the PIR, with the idea of using it as a hard wired indication of a positive PIR hit (for debugging). turns out the PIR only kicks out a 3.5v signal, so by the time youve lit the led (dimly) there isnt enough juice to trip the PIN. i cut the wire and the PIR works again just fine.
- I was getting inconsistent weight records, so i modded the code. I took the averaging section from the tare, and copied it into the weighing section. It takes a 10 point average, stores it, then takes another 10 point average. If the new one is higher than the previous one, it replaces it. This gives me a much smoother response.
- I restudied my conversion factors, and realised that my excel trend calculation was giving me two numbers. i had overlooked it because i was getting a bit tired and crabby yesterday. You know, blind to the details which are causing you grief. Now the conversion factor takes into acount both numbers, ConvFactA, and ConvFactB.
Updated sketch is below. The unit is cobbled together and in position. Tested well on jugs of water, lets see how the next 48 hours pans out!
23/02/2014
Ive debugged a few issues, and also given the board a few coats of floor board paint.
I was getting a few spikey readings (as high as 5kg) and i think that was down to my "averaging" technique needing a tweek. I reckon todays final mods should smooth my results out well. Ive also changed the detction limits (the sketch only records the value if its between 2 and 4 kg, as not to pick up topping up the food bowl etc), and the response of the LED. the LED now takes the First value recorded in a "session" and uses that as the base. Therefore rather than giving a feedback representing the most recent change, it gives and overall representation of change over the whole session (from last arduino reset).
EDIT and UPDATE 20/04/2014 So i feel i owe you an update perhaps. All was going well, until a "friend" of mine came over and his little brat thought it was hilarious to jump up on down on the fragile technical device. I wouldnt have been annoyed, except this friend thought it was hilarious that i didnt take to kindly to it. I guess some people just dont appreciate the time and effort that go into these things. ANYWAY, this was some time ago, it was giving me all sorts of wrong readings following its mauling. I decided to have a look at it today. Ive spent most of the afternoon stripping it, and rebuilding it with fresh tidy wiring, gluing the strain gauges on properly etc etc.
having carried out a "calibration" on the new build, by using verying known weights (books weighed on kitchen scales). this is the response line i got. The top one is the original calibration line, the bottom one the new one.
I think this pretty clearly demonstrates when you overload strain gauges! :
Sketch (latest version)
#include <Wire.h> #include <SD.h> #include "RTClib.h"const int chipSelect = 10;
File dataFile;
RTC_DS1307 RTC;//Connections
const int analogPin = A0;
const int pirPin = 2;
const int wake = 4;
const int rLED = 6;
const int gLED = 5;
const int bLED = 3;// values
float strainRead; // analogue read
float FinalstrainRead; // analogue read
float mass; // mass (raw from arduino)
float firstmass; //first gram, for the LED
boolean first = true;
float gram; // mass converted to g (by use of cal factor)
float tare; // value for storing tare
long startmillis; // timer start
float ledRatio; // ratio of Red/green LEDs
int redVal = 255; // store the value of the red LED
int greenVal = 255; // store that value of the green LED
float averageCount; // function to count size of average during tare
int averageSize = 10; // size of average taken during tare
float upperdetectionLimit = 4000; // detection limit in grams
float lowerdetectionLimit = 2000; // detection limit in grams
float calFactorA = 5.443285; // change this factor as required by the strain gauge
float calFactorB = 456.31; // change this factor as required by the strain gaugevoid setup() {
Serial.begin(9600);
RTC.begin();
Wire.begin();if (! RTC.isrunning()) {
Serial.println(“RTC is NOT running!”);
// following line sets the RTC to the date & time this sketch was compiled
// uncomment it & upload to set the time, date and start run the RTC!
// RTC.adjust(DateTime(DATE, TIME));
}Serial.print(“Initializing SD card…”);
pinMode(SS, OUTPUT);
if (!SD.begin(chipSelect)) {
Serial.println(“Card failed, or not present”);
analogWrite(rLED, 0);
analogWrite(gLED, 255);
analogWrite(bLED, 0);
while (1) ;
}
dataFile = SD.open(“LOG1.txt”, FILE_WRITE);
Serial.println(“card initialized.”);analogReference(EXTERNAL);
pinMode(rLED, OUTPUT); //Set the three LED pins as outputs
pinMode(gLED, OUTPUT); //Set the three LED pins as outputs
pinMode(bLED, OUTPUT); //Set the three LED pins as outputs
pinMode(pirPin, INPUT); //Set the three LED pins as outputs
pinMode(wake, INPUT); //Set the three LED pins as outputs
pinMode(analogPin, INPUT); //Set the three LED pins as outputsdigitalWrite(wake,HIGH);
analogWrite(rLED, 255);
analogWrite(gLED, 255);
analogWrite(bLED, 255);// Header
dataFile.println (“UnixTime, RealDate&Time, grams, RAW mass”);
}
void loop() {
if (digitalRead(pirPin) == HIGH) { // poll PIR
analogWrite(bLED, 0);
delay (100);
Serial.print ( "ledRatio±: ");
Serial.print ( ledRatio);
Serial.print ( " - redVal: ");
Serial.print ( redVal);
Serial.print ( " greenVal: ");
Serial.println ( greenVal);analogWrite(rLED, 128); // LEDs come on upon PIR detection. If ratio is updated, LEDs will update for 2 secs following 10 secs of monitoring, then go out. If not updated, will just go out after 10 secs
analogWrite(gLED, 128);
analogWrite(bLED, 255);startmillis = millis(); // starttimer
for (int averageCount = 1; averageCount < averageSize; averageCount++) { // takes 1 second to tare. 10 readings with 200millis delay
delay (100);
strainRead = strainRead + analogRead(analogPin);
}
tare = strainRead/averageSize;
Serial.print("FinalAverageTare: ");
Serial.println(tare);
strainRead=0;analogWrite(bLED, 0);
delay (200);
analogWrite(bLED, 255);
analogWrite(rLED, redVal); // LEDs come on upon PIR detection. If ratio is updated, LEDs will update for 2 secs following 10 secs of monitoring, then go out. If not updated, will just go out after 10 secs
analogWrite(gLED, greenVal);while (millis() - startmillis < 60000) { // for 60 seconds:
for (int averageCount = 1; averageCount < averageSize; averageCount++) { //
delay (100);
strainRead = strainRead + analogRead(analogPin);
}
FinalstrainRead = strainRead/averageSize;Serial.print("AverageStrainReading: ");
Serial.println(FinalstrainRead);if (FinalstrainRead > mass){ // recorded heaviest mass applied
mass = FinalstrainRead;
}
strainRead=0;
FinalstrainRead=0;
}Serial.print("MaxMassRaw: ");
Serial.println(mass);mass = mass - tare;
Serial.print("MaxMass-TareRaw: ");
Serial.println(mass);gram = ((mass*calFactorA)+calFactorB);
Serial.print("Max g applied: ");
Serial.println(gram);if ((gram > lowerdetectionLimit) && (gram < upperdetectionLimit)){
if (first == true){
firstmass = mass;
first = false;
}
DateTime now = RTC.now();// form data string
dataFile.print (now.unixtime());
dataFile.print (" , “);
dataFile.print (now.day(),DEC);
dataFile.print (”/");
dataFile.print (now.month(),DEC);
dataFile.print ("/");
dataFile.print (now.year(),DEC);
dataFile.print (" “);
dataFile.print (now.hour(),DEC);
dataFile.print (”:");
dataFile.print (now.minute(),DEC);
dataFile.print (":");
dataFile.print (now.second(),DEC);
dataFile.print (" , “);
dataFile.print (gram);
dataFile.print (” , ");
dataFile.println (mass);Serial.print (now.unixtime());
Serial.print (" , “);
Serial.print (now.day(),DEC);
Serial.print (”/");
Serial.print (now.month(),DEC);
Serial.print ("/");
Serial.print (now.year(),DEC);
Serial.print (" “);
Serial.print (now.hour(),DEC);
Serial.print (”:");
Serial.print (now.minute(),DEC);
Serial.print (":");
Serial.print (now.second(),DEC);
Serial.print (" , “);
Serial.print (gram);
Serial.print (” , ");
Serial.println (mass);dataFile.flush();
analogWrite(rLED, 255);
analogWrite(gLED, 0);
analogWrite(bLED, 0);
delay (1000);ledRatio = mass - firstmass;
Serial.print ( "ledRatio: ");
Serial.println ( ledRatio);
redVal = map(ledRatio, -1000, 1000, 0, 255);
greenVal = map(ledRatio, 1000, -1000, 0, 255);
redVal = constrain (redVal, 0, 255);
greenVal = constrain (greenVal, 0, 255);mass = 0;
tare = 0;}
Serial.print ( "ledRatio: ");
Serial.print ( ledRatio);
Serial.print ( " redVal: ");
Serial.print ( redVal);
Serial.print ( " greenVal: ");
Serial.println ( greenVal);analogWrite(rLED, redVal);
analogWrite(gLED, greenVal);
analogWrite(bLED, 255);delay (3000);
analogWrite(rLED, 255);
analogWrite(gLED, 255);
analogWrite(bLED, 255);}
else {
delay (500);
}
}