Automatic pet feeder on an Arduino Uno
A DS3231 clock opens an MG996R food gate at three meal times a day, with a chirp first, the next meal on an I2C LCD and a FEED NOW button. The whole build, an Arduino Uno and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate Runs in your browser. Free, and no account needed.
The parts retail for about $44. On Mokxi they're free.
The note on the canvas
Word for word, as it sits beside the circuit in the editor.
Try it
1. Press Run. The clock starts at 07:29:50 and the screen shows the next meal, 07:30.
2. Wait ten seconds: the buzzer chirps, the gate opens for a portion and closes, and the serial monitor logs the meal.
3. Press FEED NOW for an extra portion at any time.
4. Change MEALS in the sketch to your own times.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Automatic pet feeder: a DS3231 keeps the time, and at each meal time a
// servo opens the food gate for one portion and closes it again.
//
// DS3231 SDA and SCL to the Uno's SDA and SCL, VCC 5V, GND
// LCD 16x2 with an I2C backpack at 0x27: SDA A4, SCL A5, VCC 5V, GND
// (A4 and A5 are the same I2C bus as SDA and SCL)
// servo MG996R signal on pin 9, VCC from its own 5 to 6 V supply,
// GND joined to the Uno's
// button FEED NOW on pin 2, to GND (the pin's pull-up holds it HIGH)
// buzzer active buzzer on pin 8, other leg to GND
//
// HOW IT WORKS
// The screen shows the time and the next meal. When the clock reaches a
// meal time the buzzer chirps three times (so the pet learns the sound),
// the gate opens to OPEN_ANGLE for PORTION_MS and closes. Each meal is
// logged on the serial monitor with the time it was served.
//
// A meal is served once: the sketch remembers the day and minute it last
// fed, so the minute the clock sits on 07:30 gives one portion, not sixty.
// FEED NOW gives an extra portion at any time, and is logged as one.
//
// WHAT TO CHANGE
// MEALS the meal times, as hour and minute, up to eight
// PORTION_MS how long the gate stays open: a longer time, a bigger meal
// OPEN_ANGLE how far the gate opens; CLOSED_ANGLE where it rests
//
// SETTING THE CLOCK. A DS3231 keeps time on its coin cell. To set a real
// one, run rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); once from setup()
// and upload again without it. In Mokxi the clock starts at the time in its
// "start" property.
#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo gate;
const int SERVO_PIN = 9;
const int BUTTON_PIN = 2;
const int BUZZER_PIN = 8;
struct Meal {
uint8_t hour;
uint8_t minute;
};
const Meal MEALS[] = {{7, 30}, {12, 0}, {18, 0}};
const int MEAL_COUNT = sizeof(MEALS) / sizeof(MEALS[0]);
const unsigned long PORTION_MS = 1500;
const int OPEN_ANGLE = 90;
const int CLOSED_ANGLE = 0;
long lastFed = -1; // the day and minute of the last scheduled meal
int lastSecond = -1;
void two(Print &out, int n) {
if (n < 10) out.print('0');
out.print(n);
}
void showTime(Print &out, const DateTime &t, bool seconds) {
two(out, t.hour());
out.print(':');
two(out, t.minute());
if (seconds) {
out.print(':');
two(out, t.second());
}
}
// The next meal after this minute, wrapping round to tomorrow's first.
int nextMeal(const DateTime &t) {
int now = t.hour() * 60 + t.minute();
for (int i = 0; i < MEAL_COUNT; i++) {
if (MEALS[i].hour * 60 + MEALS[i].minute > now) return i;
}
return 0;
}
void chirp() {
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
delay(100);
}
}
void feed(const char *why) {
DateTime t = rtc.now();
lcd.setCursor(0, 1);
lcd.print("Feeding... ");
chirp();
gate.write(OPEN_ANGLE);
delay(PORTION_MS);
gate.write(CLOSED_ANGLE);
showTime(Serial, t, true);
Serial.print(" ");
Serial.println(why);
lastSecond = -1; // redraw the screen at once
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
gate.attach(SERVO_PIN);
gate.write(CLOSED_ANGLE);
lcd.init();
lcd.backlight();
if (!rtc.begin()) {
Serial.println("no DS3231 at 0x68: check SDA, SCL and VCC");
lcd.print("No clock found");
while (true) delay(10);
}
if (rtc.lostPower()) Serial.println("the clock lost power: set the time");
Serial.print("pet feeder ready, meals at");
for (int i = 0; i < MEAL_COUNT; i++) {
Serial.print(' ');
two(Serial, MEALS[i].hour);
Serial.print(':');
two(Serial, MEALS[i].minute);
}
Serial.println();
}
void loop() {
DateTime t = rtc.now();
// A meal time: once per day and minute, however long the minute lasts.
long stamp = (long)(t.unixtime() / 60);
for (int i = 0; i < MEAL_COUNT; i++) {
if (t.hour() == MEALS[i].hour && t.minute() == MEALS[i].minute && stamp != lastFed) {
lastFed = stamp;
char why[] = "meal 0 served";
why[5] = '1' + i;
feed(why);
}
}
if (digitalRead(BUTTON_PIN) == LOW) {
feed("extra portion (FEED NOW)");
while (digitalRead(BUTTON_PIN) == LOW) delay(10);
}
if (t.second() != lastSecond) {
lastSecond = t.second();
lcd.setCursor(0, 0);
lcd.print("Time ");
showTime(lcd, t, true);
lcd.print(" ");
const Meal &next = MEALS[nextMeal(t)];
lcd.setCursor(0, 1);
lcd.print("Next meal ");
two(lcd, next.hour);
lcd.print(':');
two(lcd, next.minute);
lcd.print(' ');
}
delay(50);
}
Parts list
10 parts, plus the jumper wires. They retail for about $44; on Mokxi they're free.
- 1 × Arduino Uno R3
- 1 × Half breadboard
- 1 × Real-time clock, DS3231
- 1 × Servo, MG996R metal gear
- 1 × Pushbutton
- 1 × Buzzer
- 1 × Character LCD with I2C backpack
- 1 × Power, 6 V
- 2 × Power, 5 V
How it is wired
16 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Uno R3 pin SCL; Real-time clock, DS3231 pin SCL
- Arduino Uno R3 pin SDA; Real-time clock, DS3231 pin SDA
- Ground: Arduino Uno R3 pin GND
- Arduino Uno R3 pin 9; Servo, MG996R metal gear pin PWM
- Arduino Uno R3 pin 8; Buzzer pin 1
- Arduino Uno R3 pin 2; Pushbutton pin 1b
- Arduino Uno R3 pin A4; Character LCD with I2C backpack pin SDA
- Arduino Uno R3 pin A5; Character LCD with I2C backpack pin SCL
- 5 V: Real-time clock, DS3231 pin VCC
- Ground: Real-time clock, DS3231 pin GND
- Ground: Servo, MG996R metal gear pin GND
- 6 V: Servo, MG996R metal gear pin VCC
- Ground: Pushbutton pin 2b
- Ground: Buzzer pin 2
- Ground: Character LCD with I2C backpack pin GND
- 5 V: Character LCD with I2C backpack pin VCC
Questions and answers
Will it feed twice if the clock sits on the meal minute?
No. The sketch remembers the day and minute of the last scheduled meal, so each meal time serves one portion however long that minute lasts.
How do I make the portions bigger or smaller?
Change PORTION_MS, how long the gate stays open (1.5 s here). OPEN_ANGLE sets how far it opens.
How do I set my own meal times?
Edit the MEALS list at the top of the sketch, as hour and minute pairs. The LCD always shows the next one.
Why does the servo have its own power supply?
An MG996R can pull over an amp when it moves, more than the Uno’s 5 V pin can give. A separate 5 to 6 V supply with the grounds joined keeps the board from resetting.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.