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.