Introduction: Automatic Arduino Powered Pet Feeder

This is my automatic pet feeder. Powered by arduino, using a 3D printed auger, and programmable with two feeding times with a user set quantity of food, with a battery backed up internal clock.

My cats drive me nuts in the morning begging for food so I decided it was time to robotize feeding time

I got frustrated with pet feeders available in the UK. They are either the type that rotate and unveil a meal, up to four times only, or the only half decent machine with a hopper is horrific to program, forgets everything if you remove the batteries, and frequently fails to even deliver any food! I program here and there, and the last machine I used made me so mad just trying to set the feeding times I nearly threw it out the window.

America has some good machines but they start at $300 USD, and shipping would be painful to the UK, so I decided to make one!

Step 1: Materials Needed

Electronics:

1 x normal RC servo - regular size

1 x continuous RC servo - off the shelf or hacked.

1 x 3D printed Auger parts (see auger step) (pennies if you have a 3D printer)

1 x 1 1/2" PVC T-Piece pipe (ebay £2.89) http://www.ebay.co.uk/itm/121534086943?_trksid=p20...

1 x Arduino (I used an Uno but any type will do)

OR ... for PCB (1 x ATMega328p, crystal, 2 caps, 5v voltage reg) - google "arduino breadboard"

1 x KY-040 Rotary encoder on board (ebay £3.85) http://www.ebay.co.uk/itm/181595206550?_trksid=p20...

1 x 16x2 LCD HD44780 compatible (ebay £2.53) http://www.ebay.co.uk/itm/281211020326?_trksid=p20...

1 x small push button switch (non-latching)

1 x food hopper - I used one for storing food with a firmly sealable lid - to stop feline criminal activity.

1 x mounting box - I built one from MDF

1 x 12v power supply - around 500Ma+ to be safe - driving the servos. You may need more.

Step 2: The Basics - Making the Auger (food Feed Mechanism)

The 1st hurdle is to make an auger (screw type feeder). Previously this was too much of a hurdle for me, but now with 3D printing, I just downloaded a design from thingiverse and printed it! If you like tinkering, build a 3D printer! - It's fun to make and amazingly useful to own. I built a Prusa i3 for around £350.

(http://www.thingiverse.com/thing:27854) - Credit to Kitlaan for his initial design which my machine is heavily based on.

His physical design is good, but the code is too simplistic for my needs, there is no display, no multiple feedings, and no user variable portion sizes. This is where the Arduino comes in and saves the day. I've previous experience with Arduino, but have never used an LCD or a rotary encoder - but it's no problem - the Arduino tutorials and examples are amazingly well written. I do have experience with battery backed up real time clocks (RTC) so adding one was a no-brainer.

The auger print comes in a few files - one for the auger, one for the servo mount/tube mount, and a servo spacer, which I didn't need. I printed the auger in one go (the author provides a split version too so you can print smaller objects and glue them together) - I have a Prusa i3 which had no issues with this. The auger is 10cm tall, the i3 prints up to 20cm tall. I printed with no support material and it came out fine. I had to trim the base with a knife a bit where the recess for the servo horn is, as it has collapsed a fraction due to the lack of support material. You can tell slic3r to print support material for only the 1st "x" rows, but I didn't think to do that.

I used a Futaba digital servo I had to hand, any servo will do, it doesn't need to be digital. You need it to be continuous ( no end stops) - these can be bought from places like sparkfun, but I just opened the servo , removed the plastic bump on one of the cogs that stops it rotating infinitely, and removed the potentiometer. Voila! - a continuous servo. Just search the web - there are loads of how-to's regarding changing regular servos to continuous servos.

I screwed the servo to the auger base, making sure it was level and rotated without wobble, and then test-fitted it in a piece of 1 1/2 inch plastic T-piece tubing (commonly used for hot tub plumbing) - cost £2.90 off ebay (PVC solvent weld pipe - ponds / hot tubs etc). Once I was happy it would rotate without binding I fixed it in place with hot glue where it inserts into the pipe.

Now I have an auger! (ho ho ho) :-)

Step 3: Making the Box

The box/base/enclosure is a simple MDF box (9mm). I first made a box, then cut out the base with a jigsaw, the holes for the auger with a hole cutting saw you attach to your drill, and the square holes for the control panel and servo with a jigsaw again.

I didn't want the hassle of trying to cut neat enough holes (square holes!) to mount the LCD so istead I 3D printed a home designed facia - this takes the LCD, push button (for manual feed) and the rotary encoder knob. You can then screw this to the wooden box and it looks nice and neat!

The food agitator is again 3D printed and screws directly to a servo horn. The round spout is also 3D printed (credit to Kitlaan again on Thingiverse http://www.thingiverse.com/thing:28483 ) I could have made the auger tube stick out the front of the box more negating the need for the spout but I wanted the top hole a littel further back from the edge so if I decide to replace the current food hopper with a larger one, the hole won't be right at the edge.

The food hopper screws onto the box and is easily replaceable / upgradable.

Time will tell if my cats learn to press the manual feed button!

Step 4: Arduino - the Hardware

This whole project is driven by an Arduino. I highly recommend you build a prototype with a regular arduino and a breadboard. It's the best way to understand what everything does and save mistake later. I've included the Fritzing file so you can load it up and see the breadboard layout in detail (cables are labelled in the program where nescessary). Fritzing software is free/donateware.

I built a breadboard with the LCD on first, then learnt the basics of how to control it. Then I added a rotary encoder and learnt how to use that, then I combined the two in code. Finally I added the real time clock , the servos, and started programming the final code.

You will notice I said "servos". The second servo is a regular servo, used to agitate the pet food before each feed, to stop it clumping (the pet food is known as kibbles to the USofA I believe :-) )

After I was happy with the arduino uno and breadboard test I replaced the Arduino uno with pure components - the ATMEGA328P chip, a few caps, 5v regulator (power supply) and a crystal - basically - an arduino without the circuit board. It's dead simple to knock up (google arduino breadboard) and is a good habit to get into - saves you a fortune on arduino uno's and means you can design one PCB to house everything. The picure of the breadboards with a rats nest of wiring is the "pure version".

This version tested ok so I then drew the circuit in PCB Wizard (a great package that Maplin sells for £40 ish) and converted it into a PCB drawing using the auto routing feature.I've included the file.

I print and etch my PCBs using the laser printer method. I've made an Instructable on it - have a look under my name - it works every time (the PCB, not my name). I've attached the PDFs for printing.

Finally I soldered all the components on and hooked up the LCD, rotary encoder, and manual feed button with ribbon cable.

You don't need to go that far - you can run your feeder with a regular Arduino, and a breadboard, or you could use that PCB board where you cut links with a knife - the choice is yours. The hardware is kind of academic - it's all in the code.

Step 5: Arduino - the Code

The code is not as intimidating as it first looks, and I've tried to annotate it as much as possible.

In summary it:

Reads the time from the RTC chip

Displays the time, and the default feed times and quantity

Checks to see if the rotary encoder has been pressed if it has: it then goes into the programming routine, cycling in order through clock, portion size, feed time 1, feed time 2. When you are done, it continues below....

Checks if it is feeding time 1 or 2 - if so it feeds.

Checks if you have pressed the manual feed button, if so it feeds. And then it starts all over again.

This is the first time I've used an interrupt service routine - for the rotary encoder. This means if you turn it, whatever the program is busy doing , it registers your action. So it's ALWAYS listening for the encoder.

I thought the routine for setting the clock/feed times & qty would be daunting, but I just broke it down into simple steps. 1st I learnt how to make the cursor flash on the screen, easy enough. Then I positioned the cursor on the hour digit of the clock, and told it to increase the hour as the encoder was turned (or decrease), then once the encoder was pressed, write the new "hour" to the real time clock, and then move the cursor to the minute, and start changing that, and so on. Was surprisingly easy.

The feeding routines are simple "if" routines - i.e. "if" the times matched, then do the feeding routine. Simple as that. I had to add a bit so it checks the seconds or else it feeds for a whole minute, as the program loops and checks the time again - and it is still feeding time! so it repeats... adding a check on the seconds stops this as it takes at least a second for the feeding cycle.

The program isn't perfect - sometimes turning the encoder doesn't always increment the number on screen. I think this is down to my inexperience. It doesn't however hinder the operation of the machine!

I've included a screen grab of my arduino IDE for no real reason other than I didn't want this section to be without a picture :-)

Obviously the video on the 1st page shows me pressing the manual feed button, but of course, it auto-feeds at the two set times (you could set both times the same and it would only feed once).

/*

Automatic Auger Audiono pet feeder Copyright Roger Donoghue 28/03/2015 all rights reserved. For personal use only.
Not for commercial use or resale.
Allows you to set 2 feeding times and the quantity as a multiple of the default feed quantity.
Uses a DS1307 real time clock to keep the time, with a rechargable battery built in.
(You can use the arduino RTC example code in the IDE to set the clock , or use the rotary encoder as intended)

*/
// include the library code:
#include <LiquidCrystal.h>
#include <Wire.h>      // needed for the RTC libraty
#include <Time.h>
#include <DS1307RTC.h> // Real Time Clock Library
#include <Servo.h>

// initialize the library with the numbers of the interface pins dor the LCD
LiquidCrystal lcd(12, 11, 5, 8, 7, 6);

#define PIN_SERVO 9

Servo feedServo;
Servo stirServo;
int pos = 0;
volatile boolean TurnDetected;
volatile boolean up;
const int PinCLK=2;                   // Used for generating interrupts using CLK signal
const int PinDT=3;                    // Used for reading DT signal
const int PinSW=4;                    // Used for the push button switch of the Rotary Encoder
const int buttonPin = A3;             // the number of the pushbutton pin for manual feed 13
int buttonState = 0;                  // variable for reading the manual feed pushbutton status
int feed1hour = 07;                   // variables for feeding times and quantity
int feed1minute = 00;
int feed2hour = 17;
int feed2minute = 30;
int feedQty = 4;
int feedRate = 800;   //a pwm rate the triggers forward on the servo 75
int feedReversal = 80; //a pwm rate that triggers reverse on the servo
                       // play with these numbers for your servo. Mine is a Futaba digital servo
                       // that I removed the pot from and the plastic lug, to make it continuous.

void isr ()  {                    // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
 if (digitalRead(PinCLK))         // this keeps an eye out for the rotary encoder being turned regardless of where the program is
   up = digitalRead(PinDT);       // currently exectuting - in other words, during the main loop this ISR will always be active
 else
   up = !digitalRead(PinDT);
 TurnDetected = true;
}


void setup ()  {
   // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  // setup the Rotary encoder
 pinMode(PinCLK,INPUT);
 pinMode(PinDT,INPUT);  
 pinMode(PinSW,INPUT);
 pinMode(buttonPin, INPUT);
 attachInterrupt (0,isr,FALLING);   // interrupt 0 is always connected to pin 2 on Arduino UNO
    lcd.setCursor(17,0);            
    lcd.print("Roger Donoghue's");  // A bit of fun :-)
    lcd.setCursor(17,1);
    lcd.print("  Cat-O-Matic");
     for (int positionCounter = 0; positionCounter < 17; positionCounter++) {
     // scroll one position left:
     lcd.scrollDisplayLeft(); 
     // wait a bit:
     delay(150);
          }
      delay(3000);
      for (int positionCounter = 0; positionCounter < 17; positionCounter++) {
     // scroll one position left:
     lcd.scrollDisplayRight(); 
     // wait a bit:
     delay(150);
     
             }  // end of fun
     lcd.setCursor(17,0);
     lcd.print("                ");
     lcd.setCursor(17,1);
     lcd.print("                ");  
        }
  
void loop ()  {  //Main program loop - most things in here!
    static long virtualPosition=0;    // without STATIC it does not count correctly!!!
    tmElements_t tm;    // This sectionm reads the time from the RTC, sets it in tmElements tm (nice to work with), then displays it.
    RTC.read(tm); 
    lcd.setCursor(0, 0);
    printDigits(tm.Hour); //call to print digit function that adds leading zeros that may be missing
    lcd.print(":");
    printDigits(tm.Minute);
    lcd.print(":");
    printDigits(tm.Second);
    lcd.print("  ");
    lcd.print("Qty ");
    lcd.print(feedQty);
    lcd.print(" ");
    lcd.setCursor(0,1);
    lcd.print("1)");
    printDigits(feed1hour);
    lcd.print(":");
    printDigits(feed1minute);
    lcd.print(" 2)");
    printDigits(feed2hour);
    lcd.print(":");
    printDigits(feed2minute);
    
    
// MAIN BREAKOUT "IF" SECION BELOW THAT MONITORS THE PUSH BUTTON AND ENTERS PROGRAMMING IF IT'S PUSHED 

 if (!(digitalRead(PinSW))) {      // check if pushbutton is pressed
                                   // if YES then enter the programming subroutine
    lcd.blink();   // Turn on the blinking cursor:
    lcd.setCursor(5,0);
    lcd.print(" SET");
    virtualPosition = tm.Hour;  //needed or the hour will be zero each time you change the clock.
       do  {
            lcd.setCursor(0,0);   // put cursor at Time Hour  
            delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
            if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the hour of time -
            tm.Hour = virtualPosition;
            RTC.write(tm);
            lcd.setCursor(0, 0);
            printDigits(tm.Hour);  // then re-print the hour on the LCD
         } while ((digitalRead(PinSW)));  // do this "do" loop while the PinSW button is NOT pressed
         lcd.noBlink();
         delay(1000);
       
       //   SET THE MINS
        
        lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = tm.Minute;  //needed or the minute will be zero each time you change the clock.
       do  {
       lcd.setCursor(3,0);   // put cursor at Time Mins
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the min of time -
            tm.Minute = virtualPosition;
            RTC.write(tm);
            lcd.setCursor(3, 0);
            printDigits(tm.Minute);  // then re-print the min on the LCD
         } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);
    
       //   SET THE QTY - Feed quantity
        
       lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = feedQty;  //needed or the qty will be zero.
       do  {
       lcd.setCursor(14,0);   // put cursor at QTY
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the feed qty
            feedQty = virtualPosition;
            lcd.setCursor(14, 0);
            lcd.print(feedQty);
           } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);
         
     //   SET THE Feed1 Hour
        
       lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = feed1hour;  //needed or will be zero to start with.
       do  {
       lcd.setCursor(2,1);   // put cursor at feed1hour
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the feed1 hour
            feed1hour = virtualPosition;
            lcd.setCursor(2,1);
            printDigits(feed1hour);
           } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);  
  
      //   SET THE Feed1 Mins
        
       lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = feed1minute;  //needed or will be zero to start with.
       do  {
       lcd.setCursor(5,1);   // put cursor at feed1minute
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the feed1 minute
            feed1minute = virtualPosition;
            lcd.setCursor(5,1);
            printDigits(feed1minute);
           } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);   

   //   SET THE Feed2 Hour
        
       lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = feed2hour;  //needed or will be zero to start with.
       do  {
       lcd.setCursor(10,1);   // put cursor at feed1hour
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the feed1 hour
            feed2hour = virtualPosition;
            lcd.setCursor(10,1);
            printDigits(feed2hour);
           } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);  
  
      //   SET THE Feed2 Mins
        
       lcd.blink();   // Turn on the blinking cursor:
       virtualPosition = feed2minute;  //needed or will be zero to start with.
       do  {
       lcd.setCursor(13,1);   // put cursor at feed1minute
       delay(500);   // Delay needed or same button press will exit do-while as while is checking for another button push!
           if (TurnDetected)  {		    // do this only if rotation was detected
            if (up)
              virtualPosition--;
            else
            virtualPosition++;
            TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
                               }
            // Here I change the feed1 minute
            feed2minute = virtualPosition;
            lcd.setCursor(13,1);
            printDigits(feed2minute);
           } while ((digitalRead(PinSW)));
         lcd.noBlink();
         delay(1000);                         
      
  }  // end of main IF rotary encoder push button checker
 
   // CHECK FOR MANUAL FEED BUTTON
  buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH) {
    feed();
  }
  // CHECK FEEDING TIME AND FEED IF MATCHED
  
  if (tm.Hour == feed1hour && tm.Minute == feed1minute && tm.Second == 0)  {  // if I dont' check seconds are zero
    feed();                                                                   // then it'll feed continuously for 1 minute!
      }
  if (tm.Hour == feed2hour && tm.Minute == feed2minute && tm.Second == 0)  {
    feed();
      }  
  
}   // End of main Loop

void printDigits(int digits){   // utility function for digital clock display: prints leading 0
   if(digits < 10)
    lcd.print('0');
   lcd.print(digits);
 }
 
 void feed() {
   
   lcd.setCursor(17,0);
   lcd.print("   Meowwwww!");
    for (int positionCounter = 0; positionCounter < 16; positionCounter++) {
     // scroll one position left:
     lcd.scrollDisplayLeft(); 
     // wait a bit:
     delay(150);
         }
  
   // Stir servo section     If you don't need a stir servo simply comment out all fo this until the Auger rotate section  
   stirServo.attach(10);      // I don't know if I need one either but I'm adding it now as it's easiest before I build it!
    for(pos = 0; pos <= 180; pos += 1) {                                  
    stirServo.write(pos);              
    delay(5);   } 
   for(pos = 180; pos>=0; pos-=1)  {                                
    stirServo.write(pos);          
    delay(10);   } 
    delay(200);
   for(pos = 0; pos <= 180; pos += 1) {                                  
    stirServo.write(pos);              
    delay(10);   } 
   for(pos = 180; pos>=0; pos-=1)  {                                
    stirServo.write(pos);          
    delay(5);   }  
   stirServo.detach();  
   
   // rotate the Auger   
   feedServo.attach(PIN_SERVO);
    for (int cnt = 0; cnt < feedQty; cnt++)
    {
      feedServo.write(feedRate);  //the feedrate is really the feed direction and rate.
      delay(600);   //this delay sets how long the servo stays running from the previous command
      feedServo.write(feedReversal);  //...until this command sets the servo a new task!
      delay(200);
      feedServo.write(feedRate);  
      delay(600);   
      feedServo.write(feedReversal);  // if you want to increase the overall feedrate increase the forward delays (1000 at the moment)
      delay(200);                     // or better still just copy and past the forward & backwards code underneath to repeat
          }                           // that way the little reverse wiggle is always there to prevent jams
     feedServo.detach();
     for (int positionCounter = 0; positionCounter < 16; positionCounter++) {
     // scroll one position left:
     lcd.scrollDisplayRight(); 
     // wait a bit:
     delay(150);
         }
             }

Step 6: Update: I've Replaced the Feed Servo With a NEMA 17 Style Stepper Motor

My feed servo started to refuse to go backwards recently. I'm not sure why, and I've never been keen on the PWM method of controlling it's direction. All seemed a bit like guess work, so, I replaced it with a Nema 17 style stepper motor, driven by an Easydriver stepper driver.

The changes are simple. Use a Nema 17 size stepper of your choice. I had one spare. Mine was a 6 wire - I idnetified which wires were the pair for coil "A" and which for coil "B" and ignored the remaining wires. With four wire steppers you won't need to worry about this!.

The wiring of the Easydriver (https://www.sparkfun.com/products/12779) goes like this:

Motor plus goes to your power source of 12v+

Motor Gnd goes to ground -

Motor "A" pins go to the motor A pair of wires of your stepper.

Motor "B" pins go to the motor B pair of wired of your stepper.

The "Step" pin goes to digital pin 13 on your arduino.

The "Dir" pin goes to analogue pin A0 on your arduino (although we are using this as a digital pin)

Finally, the "Enable" pin on the Easydriver goes to the arduino A1 pin. This is used to turn the Easydriver power on and off for power and heat saving..

I just soldered the wires directly onto my existing PCB so if you use my PCB design you can do the same. I wasn't going to redesign the PCB just for this change and it took less than 10 mins to add on the easydriver and it's connections. I just disconnected the old feed servo.

The code is not annoteded as well as I'd like as it was done in a hurry but the changes are obvious. I've used the accellstepper library to drive the stepper, and I've used the enable pin on the Easydriver to power down the motor coils when not in use, or it gets really warm being held in place for hours upon end. Using the enable pin to power it down makes good sense - it doesn't need to hold the auger still when not in use!

Animals Contest

Runner Up in the
Animals Contest

Automation Contest

Participated in the
Automation Contest