PWM (Pet Weight Monitor)

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)

yiWTq.png

 

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 gauge

void 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 outputs

  digitalWrite(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);
  }
}



I did think that Maxhirez,

I did think that Maxhirez, but she isnt around to take a good one. This will have to do for now!

Now my PIRsensor is playing

Now my PIRsensor is playing up. Not sure if its faulty. I could have the sketch constantly averaging a reading to get tare, then record peaks of load to get weight readings., rather than wait for pir triggers. The mechanical sympathyst in me wonders if making a microcontroller work like that for weeks on end is cruel? What do you think?

what a great project!

…and a beautiful cat! I too have a cat with the thyroid issue… its hard to keep her up to 7 or 8 lbs, and even a few ounces is important. 
I’m going to make one of these for her. Thanks for sharing! 

LOL, that’s a quite useful

LOL, that’s a quite useful project, more useful as the most of our projects (sorry guys, but obstacle avoiders became sooo boring) :slight_smile:

What i don’t understand is the PIR just there for detecting your (your girlfriends) cat on the plate (oops, i mean the weighting platform)? Why not just check those weight sensors for a bigger change to trigger the readings?

Thanks Lumi.you are indeed

Thanks Lumi.

you are indeed correct lumi, I used the PIR for a few reasons:

  1. I had a pair of PIR sensors in the parts bin! The development was more of a “how can i do this with what i have” rather than "what do i need"
  2. I found little in the way of data on the strain gauges, and the ones i used were pulled out of a cheap kitchen scales rather than bought speciifically. I had NO idea about whether they would drift, or settle, or behave. the PIR sensor senses approach, and takes the first reading few readings as a tare value. If i didnt use the PIR, i would have to buffer an array of numbers, then IF it elevated,look back to the pre elevation value as a tare. This is more complicated to program (for me at least)
  3. the PIR, also detects people, so i used this functionallity, and whenever it is triggered, it displays an output indicating how she is doing weight wise as two flashes. One of the starting value for comparison, then the lastest value ! No button presses or further interaction required. Just passing by gives a yellow flash, then a flash determined by the data. RGB led as an output, because i had it in the parts bin!
  4. The amplifier has a “sleep” function on it. Im not sure how much power it would save, but i COULD put the amp and sensors to sleep until it detects motion. Obviously, it cant pick any values up without being powered up!

Thanks for that extensive

Thanks for that extensive reply.

I understand. Using parts you already have makes things mkore easy since you can start right away to make something.