Introduction: Arduino Adjustable Pet Food Dispenser
Hello there! Welcome to our Instructable for our Arduino Pet Food Dispenser.
We are Dan and Tom, and we are product design students at Cardiff Metropolitan University in South Wales, we were given this challenge as part of an assessment brief to show that we have a basic understanding of coding, electronic prototyping and mechanics
So here goes...
Here's what you'll need
Electrical Components
Arduino Uno or Mega
LCD Screen 12x2
L298N Motor Driver Module
DS3231 Real Time Clock Module
KY-040 Rotary Encoder
Solderless Breadboard
5v Breadboard Power Supply
Jumper Cables (a good mix of male and female)
Different colour insulated wire
Resistors (220 and 10k ohm)
Switch button
3 LEDs
High Torque, Low Speed Motor
Tools
Soldering Iron
Wire Cutters
Wire Stripers
Hacksaw
Metal/wood File
Dremel Cutting Tool
Epilog Laser Cutter (or equivalent)
Ultimaker 3D Printer (or equivalent) or 3D milling machine
Materials
4 Sheets of 3mm thick Acrylic
1 Sheet of 6mm thick MDF
4 Lengths of M10 Threaded Metal Rod (Approx. 140mm each)
8 M10 Washers
8 M10 Nuts
Solder
Shrink Wrap (Or insulation tape)
M3 Nuts and bolts for mounting Arduino and components
4 Metal Bearings (We used 26 Outside Diamter and 10mm Internal Diameter)
10mm Rod
Adhesive (we used Gorilla Glue however other brands or adhesives may be suitable)
Step 1: Making the Dispenser Mechanism
To make the whole thing work, we made a few different assemblies
-Food Storage Box and Funnel
-Dispensing Mechanism
-Base and Chute
-Interface Assembly
Food Storage Box and Funnel
The parts for this assembly are all cut from 3mm acrylic and all fit together using finger joints. The parts are all cut using an Epilog Laser Cutter and the vector files were developed using Corel Draw X7.
Acrylic is a suitable material for holding the food as it is a food safe material and can be easily laser cut. This part could be made from different materials by hand however please ensure that the material is suitably finished to ensure it is safe for food storage.
[Picture of top box]
Food Dispenser Assembly
This part is made from Acrylic tubing (50mm and 30mm), two 3D printed Screw parts, 6mm MDF, Metal Bearings and metal rod.
Cut the main MDF bracket from the Dxf file bellow called "MDF Bracket.dxf".
Cut the 50mm Pipe to 140mm and drill a 30mm hole 40mm from one end of the tube and the fit the 30mm tube through the top (as shown in the illustration bellow). You may need to use a Dremel to grind away some material to make it fit.
The Screw parts are 3D printed in halves and then later joined, this is due to limitations in 3D printing capabilities on the Ultimaker Printers. In total the printing tim should be roughly 12-14 hours per part depending on your chosen settings. We used a 0.4mm nozzle, a normal quality print and there was no need for support structures.
Fit two 30mm long lengths of metal bar or tube and press them into the bearings.
Once the 3D printing is done, press these bearings into the screw parts, you may have to file away some material as we printed the parts to ensure a tight fit.
Slide the Screw through the tubes, slide the tubes through the bracket and then the axles into the end brackets as shown in the illustration.
This set up ensures that the amount of food dispensed is adjustable, dependant on the size of animal you have. The bearings reduce friction on the moving parts and therefore reduces the load on the motor. By utilising two screws, the amount of food will be more easily controlled, one is mounted half turned and the other fully turned meaning that there is constantly food flowing from the dispenser. We chose to have 3 full rotations on our screw parts (3 full "threads") as this allowed smaller amounts of food to be measured to ensure your pet doesn't get over or underfed.
[You could CNC mill the screw part in one piece if you have the facilities available, however we milled two pieces of medium density model board and chose to 3D print later as the Modelboard would have require a lot of finishing (sanding and sealing) to ensure it was safe to touch food).
Base and Chute
The base and chute is fairly straight forward and is constructed similarly to the food storage box and funnel earlier on in this section. Download the DXF file bellow labelled "Base and chute.dxf".
Interface Assembly
The interface assembly consists of 4 pieces of laser cut acrylic, each piece is constructed using M10 threaded rod and nuts as hsown...
[picture of interface]
Later, the rotary encoder, LCD and LEDs can be fitted...
Assembly
Once you have assembled the dispenser assembly (MDF part), mock it up to your assembled base and chute part and cut a slot in each 50mm tube so that the food can fall into the chute and be collected, ready to slide straight into your pets bowl!
Step 2: Circuit
Below you'll be able to see a Fritzing Diagram of the circuit we used. Depending on the componets you are using (I know there are many different versions of RTC and motor driver modules) you may be able to use different libraries.
Step 3: Code
This is the code for the arduino. This code checks the time and compares it with the alarms, if they match up it will turn the motor, pushing the food out. To work out how long the motor should turn for, we worked out how much food is released for each turn. One turn of one screw pushed out 10g and each rotation takes 11 seconds. Therefore 2 screws will push 20g every 11 seconds. We researched dog food portions and worked out that a small dog needed approximately 50g of food, a medium sized needs 140g and a large needs about 260g. This means that for a small portion the screw turns for 27.5 seconds, a medium portion turns for about 77 seconds and a large for about 141 seconds.
Depending on the food you're using you may want to change this. You can usually find the correct portions on the back of the packaging. Remember that the time scale in the arduino IDE is in milliseconds. ((recommended portions size)/20)*11 = The length of time the cog should turn The library’s that we used can all be found on the arduino website, they are called time.h, DS1307RTC.h. The other two are already installed in the Arduino IDE. https://www.arduino.cc/en/Reference/Libraries
//library's:
#include
#include // needed for the RTC libraty
#include
#include // Real Time Clock Library
// LCD setup
LiquidCrystal lcd(12, 11, 5, 8, 7, 6);
volatile boolean TurnDetected; //Allows us to use && statements
volatile boolean up;
//Setup pin integers
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;
const int motorPin1 = 10;
const int motorPin2 = 13; // the number of the pushbutton pin for manual feed 13
//setup button
int buttonState = 0;
//setup food dispensing time
int feed1hour = 10;
int feed1minute = 30;
int feed2hour = 17;
int feed2minute = 30;
//setup food amount
int feedQty = 1;
//LED setup
int LED1 = 0;
int LED2 = 1;
int LED3 = 9;
// set time
int tm.Hour = 1;
int tm.Minute = 1;
int tm.Hour = 1
void isr () {
if (digitalRead(PinCLK)) //setup for the click on the rotary encoder
up = digitalRead(PinDT);
else
up = !digitalRead(PinDT);
TurnDetected = true;
}
void setup () {
//setting time
// LED setup
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
pinMode(LED3, OUTPUT);
// 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("Tom & Dan's Dog"); //startup screen
lcd.setCursor(17,1);
lcd.print("Food Dispenser");
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);
}
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 RT, 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("Size ");
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);
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) {
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(5000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
// CHECK FEEDING TIME AND FEED IF MATCHED
if (tm.Hour == feed1hour && tm.Minute == feed1minute && tm.Second == 0 && feedQty == 1) { // if I dont' check seconds are zero
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(5000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
if (tm.Hour == feed1hour && tm.Minute == feed1minute && tm.Second == 0 && feedQty == 2) { // if I dont' check seconds are zero
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(10000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
if (tm.Hour == feed1hour && tm.Minute == feed1minute && tm.Second == 0 && feedQty == 3) { // if I dont' check seconds are zero
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(15000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
if (tm.Hour == feed2hour && tm.Minute == feed2minute && tm.Second == 0 && feedQty == 1) {
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(27500);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
if (tm.Hour == feed2hour && tm.Minute == feed2minute && tm.Second == 0 && feedQty == 2 ) {
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(77000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
if (tm.Hour == feed2hour && tm.Minute == feed2minute && tm.Second == 0 && feedQty == 3) {
digitalWrite(motorPin1, HIGH);
digitalWrite(motorPin2, LOW);
delay(141000);
digitalWrite(motorPin1, LOW);
digitalWrite(motorPin2, LOW);
}
//LED Settings
if (feedQty == 1){
digitalWrite(LED1, HIGH);
digitalWrite(LED2, LOW);
digitalWrite(LED3, LOW);
}
if (feedQty == 2){
digitalWrite(LED1, LOW);
digitalWrite(LED2, HIGH);
digitalWrite(LED3, LOW);
}
if (feedQty == 3){
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(LED3, HIGH);
}
} // 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("Extra Feed!");
for (int positionCounter = 0; positionCounter < 16; positionCounter++) {
// scroll one position left:
lcd.scrollDisplayLeft();
// wait a bit:
delay(150);
}
}
Step 4: Watch Those Tails Wag and Enjoy!
We used CorelDraw and Adobe Illustrator to create the laser cut
files and we used SolidWorks and Cura for the 3d printing. All the files are here so feel free to change and improve our work. Depending on the 3d printer you use, you may need to make some changes in the file and you will probably need to do some sanding when printed. We hope you enjoy building your own dispenser and if you want to see more of our work have a look at @tomowendesign on instagram or danielbartlett19@wordpress.co.uk.