Automatic Scheduled Pet/Livestock Feeder over IoT
Intermediate1/8/2026- Author: IoTSpark Maker

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.

esp32servortc-ds3231livestocksmart-farmautomationiot
0 steps3 components

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

ComponentQtyReference Price
ESP32 DevKit V1 (30-pin)1~90,000 - 120,000₫
SG90 — Mini 9g Servo Motor1~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 setVaries by material
Jumper wires, a 5V/USB power supply for the ESP321 set~20,000 - 30,000₫

3. Wiring Diagram

Servo SG90ESP32
VCC (red)VIN (5V)
GND (brown)GND
Signal (yellow)GPIO13
DS3231 RTCESP32
VCC3V3
GNDGND
SCLGPIO22 (I2C_SCL)
SDAGPIO21 (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

IssueCauseFix
The ESP32 resets itself when the servo starts movingVoltage sag from the servo's startup current pulled through a weak USB supply, or the servo's VCC is powered from 3V3Power the servo from the VIN (5V) pin, using a separate 5V/1A+ supply if needed
rtc.begin() always returns falseWrong SDA/SCL pins in Wire.begin(), or a loose/broken I2C wireCheck 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 minuteMissing the firedToday flag, or the flag is reset at the wrong timeUse 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 wrongThe DS3231 module's backup CR2032 battery has diedReplace 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.