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:
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).
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.
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.
- 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.
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGxn0B0nkhMH8Uj0cddu0xfGixnqD1m6_PCsA2SAKZcHa1zdmJ6R-h5ls_27g7qBMlY_32Dcz7zCxImOyZ4PZVeKuldOW7K-eF6yYKfY31pyJiDllsx0joW9MgT4eA2Da6ZXi3ZbRd4lDC/s640/Design_Collage.jpg)
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.
Mechanics
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGxn0B0nkhMH8Uj0cddu0xfGixnqD1m6_PCsA2SAKZcHa1zdmJ6R-h5ls_27g7qBMlY_32Dcz7zCxImOyZ4PZVeKuldOW7K-eF6yYKfY31pyJiDllsx0joW9MgT4eA2Da6ZXi3ZbRd4lDC/s640/Design_Collage.jpg)
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 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
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
Post a Comment