Introduction: Cat and Dog Selective Feeder

About: I like to make stuff. Mostly designing things in Fusion to 3D print, but also woodworking and crafting.

I designed and built this automatic cat feeder to help my diabetic cat to control his feeding and to prevent my other cat from eating his food. This feeder could prove useful to other pet owners who need to monitor the feeding needs of their pets.

My cat needs medicated food as well as daily insulin doses, he would often beg trick us into feeding him twice, or the other cat would eat his food making it hard for us to know how much he was actually eating.

I wanted to create an enclosed feeder that could detect which cat was at the feeder and then open for the correct cat. My first thought was to use NFC tags on their collars so I could differentiate between them. However, the effective proximity of the NFC tags was problematic since they only activate within a few millimeters of the tag. After several other ideas including reading the microchip in their back, I decided that the easiest way to differentiate between my two cats was by weight.

I found a project on Instructables to create a bathroom scale with an Arduino Uno, and after successfully building that project, I decided to create this project. The scale is accurate to within a tenth of a pound but your animal will be moving a lot so we will try to differentiate within about half a pound or a quarter kilo.

I created one prototype and went through a dozen or more design ideas before settling on this solution.

This is an intermediate difficulty project. It requires use of some basic power tools, soldering, a lot of 3D printing, sanding and gluing. All code for the project is provided as well as instructions on how to modify the code. 

This project was completed as part of an internship for Kalliot LLC. Published with permission from Kalliot LLC.

Supplies

Tools Needed: 3D printer, computer, drill, soldering iron, screw driver set, pliers, needle nose pliers, sand paper, Dremel tool (optional), heat gun, cyanoacrylate liquid glue, hacksaw, volt meter. 

Attached is also the bill of materials as a PDF.

Step 1: 3D Print

Print all custom components starting with the load cell mounts. You will need 4 of both the Load Cell Base and Top.

Step 2: Weight Scale Construction

Cut your weight scale base to whatever size you prefer using a material such as MDF. Mine is 30” x 15” (76cm x 38cm) with the feeder itself being ~7” x 8.5” (18cm x 21.5cm)


Pre-drill holes using the load cell mounts as a guide in the four corners. It can be helpful to mark out which load cell is which (upper left, lower left, etc.).


Using the wiring diagram in Figure 4,

1) connect your load cell wire pairs

2) cut the wires to length

3) put a heat shrink sleeve onto the wire before using a wire stripper to remove the housing and twist together the pairs.


Apply solder to the wires and then move the heat shrink over the connection and apply heat.


After linking the pairs, you should have four loose red wires each coming from one of the load cells. You will use a four pin female RGB header connector. Drill a hole large enough for the wires to fit through near the Lower Left load cell where the red circle is shown in Figure 2. Next, following the wiring diagram in Figure 4, solder and heat shrink the red wires to the correct colored wire of the RGB female header.  You must connect the correct signal wire to the correct color or your scale will not work. Solder the male connector to the HX711 which is the red chip in the wiring diagram, again making sure all the colors are in the correct order. Also solder the 4 breakout pins to holes marked VCC, SCK, DT, and GND. 

Step 3: Scale Calibration and Test

For the next step you will need the bread board, Arduino Uno, and some jumper wires. There are very good instructions in the bathroom scale project on how to calibrate your scale. Follow the instructions for the bathroom scale Instructables.

https://www.instructables.com/Arduino-Bathroom-Scale-With-50-Kg-Load-Cells-and-H/


Download the Arduino IDE here: https://www.arduino.cc/en/software


From the scale calibration sketch in the Instructables, obtain your calibration factor ex. -9660.0 You will need the calibration factor in a later step.

Step 4: Feeder Body and Lid Assembly

The first step is to attach the Feeder Lid to the Body Mid Hinge component. You will also need the plastic miter gear axle, the M5 coupling nut, and the M5 threaded rod. You want to make sure that your lid fits and has clearance between the fins of the hinge mechanism without any hiccups or chaffing.


Make sure that your coupling nut fits very snuggly both inside the hexagonal connector on the Feeder Lid and into plastic miter gear. A snug fit here is very important and there should be no play. Next, cut your threaded rod to 18cm using a hack saw or the circular cutting bit on your Dremel. Make sure to remove any burrs from the cut surface. Run the threaded rod through the assembly. You may want to try without the coupling nut first. If the rod won’t go in or is too snug, carefully drill out the holes to the appropriate size.


If the assembly moves freely then it is time to check for clearance with the feeder body front. Align the registration squares of the body front feeder with the body mid hinge, making sure the two bodies contact fully with no gaps. The registration squares should help prevent misalignment Figure 6. Raise and lower the lid to make sure there are no clearance or fit issues. If the clearance looks good, remove the rod from the hinge assembly and set aside those components for now.

You will now glue the Body Mid Hinge to the Body Front Feeder. Apply a very small amount of liquid cyanoacrylate glue (super glue) to the center of each gluing surface. I highly recommend doing this over wax paper as your print will become glued to the surface it is on top of. Press and hold the two components together firmly for thirty seconds. We can also glue in the barrel plug and male 4 pin RGB header, which is connected to the HX711, to the Body Rear Motor.


While the glue is curing, you will attach the Plastic Miter Gear Servo to the servo. To do this you will heat form the Miter gear over the teeth of the crown gear on the servo. Preheat your oven to 160 F. Take out a baking tray and place a layer of parchment paper down. Place your miter gear down on the parchment paper and place that on the top rack in the oven. The glass transition phase for PLA is between 50-80 C. If you are using ABS or another plastic, obtain the glass transition temperature for that material and change your oven temperature accordingly. When your oven finishes pre-heating, leave the miter gear in for approximately three more minutes. Take the miter gear out of the oven and quickly but carefully align the hole on the gear over the crown gear and press down firmly. Be gentle with this step as the plastic has now become pliable and very hot and can be susceptible to deformation. Leave the miter gear on the gear until it cools down to avoid thermal shrink or expansion.


Once cool, secure the miter gear with a M3 flathead bolt and washer. Place your servo into the Body Rear Motor and secure it with the four M5 x 30 bolts.


With the Body Mid Hinge and Body Front Feeder now glued, reassemble the hinge mechanism with the Feeder Lid, Hex Nut, miter gear and M5 rod. Place two bearings into the Bearing Caps and fit the bearing caps into the holes of the Body Mid Hinge. You should now have all of the 3D components assembled except for the shroud for the gears and the door for the electronics. 

Step 5: Electronics Assembly

In this stage we will be assembling the electronics on a bread board so that we can make sure that all our electronic components are functioning as expected. 1) Build the power circuit from Figure 4, but do not connect the yellow signal wire of the servo. The left side of the circuit is power for the Arduino and the right side of the circuit is power for the servo. Power to the servo also runs through the Adafruit INA260 current and voltage sensing chip which will act as our safety system. If you are happy that the voltages your components are correct when powered, then finish wiring up the chips as shown in Figure 4. The order of the chips in Figure 9, are different to those in the wiring diagram. Make sure to connect the male and female RGB headers which will connect your scale to your feeder. 

Step 6: Coding and Testing

Now that everything is wired up, you can move on to applying the code. Power on your circuit with by plugging in your power supply. Connect your Arduino or Pro Trinket to the computer using a USB cable. Always make sure to have your circuit powered while connected to the computer or the servo may try to draw power through the signal wire, which could cause damage to our components. You may want to leave the signal wire of the servo disconnected for now. Copy the code provided into a new sketch in the Arduino IDE and save it. Make sure to add the INA260 library to the IDE. The servo library should be included by default, and you should have added the HX711 library when calibrating your scale. All three libraries need to be included for this project.

1)     Turn on debugging mode by going to the debug class and setting debugOn to true.

2)     Go to the Feeder class and go to the private data section at the bottom.

3)     Change the calibrationFactor to the one you obtained in the scale calibration step and make sure to add one decimal place.

4)     Change the minCatWeight to a little bit less than the weight of your pet.

5)     Do not change maxCatWeight for now.

6)     If you used different pin designations than the ones in the wiring diagram, you can set those in this section.

7)     Compile and upload the sketch to your Arduino.

8)     Open the serial monitor in the Arduino IDE and make sure that the INA260 chip is found.

9)     It should then print out “I’m in the loop” over and over.

10)  Go back to the debug class and set debugOn to false.

11)  Connect the signal wire of the servo and reupload the sketch.

12)  The Servo should spin to 20 degrees and then slowly rotate back to zero.

13)  Disconnect the USB and power supply.

14)  We now know the position of the servo is at zero. Align the miter gears of the servo and axle and add the four fasteners to the corners being careful not to smoosh any wires. The lid should be all the way closed with no gaps.

15)  Reconnect the power supply to the feeder. The lid should quickly lift to twenty degrees and then slowly close to zero. This is the calibration phase and will happen every time the feeder is powered on.


16)  Put some weight on the scale and watch it open.


17)  Make sure it closes when weight is removed.


18)  You can test safety features by trying to obstruct the movement of the lid. (Please use caution if safety feature fails)


If your feeder is working, it is time to move on to the final steps.

Mount your finished feeder to the scale using double sided tape or Velcro.

Set maxCatWeight to a little bit more than the weight of your pet and reupload the sketch, but this time to your Pro Trinket. Make sure everything is still working with the bread board. We will minimize the size of the electronics by transferring everything to a solder board. Using the same wiring diagram as before Figure 4, Add each component to the solder board. The exact arrangement of components does not matter, only that the connections be the same as in the wiring diagram. You can copy the arrangement from Figure 10. Figure 11 shows how to bridge connections using solder.

Mount your solder board to the electronics mount and fix that inside the feeder using double sided tape or Velcro.

In order no mount the shroud that covers the gears the lid must be fully open. Slide the cover in to place and allow the lid to close. Place to set screws in the top to hold the shroud in place.

We plugged our feeder into a Govee smart plug which allows us to control it with our smart speaker. 


/**
Developed for Hobbyiest use by Tomas Diaz-Wahl for Kalliot LLC.

Copyright © 2022 Kalliot LLC

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
conditions are met:
1. Redistributions of source code and documentation must retain the above copyright notice, this list of conditions and the 
following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE AND DOCUMENTATION ARE PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

/*
 * This project uses two third-party libraries.
 * INA260 and HX711 libraries can be found at:
 * <https://learn.adafruit.com/adafruit-ina260-current-voltage-power-sensor-breakout/arduino>
 * <https://github.com/bogde/HX711>
 */
 
#include <Adafruit_INA260.h>
#include <Servo.h>
#include "HX711.h"


Adafruit_INA260 ina260 = Adafruit_INA260();
Servo servo;
HX711 scale;

/*
 * Debug class is designed to output info to the serial monitor, (found under the tools menu in the Arduino IDE),
 * This is designed to make sure that all boards and electronics are working as designed.
 * TO ACTIVATE DEBUG CLASS MAKE SURE debugOn IS SET TO TRUE.
 */

class Debug {
 public:
  static const bool debugOn = false; //turn debug on and off
  void printMsg(int x){
   if (debugOn){
    Serial.print(x);
    Serial.flush();
   }
  }

  void printMsg(String x){
   if (debugOn){
    Serial.print(x);
    Serial.flush();
   }
  }

  void printLine(int x){
   if (debugOn){
    Serial.println(x);
    Serial.flush();
   }
  }

  void printLine(String x){
   if (debugOn){
    Serial.println(x);
    Serial.flush();
   }
  }

  void setUpSerial(){
   if (debugOn){
    Serial.begin(9600);
    // Wait until serial port is opened
    while (!Serial) { delay(10); }
   }
  }
};//Debug

/*
 * Queue class is designed to make a basic queue for our current measurements. 
 * There is no need to modify this class.
 */

class Queue{
 private: 
  const static int maxQueueSize = 8;
  float arr [maxQueueSize];
  int queuePos;
   
 public: 
  Queue(){
    for (int i = 0; i < maxQueueSize; i++)
    {
     arr[i] = 0;
    }
    queuePos = maxQueueSize - 1;
  }// Queue constructor
   
  int enqueue (float val){
    queuePos += 1;
    if (queuePos >= maxQueueSize){
      queuePos = 0;
    }
    arr[queuePos] = val;
  }
   
  float getAvg (){
     float total = 0;
     float avg;

     for (int i = 0; i < maxQueueSize; i++){
      total += arr [i]; 
     }
     avg = total / maxQueueSize;
     return avg;
  }
};// class Queue

/*
 * Feeder class allows for motion of the feeder lid as well as implementing a check 
 * to see if there is an obstruction affecting lid motion.
 * You will need to change several variable in the private data section of this class.
 * You MUST change: calibrationFactor, minCatWeight, maxCatWeight
 * You MAY need to change: maxCurrent, lidMaxPosition, any pin designations you wish to change.
 */

class Feeder {
  public:
     
    const int moveAmount = 1; // How much the servo should move per command

    /*
     * Set up stores an known initial position in the microcontroller memory.
     * Tells the microcontroller which pin the servo is on.
     * Activates and sets the scale calibration factor (see SparkFun_HX711_Calibration sketch)
     */

    void setUp () {
      servo.write (initialPos);
      servo.attach(servoPin);
      delay (1000);
      scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
      scale.set_scale(calibrationFactor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
    }

    /*
     * reset should be called when powering up the feeder but can be called manually as well.
     * reset tares the scale as well as moves the servo to a known position and then slowly moves the servo to position 0.
     */
     
    void reset () {
      scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0
      servo.write (initialPos);
      delay (500);
      for (int i = initialPos; i > lidMinPosition; i--){
       servo.write (i);
       delay (servoDelay); 
      }
      pos = lidMinPosition;
      numberOfRetries = 0;
    }
     
    bool IsCatOn ()
    {
     float weight = scale.get_units();
     return ((weight >= minCatWeight) && (weight <= maxCatWeight));
    }

    void moveServo (int amount){
      pos += amount;
      servo.write(pos);
    }

    void raiseLid (int amount){
      moveServo(amount);
    }
     
    void lowerLid (int amount){
      moveServo (-amount);
    }

    /*
     * Open and close lid are nearly identical in function (haha! get it? they're actually methods)
     * These methods are designed to use the Adafruit INA260 current sensor to detect the current during operation.
     * If the current draw from the servo is too high, it may indicate an obstruction. 
     * The implementation of this is a little complex so I recommend not messing with these values.
     */

    float openLid () {
     while ((pos < lidMaxPosition) && (numberOfRetries < maxRetriesAllowed))
     {
      raiseLid(moveAmount);
      float current = ina260.readCurrent();
      myQueue.enqueue(current);
      if (myQueue.getAvg() >= maxCurrent){
          numberOfRetries += 1;
          lowerLid(5);
          delay(1000);//Wait before lid open retry
          debug.printLine("Max Current Detected, Closing Lid Before Retry");
          if(numberOfRetries > maxRetriesAllowed){
           exit (-1);//Lid is blocked
          }
          closeLid();
          return;
      }
      delay(servoDelay);
     } // end while loop
     numberOfRetries = 0;//reset counter
    } // openLid

    void closeLid (){
     while (pos > lidMinPosition)
     {
      lowerLid(moveAmount);
      float current = ina260.readCurrent();
      myQueue.enqueue(current);
      if (myQueue.getAvg() >= maxCurrent){
          numberOfRetries += 1;
          raiseLid(5);
          delay(1000);
          debug.printLine("Max Current Detected, Opening Lid Before Retry");
          if(numberOfRetries > maxRetriesAllowed){
           exit (-1);//Lid is blocked
          }
          openLid();
          return;
      }
      delay(servoDelay);
     } // end while loop
     numberOfRetries = 0; //reset retry counter
    } // CloseLid
     
  private:
    const float calibrationFactor = -9660.0; //This value is obtained using the SparkFun_HX711_Calibration sketch.
    const int LOADCELL_DOUT_PIN = 10;
    const int LOADCELL_SCK_PIN = 11;
    const int servoDelay = 40; // Speed of servo. (change not recommended)
    const int initialPos = 20; // initial position of servo when feeder is reset. Be careful!; servo will move at max speed to this position. 
    const int lidMaxPosition = 70; // Maximum position of servo may vary depending on tolerances.
    const int lidMinPosition = 0; 
    const int servoPin = 9;
    const float minCatWeight = 11.0; // lbs. Set the minimum activation weight of your animal.
    const float maxCatWeight = 1000.0; // lbs. Set the maximum weight of animal. (if you have a bigger dog or cat you dont want getting into the feeder)
    const float maxCurrent = 250.00; // Maximum current that is allowed under normal feed operation. Triggers overcurrent protection mechanism. 
    const int maxRetriesAllowed = 10; // Maximum number of lid movement commands during overcurrent protection before lid operation ceases. 
    const int numberOfMeasurements = 5; // Number of current measurements to be averaged.
    int pos;
    int numberOfRetries;
    Queue myQueue;
    Debug debug;
     
}; // Feeder



Feeder feeder;
Debug debug;

void setup() {
 delay(1000); //wait for boot up
 // DEBUG BEGIN
 debug.setUpSerial(); // Enables serial port communication. (Make sure debugOn set to true in debug class for USB serial communication) 

 debug.printLine("Adafruit INA260 Test");

 if (!ina260.begin()) {
  debug.printLine("Couldn't find INA260 chip");
  exit (-1);
 }
 debug.printLine("Found INA260 chip");
 debug.printLine("Configuring Ina");
 debug.printMsg("INA260_TIME_8_244_ms = ");
 debug.printLine(INA260_TIME_8_244_ms);
  
 ina260.setCurrentConversionTime(INA260_TIME_8_244_ms); // Changes the measurement interval. This makes current readings less spikey.
  
 debug.printMsg("INA260_COUNT_4 = ");
 debug.printLine(INA260_COUNT_4);
  
 ina260.setAveragingCount(INA260_COUNT_4); // Changes the number of measurements averaged by the INA260 sensor. This will also make readings less spikey.
 INA260_ConversionTime convTime = ina260.getCurrentConversionTime(); // Verifying Ina Configuration
 INA260_AveragingCount avgCount = ina260.getAveragingCount();  // Verifying Ina Configuration
  
 if (convTime != INA260_TIME_8_244_ms){
  exit(-1);
 }
 if (avgCount != INA260_COUNT_4){
  exit(-1);
 }
  
 debug.printLine("Configured Ina");

 // DEBUG END

  feeder.setUp(); // initialize feeder.
  feeder.reset(); // set feeder lid to position 0 and tare scale.

 delay(2000);// During construction of the feeder, this delay can be set much higher to align the servo gear with the door gear. (avoid pinchy fingies)
}


void loop() {

 debug.printLine("I'm in the loop");

 // The following code was used during construction for debug purposes
 // feeder.openLid();
 // feeder.closeLid();
 //debug.printLine("Program Exit");
 //
 // exit(0);
 //END DEBUG
  
 //main code for feeder operation
 if (feeder.IsCatOn()) {
  feeder.openLid();
 }
 else{ // Cat is not on the feeder
  feeder.closeLid();
 }

 delay(1000);
}
Pets Challenge

First Prize in the
Pets Challenge