Skip to main content

Automatic Cat Feeder

After waking up many mornings to my cat walking on my face to get me to feed him, I decided to build an automatic feeder. I wanted it to have several features:
  • Weight measurement for portion control
  • Easily adjustable feeding schedule
  • System for identifying the status of the machine
  • Food and water dispensing contained in one unit
There are many similar projects on Instructables, Thingiverse, etc. Most use a motor and a paddlewheel cereal dispenser or a servo/motor and auger to dispense the food. Some use other novel means but one feature that was missing in all of the feeders I looked at was a system for measuring the weight of the food that was dispensed. This was of the highest priority in my design because it allows me to accurately adjust portion size to help my cat maintain a healthy weight.

Mechanics



I started by designing the box and dispensing mechanism. The box is a simple design with a hinged back panel so I can easily access the electronics and mechanisms inside. I chose a NEMA 23 stepper motor I had lying around paired with an 3D printed auger to dispense the food. The auger fits inside a 1.5" PVC tee with one ended fixed to the shaft of the stepper motor.



The food weigh boat was formed from a piece of sheet metal. This is one part that I want to change soon because it did not turn out very well. Without a proper bending brake it is difficult to get clean creases. Also, on the end where food falls out, I was unable to make a lip on one side so food often spills out that side before making it into the PVC pipe.




The weigh boat was then attached to the top of the load cell with two screws. The ground of the load cell is connected to a 9 gram servo that tilts it up to dispense the food. The servo-load cell assembly is supported by bearings in a 3D printed bracket (orange in the gifs).



Electronics

On the diagonal front panel, I included a 16x2 I2C LCD panel and two indicator LEDs. I might add more to the interface panel in the future like a button to override errors and a photosensor to determine when it's necessary to have the LCD display on.

To keep track of time I used a commonly available real time clock (RTC) module, the DS3231, that has many libraries already written for it. This uses I2C communication like the LCD, so together they are connected to the Data and Clock pins. 

To measure the weight of the food I used a 1 kg load cell and HX711 analog-to-digital converter (these are all over Ebay, Amazon, and Banggood). Initially, I mistakenly thought the HX711 used I2C communication but after testing I found out quickly that it didn't... So I switched it to two open digital pins.

The "circuit" board is fairly simple because it is primarily just a hub to distribute signals from the Arduino Nano to all of the breakout boards and various electronic components.
I drew up the plan in an open source electronics layout program called Fritzing. All the wiring was planned out and how it would connect to the perfboard. The 9V battery in the diagram is a stand-in for my 12V wall-wart power supply. On the first iteration of the circuit, I soldered the Nano directly to the perfboard. After some testing, it died and I had to replace it. So, then I did what I should have the first time around and soldered female header pins to the board so I can remove the Nano if it dies again.

Finished Product

 Overall, it turned out pretty good. In the future, I would like to add a water container to the right side next to the food. I intend to use a system like dog water bowl replinsher that uses the vacuum pressure at the top of the container to keep the water from draining. When the water level gets low, air is able to enter through the bottom of the container (pipe in my case that will be run from the water box to the bowl on the floor) and a small amount of water will drain as a result.




Code

The code is has one main function, dispenseFood, which is broken into sub functions like motorStep, servoShake, and error. The RTC time is read and at specific user selected times the motor uses a simple closed-loop control with weight as feedback to dispense the food into a bowl. The LCD is used to display information about the last feeding time and error cases such as a motor/auger jam and a servo jam. I tried to keep it pretty organized and logical.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
//============================================================
//                    LIBRARIES
//============================================================
#include <Q2HX711.h>
#include <HX711.h>
#include <Wire.h> 
//#include <WSWire.h>
#include <LiquidCrystal_I2C.h>
//#include <DS3231.h>
#include <DS3231_Simple.h>
#include <Servo.h>
//#include <stdio.h>

//============================================================
//                    PIN ASSIGNMENTS
//============================================================

// Stepper -----------------------------
#define resetPin 8
#define sleepPin 7
#define stepPin 6
#define dirPin 5
#define MS1 11
#define MS2 10
#define MS3 9

int delayTime = 70;    //step delay in microseconds 70
bool forward = false;
bool backward = true;
int stepCounter = 0;  // keeps track of auger position

// Servo ------------------------------
#define servoPin A3
Servo servo;

// Buzzer -----------------------------
//#define beeperPin

// Panel ---------------------------------
#define greenLEDPin 3
#define redLEDPin 4
#define backlightPin 2
#define buttonPin 5
LiquidCrystal_I2C lcd(0x27, 16, 2);

String feedString;

// HX711 ------------------------------
#define DATPin A1
#define SCKPin A0
float calibration_factor = -2251000;
HX711 scale;
double mass = 0;
double oldMass = 0;

// Real time clock ---------------------
DS3231_Simple  Clock;
DateTime rtcTime;
DateTime lastFedTime;
char lastFedString [16];


bool feedComplete = false;
bool lcdOnComplete = false;
bool lcdOffComplete = false;


//============================================================
//                    SETUP
//============================================================
void setup() {  
  Serial.begin(9600);
  
  
  pinMode(resetPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(MS1, OUTPUT); 
  pinMode(MS2, OUTPUT);
  pinMode(MS3, OUTPUT);

  digitalWrite(resetPin, HIGH);
  digitalWrite(sleepPin, LOW);
  digitalWrite(MS1, HIGH); // L L L Full stepping
  digitalWrite(MS2, HIGH); // H L L Half
  digitalWrite(MS3, HIGH); // L H L Quarter
                           // H H L Eighth
                           // H H H Sixteenth
  
//  pinMode(SCKPin, OUTPUT);
//  pinMode(DATPin, INPUT);

  scale.begin(DATPin, SCKPin);


  
  Serial.println("hi");
  lcd.begin();
  Serial.println("hi");
  pinMode(greenLEDPin, OUTPUT);
  pinMode(redLEDPin, OUTPUT);
  pinMode(backlightPin, OUTPUT);

  scale.set_scale(calibration_factor);
  scale.tare(); //set scale to zero
  
  
  digitalWrite(backlightPin, LOW);
  digitalWrite(greenLEDPin, LOW);
  digitalWrite(redLEDPin, LOW);


  Clock.begin();
  Serial.println("hi");
  lastFedTime.Hour = 255;
  delay(50);
  
}

//============================================================
//                    LOOP
//============================================================
void loop() {

  rtcTime = Clock.read();
  Serial.println(rtcTime.Second);
  
  if((rtcTime.Hour == 8 || rtcTime.Hour == 12 || rtcTime.Hour == 16 || rtcTime.Hour == 20) && rtcTime.Minute == 0) {
    if(feedComplete == false) {
       dispenseFood(8);
       delay(1000);
       dispenseFood(8);
       lastFedTime = rtcTime;
       feedComplete = true;
    }
  }
  
  else if(rtcTime.Minute % 5 == 0) {
    if(lcdOnComplete == false) {
      digitalWrite(backlightPin, HIGH);
      
      if(lastFedTime.Hour == 255) {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Not yet fed");
      }
      else{
        sprintf(lastFedString, "%d:%02d Date:%d/%d", lastFedTime.Hour, lastFedTime.Minute, lastFedTime.Month, lastFedTime.Day);
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Last fed at:");
        lcd.setCursor(0,1);
        lcd.print(lastFedString);
      }
      
      lcdOnComplete = true;
    }
 
  }
  
  else if(rtcTime.Minute % 5 == 1) {
    if(lcdOffComplete == false) {
      lcd.clear();
      digitalWrite(backlightPin, LOW);
      lcdOffComplete = true;
    }
    
  }
  
  else{
    feedComplete = false;
    lcdOnComplete = false;
    lcdOffComplete = false;
  }


}  
  
  


//============================================================
//                    DISPENSEFOOD
//============================================================
void dispenseFood(int foodWeight) {
  // dispense food equal to weight in grams
  
  float currentWeight = getWeight();

  int stepSize = 800; // steps forward per weight check
  stepCounter = 0;
  int motorJamCounter = 0;
  int servoJamCounter = 0;
  
  digitalWrite(sleepPin, HIGH);
  
  
  while (getWeight() < foodWeight) {
    motorStep(stepSize);
//    lcdWrite(String(getWeight()));
    motorJamCounter++;
    error(motorJamCounter < 20, "Auger is jammed"); // jam counter should always be less than 20
  }

  motorJamCounter = 0;
  digitalWrite(sleepPin, LOW);
  servo.attach(servoPin);
  
  while (scale.get_units(10)*1000 > 0.5) {
  // check weight each loop and make sure that less than 0.5 grams of food remains on scale
    servoShake();
    servoJamCounter++;
    error(servoJamCounter < 4, "Servo is jammed"); // servo jam counter should always be less than 4  
  }
//  servoShake();
  servoJamCounter = 0;
  servo.detach();
}

//============================================================
//                    MOTORSTEP
//============================================================
void motorStep(int numSteps) {
  int motorDirection = (numSteps > 0)? HIGH:LOW;
  digitalWrite(dirPin, motorDirection);
  digitalWrite(greenLEDPin, HIGH);
  int backwardSteps = 1200;
  
  for(int x = 0; x < abs(numSteps); x++) {
    digitalWrite(stepPin, HIGH); 
    delayMicroseconds(delayTime); 
    digitalWrite(stepPin, LOW); 
    delayMicroseconds(delayTime);
    stepCounter++;
    
    if (stepCounter >= 2400) {
      // drive backwards if step counter goes past threshold
      digitalWrite(dirPin, LOW); 
      
      for (x = 0; x < backwardSteps; x++) {
        digitalWrite(stepPin, HIGH); 
        delayMicroseconds(delayTime); 
        digitalWrite(stepPin, LOW); 
        delayMicroseconds(delayTime);
      }
      stepCounter = 0;
    }
  }
  digitalWrite(greenLEDPin, LOW);
  delay(50);
}


//============================================================
//                    SERVOSHAKE
//============================================================
void servoShake() {
  for (int servoPos = 1400; servoPos >= 1050; servoPos--) {
    // servo should go from 1200us to 1500us
    servo.writeMicroseconds(servoPos);
    delay(1);
  }

  for(int ii = 0; ii < 40; ii++) {
    delay(60);
    servo.writeMicroseconds(1050);
    delay(60);
    servo.writeMicroseconds(1150);
  }
  delay(500);
  
  for (int servoPos = 1150; servoPos <= 1400; servoPos++) {
    // servo should go from 1500us to 1200us
    servo.writeMicroseconds(servoPos);
    delay(1);
  }
  
}


//============================================================
//                    GETWEIGHT
//============================================================

float getWeight()  {
  return scale.get_units()*1000;
}

//============================================================
//                    ERROR
//============================================================
void error(bool condition, String errorCode) {
  if (!condition) {
    digitalWrite(sleepPin, LOW);
    digitalWrite(backlightPin, HIGH);
    
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Error:");
    lcd.setCursor(0,1);
    lcd.print(errorCode);
    
    while(1) {
      digitalWrite(redLEDPin, HIGH);
      delay(100);
      digitalWrite(redLEDPin, LOW);
      delay(500);
    }
  }
}


//============================================================
//                    LCDWRITE
//============================================================
void lcdWrite(String inputString) {
  lcd.clear();
  lcd.print(inputString);
    
}



Comments

Popular posts from this blog

Depron Mini Skywalker

The 1680mm size Skywalker is a great plane and I have read lots of good things about its little brother. So I decided to try my hand at building a Mini Skywalker. Scaled to about 95% First, I found some good pictures for getting the dimensions and general design. Then, I used the pictures with a few known dimensions to find the rest of them and I drew out the most important parts. I used 6mm Depron with for most of it and some 1" EPS to thicken the nose for space to put my battery and other components. For the wing, I did a fold over style Armin-type airfoil with packing tape on one side. This was my first wing built this way so it didn't turn out great but I think it will do the job. I put a small carbon spar at about 1/3 of the chord. HXT500 5 gram servo installation Fuselage wall Both walls with 1" EPS block to thicken the front Approximate layout I am using an old mini quad motor, 2204 2300kv, with a 5045 prop, FrSky D4RII...

FPV Ground Station Part 1

Since I live in Illinois it is hard to get out to fly in the winter due to the dreadful weather. So I decided to use my time indoors building a ground station so that when the weather becomes more bearable I will be ready for some fun flights. Setup will look something like this I have about a five foot tall tripod that I will use as the base for all of my equipment. To start I drew out a general schematic of how I wanted to set it up. Dragonlink V2 for control on the top of the extendable head, 1.3 GHz biquad antenna for video on the top of the legs, and an electronics box somewhere in the middle. This will have a 2.4 GHz FrSky D4RII receiver to use as the link from my transmitter to the Dragonlink. Also, I will run the video out from the receiver through a DVR and then into the 5.8 GHz transmitter to repeat the signal to either my goggles or monitor. To reduce the wiring I decided to build an electronics box. This will hold all of the components mentioned and using ...