
Automatic Scheduled Pet/Livestock Feeder over IoT
Build an automatic feeder using an ESP32 + SG90 servo as the food-dispensing mechanism, paired with a DS3231 RTC module to keep feeding times accurate even without WiFi.
Build an automatic feeder using an ESP32 + SG90 servo as the food-dispensing mechanism, paired with a DS3231 RTC module to keep feeding times accurate even without WiFi.
Detailed guide
Build an automatic feeder using an ESP32 + SG90 servo as the food-dispensing mechanism, paired with a DS3231 RTC module to keep feeding times accurate even without WiFi.
1. Introduction
Feeding pets or livestock on time every day is a tedious but important routine, especially when the owner is away or raising a large number of animals. This project uses an ESP32 to drive an SG90 servo as the dispensing mechanism (rotating a hopper door to let food drop by gravity), paired with a DS3231 real-time clock (RTC) module to keep the feeding schedule accurate even if the ESP32 loses WiFi or power temporarily — the DS3231 has a backup CR2032 battery that keeps time correctly.
The advantage of using an RTC instead of relying solely on NTP over WiFi: the system keeps feeding on schedule even without a network connection, which suits farm/barn environments with unreliable WiFi.
2. Components Needed
| Component | Qty | Reference Price |
|---|---|---|
| ESP32 DevKit V1 (30-pin) | 1 | ~90,000 - 120,000₫ |
| SG90 — Mini 9g Servo Motor | 1 | ~25,000 - 35,000₫ |
| DS3231 Real-Time Clock Module (with CR2032 battery) | 1 | ~25,000 - 40,000₫ |
| A food hopper/container + a servo-driven dispensing door (DIY or 3D-printed) | 1 set | Varies by material |
| Jumper wires, a 5V/USB power supply for the ESP32 | 1 set | ~20,000 - 30,000₫ |
3. Wiring Diagram
| Servo SG90 | ESP32 |
|---|---|
| VCC (red) | VIN (5V) |
| GND (brown) | GND |
| Signal (yellow) | GPIO13 |
| DS3231 RTC | ESP32 |
|---|---|
| VCC | 3V3 |
| GND | GND |
| SCL | GPIO22 (I2C_SCL) |
| SDA | GPIO21 (I2C_SDA) |
Note: the SG90 servo draws a stall current of about 500-700mA, so its VCC must come from the ESP32's VIN (5V) pin rather than 3V3, to avoid a voltage sag that resets the ESP32 when the servo starts moving. The DS3231 uses the I2C bus, so it only needs two signal wires (SCL/SDA) — use the ESP32's default I2C pin pair (GPIO22/GPIO21) so you don't need to reconfigure Wire.
4. Example #1 — Testing the DS3231 and Reading the Current Time over Serial
#include <Wire.h>
#include <RTClib.h>
RTC_DS3231 rtc;
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA, SCL
if (!rtc.begin()) {
Serial.println("Khong tim thay DS3231!");
while (1) delay(1000);
}
if (rtc.lostPower()) {
Serial.println("RTC mat gio, dat lai theo thoi gian compile...");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
void loop() {
DateTime now = rtc.now();
Serial.printf("%02d:%02d:%02d %02d/%02d/%04d\n",
now.hour(), now.minute(), now.second(),
now.day(), now.month(), now.year());
delay(1000);
}
5. Example #2 — Manually Dispensing Food via a Serial Command
#include <ESP32Servo.h>
#define SERVO_PIN 13
Servo feederServo;
void setup() {
Serial.begin(115200);
ESP32PWM::allocateTimer(0);
feederServo.setPeriodHertz(50);
feederServo.attach(SERVO_PIN, 500, 2400);
feederServo.write(0); // vi tri dong
Serial.println("Go 'feed' de xa thuc an thu cong");
}
void loop() {
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd == "feed") {
feederServo.write(90); // mo cua
delay(1200);
feederServo.write(0); // dong cua
Serial.println("Da xa thuc an!");
}
}
}
6. Example #3 — The Real Application: an Automatic Feeder on an RTC Schedule
/*
May cho vat nuoi an tu dong theo lich qua IoT
Board: ESP32 DevKit V1 (30 pins)
Co cau chap hanh: Servo SG90 (co cau xa thuc an - quay cua phieu)
Dong ho thoi gian thuc: DS3231 RTC module (I2C) - giu lich cho an ngay ca khi mat WiFi
Wiring (khop voi diagram tren IoTLabs Maker):
Servo SG90 SIGNAL -> GPIO13 VCC -> VIN(5V) GND -> GND
DS3231 SDA -> GPIO21 (I2C_SDA) SCL -> GPIO22 (I2C_SCL)
VCC -> 3V3 GND -> GND
Hanh vi:
- Doc RTC moi giay, so sanh voi bang lich cho an (gio:phut co dinh)
- Khi khop 1 muc lich va chua chay trong phut do -> quay servo xa thuc an (mo -> dong)
- Neu RTC mat lien lac (I2C loi) -> bao "rtc_error", khong doan lich, cho retry
*/
#include <Wire.h>
#include <RTClib.h>
#include <ESP32Servo.h>
#define SERVO_PIN 13
Servo feederServo;
RTC_DS3231 rtc;
bool rtcOk = false;
unsigned long lastCheck = 0;
const unsigned long CHECK_INTERVAL_MS = 1000;
// Lich cho an co dinh: {gio, phut}. Them/bot theo nhu cau thuc te.
struct FeedSlot { uint8_t hour; uint8_t minute; bool firedToday; };
FeedSlot feedSlots[] = {
{7, 0, false},
{12, 0, false},
{18, 0, false},
};
const int FEED_SLOT_COUNT = sizeof(feedSlots) / sizeof(feedSlots[0]);
int lastResetDay = -1;
const int SERVO_CLOSED_ANGLE = 0;
const int SERVO_OPEN_ANGLE = 90;
const unsigned long DISPENSE_OPEN_MS = 1200; // thoi gian mo cua de thuc an roi xuong
void dispenseFood() {
Serial.println(F("{\"event\":\"dispense_start\"}"));
feederServo.write(SERVO_OPEN_ANGLE);
delay(DISPENSE_OPEN_MS);
feederServo.write(SERVO_CLOSED_ANGLE);
Serial.println(F("{\"event\":\"dispense_done\"}"));
}
void setup() {
Serial.begin(115200);
delay(300);
Serial.println();
Serial.println(F("=== May cho an tu dong IoTLabs - Boot OK ==="));
ESP32PWM::allocateTimer(0);
feederServo.setPeriodHertz(50);
feederServo.attach(SERVO_PIN, 500, 2400);
feederServo.write(SERVO_CLOSED_ANGLE);
Wire.begin(21, 22);
rtcOk = rtc.begin();
if (!rtcOk) {
Serial.println(F("{\"status\":\"rtc_error\",\"note\":\"khong tim thay DS3231, kiem tra day I2C\"}"));
} else if (rtc.lostPower()) {
// Lan dau cap nguon / het pin CR2032 -> set tam theo thoi gian compile,
// nguoi dung nen chinh lai qua NTP/app that khi co WiFi.
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
Serial.println(F("{\"status\":\"rtc_time_reset\"}"));
}
Serial.print(F("Lich cho an: "));
for (int i = 0; i < FEED_SLOT_COUNT; i++) {
Serial.print(feedSlots[i].hour);
Serial.print(':');
Serial.print(feedSlots[i].minute);
Serial.print(F(" "));
}
Serial.println();
}
void loop() {
unsigned long now = millis();
if (now - lastCheck < CHECK_INTERVAL_MS) {
return;
}
lastCheck = now;
if (!rtcOk) {
// Retry dinh ky thay vi while() vo han - cho phep he thong tiep tuc bao cao trang thai
rtcOk = rtc.begin();
Serial.println(F("{\"status\":\"rtc_error\",\"retrying\":true}"));
return;
}
DateTime nowDt = rtc.now();
if (nowDt.day() != lastResetDay) {
for (int i = 0; i < FEED_SLOT_COUNT; i++) {
feedSlots[i].firedToday = false;
}
lastResetDay = nowDt.day();
}
for (int i = 0; i < FEED_SLOT_COUNT; i++) {
if (!feedSlots[i].firedToday &&
nowDt.hour() == feedSlots[i].hour &&
nowDt.minute() == feedSlots[i].minute) {
feedSlots[i].firedToday = true;
dispenseFood();
}
}
Serial.print(F("{\"time\":\""));
if (nowDt.hour() < 10) Serial.print('0');
Serial.print(nowDt.hour());
Serial.print(':');
if (nowDt.minute() < 10) Serial.print('0');
Serial.print(nowDt.minute());
Serial.print(':');
if (nowDt.second() < 10) Serial.print('0');
Serial.print(nowDt.second());
Serial.println(F("\",\"status\":\"ok\"}"));
}
7. Common Issues
| Issue | Cause | Fix |
|---|---|---|
| The ESP32 resets itself when the servo starts moving | Voltage sag from the servo's startup current pulled through a weak USB supply, or the servo's VCC is powered from 3V3 | Power the servo from the VIN (5V) pin, using a separate 5V/1A+ supply if needed |
| rtc.begin() always returns false | Wrong SDA/SCL pins in Wire.begin(), or a loose/broken I2C wire | Check that Wire.begin(21, 22) has the correct order (SDA, SCL) matching the GPIO21/GPIO22 wiring |
| The feeder dispenses twice in a row during the same scheduled minute | Missing the firedToday flag, or the flag is reset at the wrong time | Use the firedToday array, reset once per new day as in example #3, not reset per minute |
| After a power loss, the RTC time is completely wrong | The DS3231 module's backup CR2032 battery has died | Replace the CR2032 battery; check rtc.lostPower() to detect this condition |
8. Summary
This automatic feeder solves the problem of on-time feeding without needing someone present, especially handy when the owner is away for extended periods. Using a DS3231 RTC instead of relying solely on NTP over WiFi keeps the system accurate even without a network connection. Possible extensions: add a load cell to confirm enough food actually dropped, or send a Telegram/MQTT notification on every successful feeding so the owner can monitor it remotely.