
Máy cho vật nuôi ăn tự động theo lịch qua IoT
Xây dựng máy cho vật nuôi ăn tự động dùng ESP32 + servo SG90 làm cơ cấu xả thức ăn, kết hợp module RTC DS3231 để giữ lịch cho ăn chính xác theo giờ ngay cả khi mất WiFi.
Xây dựng máy cho vật nuôi ăn tự động dùng ESP32 + servo SG90 làm cơ cấu xả thức ăn, kết hợp module RTC DS3231 để giữ lịch cho ăn chính xác theo giờ ngay cả khi mất WiFi.
Hướng dẫn chi tiết
ESP32 + servo SG90 + RTC DS3231 tạo máy cho ăn tự động theo lịch cố định, giữ đúng giờ ngay cả khi mất WiFi nhờ đồng hồ thời gian thực có pin dự phòng.
1. Giới thiệu
Cho vật nuôi ăn đúng giờ mỗi ngày là việc lặp lại nhàm chán nhưng lại rất quan trọng, đặc biệt khi chủ đi vắng hoặc nuôi số lượng lớn. Dự án này dùng ESP32 điều khiển servo SG90 làm cơ cấu xả thức ăn (quay cửa phiễu để thức ăn rơi xuống theo trọng lực) kết hợp với module đồng hồ thời gian thực DS3231 (RTC) để giữ lịch cho ăn chính xác theo giờ ngay cả khi ESP32 mất kết nối WiFi hoặc bị mất điện tạm thời — DS3231 có pin CR2032 dự phòng nên vẫn giữ đúng giờ.
Ưu điểm của việc dùng RTC thay vì chỉ dựa vào NTP qua WiFi: hệ thống vẫn cho ăn đúng lịch dù mất mạng, phù hợp với môi trường trang trại/chuồng trại có kết nối WiFi không ổn định.
2. Linh kiện cần có
| Linh kiện | Số lượng | Giá tham khảo |
|---|---|---|
| ESP32 DevKit V1 (30 chân) | 1 | ~90.000 - 120.000đ |
| SG90 – Servo Motor Mini 9g | 1 | ~25.000 - 35.000đ |
| DS3231 Real-Time Clock Module (kèm pin CR2032) | 1 | ~25.000 - 40.000đ |
| Phễu/hộp đựng thức ăn + cơ cấu cửa xả gắn servo (tự chế hoặc in 3D) | 1 bộ | tuỳ vật liệu |
| Dây jumper, nguồn 5V/USB cho ESP32 | 1 bộ | ~20.000 - 30.000đ |
3. Sơ đồ kết nối
| Servo SG90 | ESP32 |
|---|---|
| VCC (đỏ) | VIN (5V) |
| GND (nâu) | GND |
| Signal (vàng) | GPIO13 |
| DS3231 RTC | ESP32 |
|---|---|
| VCC | 3V3 |
| GND | GND |
| SCL | GPIO22 (I2C_SCL) |
| SDA | GPIO21 (I2C_SDA) |
Lưu ý: servo SG90 có dòng khởi động (stall current) khoảng 500-700mA, phải cấp nguồn VCC từ chân VIN (5V) của ESP32 chứ không dùng 3V3, tránh sụt áp làm ESP32 tự reset khi servo bắt đầu quay. DS3231 dùng bus I2C nên chỉ cần 2 dây tín hiệu SCL/SDA, dùng đúng cặp chân I2C mặc định của ESP32 (GPIO22/GPIO21) để không phải cấu hình lại Wire.
4. Code ví dụ #1 – Kiểm tra DS3231 và đọc giờ hiện tại qua 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. Code ví dụ #2 – Điều khiển servo xả thức ăn thủ công qua lệnh Serial
#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. Code ví dụ #3 – Ứng dụng thực tế: máy cho ăn tự động theo lịch RTC
/*
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. Lỗi thường gặp
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
| ESP32 tự reset khi servo bắt đầu quay | Sụt áp do dòng khởi động servo kéo qua nguồn USB yếu, hoặc cấp VCC servo từ 3V3 | Cấp servo từ chân VIN (5V), dùng nguồn 5V/1A+ riêng nếu cần |
| rtc.begin() luôn trả về false | Sai chân SDA/SCL trong Wire.begin(), hoặc dây I2C lỏng/đứt | Kiểm tra Wire.begin(21, 22) đúng thứ tự (SDA, SCL) khớp với dây nối GPIO21/GPIO22 |
| Máy cho ăn 2 lần liên tiếp trong cùng 1 phút lịch | Thiếu cờ firedToday hoặc reset cờ sai thời điểm | Dùng mảng firedToday reset mỗi ngày mới như code ví dụ #3, không reset theo phút |
| Sau khi mất điện, giờ RTC bị sai hoàn toàn | Hết pin CR2032 dự phòng trên module DS3231 | Thay pin CR2032 mới; kiểm tra rtc.lostPower() để phát hiện tình trạng này |
8. Tổng kết
Máy cho vật nuôi ăn tự động này giải quyết bài toán cho ăn đúng giờ mà không cần người trực, đặc biệt hữu ích khi đi vắng dài ngày. Việc dùng RTC DS3231 thay vì chỉ dựa vào NTP qua WiFi giúp hệ thống vẫn hoạt động chính xác dù mất kết nối mạng. Có thể mở rộng thêm bằng cách thêm cảm biến trọng lượng (load cell) để xác nhận thức ăn đã rơi đủ, hoặc gửi thông báo qua Telegram/MQTT mỗi khi cho ăn thành công để chủ nuôi theo dõi từ xa.