Berikut adalah komponen yang diperlukan untuk pemakan hewan pintar ini:
Arduino Nano R3
1
Modul jam waktu nyata DS 1307
1
Modul regulator tegangan LM-317
1
Servo rotasi berkelanjutan
1
Dispenser serbuk
1
Magnet 8mm
6
Sensor hall (unipolar)
1
LCD Adafruit RGB Backlight - 16x2
1
Backpack LCD I2C
1
LED 5 mm: Merah
1
LED 5 mm: Hijau
1
Tombol push momenter
5
Adapter jack daya DC female
1
Adaptor dinding 9-12 Volt
1
Resistor 10k ohm
1
Resistor 330 ohm
2
Alat dan Mesin yang Diperlukan
Printer 3D (umum)
Bor / Pengobrak, Nirkabel
Gergaji lingkaran
Tentang Proyek Ini
Di balik cerita pemakan hewan
Saya telah bermain dengan mikrokontroler untuk sementara waktu dan ingin mencoba keluar dari tutorial. Sebelum Covid-19, saya tidak really memiliki jadwal yang tetap dan bekerja jam yang cukup lama. Jadwal pakan anjing saya benar-benar mulai menderita dan menyebabkan ketidaknyamanan yang terlihat. Daerah saya juga rentan banjir selama musim bencana. Sayangnya, ada beberapa kasus di mana saya tidak dapat pulang untuk memberinya makan. Saya perlu menemukan solusi sehingga, jika saya tidak dapat pulang, anjing saya tidak akan lapar. Mengapa membeli satu untuk $30-$40, mengapa tidak membangun satu untuk $100+? Cuma lelucon.
Pemakan hewan!
Pemakan anjing saya menggunakan mikrokontroler Arduino. Ada beberapa fitur kunci yang saya butuhkan yang tidak dimiliki oleh sebagian besar versi pemakan anjing lainnya. Namanya, solusi untuk pemulihan pakan saat padam listrik dan menangani masalah makanan yang macet dalam mekanisme pemecah. Daerah saya juga menderita padam listrik secara acak. Padam listrik biasanya tidak bertahan lama, jadi saya tidak melihat keperluan untuk redundansi daya. Saya juga ingin sesuatu yang mudah dibongkar dan dibersihkan. Hati-hati saat mencuci bagian cetak 3D, Anda dapat menggunakan air hangan, BUKAN air panas kecuali Anda berencana mencetak dengan ABS atau PET-G.
Daftar lengkap fitur adalah sebagai berikut:
Dua pakan per hari
Pengawetan waktu akurat dengan jam waktu nyata
Pengubahan waktu manual jam waktu nyata
Opsi pakan manual
Indikator LED kegagalan sensor hall dan jam waktu nyata
Gambaran waktu pakan, waktu saat ini, dan penyelesaian pakan pada layar utama
Menu yang mudah dinavigasi
Pakan resume saat padam listrik (akan memberi makan saat daya kembali menyala)
Waktu pakan dan penyelesaian disimpan dengan aman di EEPROM
Servo "getaran" jika makanan macet saat mencair
Demo
Video demo akan segera hadir!
Apa lagi yang Anda butuhkan?
4x baut M3-0.5 x 16 mm (roda enkoder)
4x baut M3-0.5 x 10 mm (bracket servo)
12x dadu segi enam M3
4x dadu pengunci M3
16x baut kayu multipurpose 1-1/2 inci (38 mm)
Lem kayu
6x magnet 0.315 x 0.118 inci (8 x 3 mm)
Kayu 1-1/2 x 1-1/2 inci (3.8 x 3.8 cm) untuk kaki dan pemasang servo
Kayu 1/2 x 6 in (1.27 x 15.24 cm) untuk bagian belakang dan dasar
Bagian cetak 3D
Saya baru saja mendapatkan printer 3D dan berpikir, bagaimana cara yang lebih baik untuk belajar dan menggunakannya daripada mencetak bagian kustom untuk pemakan hewan saya. Semua yang dicetak adalah PLA+ dan dicetak menggunakan Ender 3 Pro dengan Cura sebagai pengiris. STL untuk semua bagian dapat ditemukan pada halaman proyek Github. Tautan di bawah ini. Baca file README untuk instruksi pencetakan dan pengaturan pengiris.
Perabot
Perabot dirancang dengan kenyamanan dan kesederhanaan dalam pikiran. Tidak perlu baut atau dadu. Cukup masukkan dan keluarkan komponen. Setiap pemasukkan memiliki 4 tab yang memegang komponen tetap di tempat.
Meski saya awalnya mengacaukan slot layar LCD, saya kembali dan memperbaiki model di Fusion 360. Saya hanya terlalu malas untuk kembali dan mencetak ulang. Saya menggunakan baut M3 0.5 x 6mm tambahan yang saya miliki untuk mengamankannya. Ada empat pemisah di setiap sudut slot LCD dengan lubang di dalamnya sehingga Anda dapat mengamankan layar jika diperlukan. Sayangnya, saya tidak pernah mengambil foto tutup kotak sebelum saya memasukkan semuanya di dalam.
Pemasang roda enkoder
Roda enkoder berfungsi untuk dua hal:
Memberikan Arduino umpan balik posisi dari servo
Memasang palet karet ke servo
Satu porsi pakan sama dengan 1/6 (60 derajat) putaran roda. Gunakan timbangan makanan untuk mengukur jumlah makanan yang diterima hewan peliharaan Anda per pakan dan sesuaikan nomor porsi sampai Anda mendapatkan rentang yang memuaskan jumlah itu. Saya yakin satu pakan untuk saya sekitar 173 gram makanan. Ukuran porsi 17 memberikan saya rentang 170-177 gram per pakan. Semua tergantung pada ukuran kibble Anda. Pastikan horn berada di antara 8 dadu segi enam M3.
Bracket servo dan pemasang sensor hall
Bracket servo kustom ini juga memegang sensor hall dan memasang servo pada kayu berukuran 1-1/2 x 1-1/2 inci (3.8 x 3.8 cm). Panjang kayu akan tergantung pada tempat servo berada (selengkapnya nanti). Ada banyak ruang untuk kesalahan dengan pemasang jadi jangan terlalu khawatir tentang mendapatkan panjang yang sempurna.
Segar, salur makanan, dan rel salur
Ini membentuk sistem pengiriman makanan. Makanan turun dari dispenser melalui segar dan ke salur dan ke dalam mangkuk makanan. Sayangnya, saya tidak mengambil foto rel salur sebelum memasangnya.
Bingkai
*Penafian* Saya tidak mengambil foto setiap tahap proses pemasangan. Beberapa foto ini akan melewati langkah, tetapi saya akan tetap membimbing Anda langkah demi langkah untuk pemasangan bingkai.
PASTIKAN MEMBOR LUBANG AWAL UNTUK SETIAP BAUT YANG ANDA GUNAKAN. Anda tidak ingin membelah kayu!
Ini adalah dispenser serbuk yang saya beli dari Amazon. Ada beberapa yang memiliki banyak dispenser jika Anda memiliki banyak hewan peliharaan dan membutuhkan banyak pemakan, saya hanya membutuhkan satu. Mereknya adalah Honey Can Do, tetapi saya yakin semua merek akan bekerja.
Hal pertama yang saya kerjakan adalah menghapus knop dari batang yang terhubung ke roda palet pada dispenser. Saya menggunakan gergaji tangan untuk menghapus knop. Anda dapat menggunakan alat bor jika Anda memilikinya. Jangan memotong batang lebih jauh di bawah bagian perak berakhir. Potong tepat di dasar knop.
Setelah Anda memotong knop, hapus sisa bahan dengan memotong 3 dukungan yang terlihat dalam foto terakhir di atas. Ini cukup lama. Saya kira akan lebih cepat jika Anda memiliki alat bor yang tepat. Setelah Anda menghapus tiga dukungan tersebut, sisa bagian yang tersisa pada batang itu sendiri harus dapat dilepaskan dengan sedikit usaha. Anda harus menggerus bagian batang terdekat dengan knop sehingga akan pas dengan baik ke dalam roda enkoder.
Selanjutnya, kita mulai membangun dasar bingkai. Saya mengambil kayu 1/2 x 6 inci (1.27 x 15.24 cm) dan memotongnya menjadi dua potongan 8 inci (20.32 cm). Ini akan membentuk dasar dan bagian belakang bingkai pemakan. Aplikasikan lem kayu dan gunakan 2 paku multipurpose untuk menggabungkannya dalam bentuk L. Anda perlu menambahkan beberapa dukungan sudut kanan untuk memperkuat koneksi panel belakang dan panel bawah. Gunakan 4 baut multipurpose dan beberapa lem kayu untuk mengamankannya pada panel belakang dan bawah. Saya tidak memiliki foto langkah ini, tetapi Anda dapat melihatnya dalam foto di bawahnya.
Dari sini Anda harus memotong tiang dispenser ke ketinggian sekitar 4.5 inci (11.43 cm). Tidak perlu sempurna. Coba dapatkan semakin dekat mungkin. Segar memberikan beberapa ruang gerak. Setelah Anda memotong tiang sesuai ukuran, letakkan terhadap bagian belakang dan pastikan berdiri datar pada dasar. Setelah Anda meletakkannya dalam posisi, gunakan pensil atau pena untuk menandai titik tengah di ujung pengisar wadah. Anda kemudian perlu membor lubang 2 inci (5.08 cm) melalui panel dasar seluruhnya. Penting untuk Anda mengukur dua kali dan memotong satu kali untuk hal ini. Itu akan menjadi lubang yang terlihat dalam foto di atas.
Setelah Anda membor lubang di panel dasar, kita siap memasang tiang dispenser ke panel belakang. Yang Anda inginkan adalah menempatkan tiang terhadap panel belakang (ditampilkan dalam foto pertama di bawah). Ada dua area cubby di bawah cincin tiang dispenser. Itu adalah tempat Anda ingin lubangnya (lihat foto di bawah). Gunakan pensil atau pena untuk menandai ketinggian di mana dua lubang harus berada di panel belakang. Anda ingin mereka semakin dekat ke tengah tiang dispenser sebanyak mungkin. Ada dua baut yang menghubungkan cincin atas tiang ke bagian bawah yang Anda potong. Hati-hati untuk tidak memukulnya saat membor. Lagi, ingat untuk mengukur dua kali dan bor satu kali. Atau dua kali, dalam kasus ini.
Sekarang setelah lubang dibor, kita dapat memasang tiang ke panel belakang menggunakan baut segi enam 5/16 inci, dadu segi enam 5/16 inci, dan pelapis 5/16 inci. Anda ingin mendorong baut melalui dengan memastikan Anda meletakkan pelapis di ujung segi enam baut sebelum mendorongnya melalui. Setelah keluar di sisi lain, tempatkan set pelapis lain di sisi berulir dan mulai mengencangkan dadu segi enam secara manual. Ini akan sedikit sulit. Setelah Anda mengencangkan dadu secara manual, Anda harus menggunakan soket untuk memegang dadu dan mengencangkannya lebih jauh. Hanya sekitar 1/4 putaran atau lebih. Hati-hati untuk tidak terlalu kencang.
Sekarang tiang telah dipasang dengan aman kita dapat menambahkan kayu 1/2 x 1/2 inci yang akan diletakkan servo. Panjang ini akan tergantung pada di mana servo Anda akan duduk. Lanjutkan dan susun mekanisme pakan dengan memasang servo ke horn pada roda enkoder dan roda enkoder ke palet karet di dalam wadah plastik. Letakkan wadah ke dalam cincin atas dan ukur di mana servo duduk dari panel dasar. Jangan khawatir tentang sempurna. Bracket servo memberikan cukup banyak ruang gerak. Gunakan lem kayu dan satu baut multipurpose untuk mengamankan tiang kayu.
Langkah berikutnya adalah memasang kaki ke panel dasar pemakan. Panjang kaki akan tergantung pada seberapa tinggi mangkuk makanan hewan peliharaan Anda. Anjing saya memiliki mangkuk makanan di atas tandang; oleh karena itu, saya perlu pemakan saya duduk cukup tinggi. Gunakan 4 baut multipurpose dan beberapa lem kayu untuk mengamankannya di tempat. Saya merekomendasikan memasang balok silang antara dua kaki depan dan dua kaki belakang dan balok silang lain antara balok silang seperti di bawah ini untuk stabilitas tambahan. Gunakan total 6 baut multipurpose dan beberapa lem kayu untuk mengamankan potongan ke kaki.
Langkah berikutnya adalah:
masukkan segar ke dalam lubang yang kami bor di panel dasar
pasang servo ke bracket servo
amankan bracket ke tiang kayu
Pasang servo ke bracket menggunakan 4 baut M3 x 10 mm dan 4 dadu segi enam M3. Setelah servo dipasang, kita dapat mengamankan bracket pada tiang kayu servo. Gunakan dua baut multipurpose untuk mengamankan ringan bracket pada tiang kayu. Jangan terlalu kencang atau Anda akan merusak bracket. Pastikan Anda melakukan langkah-langkah dalam urutan tersebut. Segar akan mengangkat servo sedikit dan pas cukup erat di ujung wadah plastik sehingga tidak bisa dipasang jika wadah plastik sudah duduk di tiang cincin atas.
Langkah akhir akan memasang bracket slide-in salur dan slide itu sendiri. Anda ingin memosisikan bracket sedikit di belakang lubang di panel dasar. Anda ingin semakin maju mungkin agar slide melewati bingkai pemakan. Gunakan dua baut multipurpose untuk mengamankan bracket ke dasar panel dasar bingkai. Terbaik untuk melakukan ini dengan slide dalam bracket karena bracket memiliki sedikit fleksibel dan Anda akan ingin pasangan yang cukup aman antara slide dan bracket.
Elektronika
Sayangnya, saya tidak pernah mengambil foto proses soldering. Tidak banyak yang harus dilakukan. Cukup solder setiap komponen ke pin yang sesuai dan Anda seharusnya siap. Jika Anda ingin menggunakan pin header, Anda juga dapat melakukannya dengan cara itu. Ada cukup ruang di bawah dan di atas slot Arduino untuk memungkinkan pin dan konektor. Saya sangat merekomendasikan untuk menyolder semuanya bersama sebelum meletakkan semua komponen di slot masing-masing.
Saya menyediakan daya pada Arduino melalui pin daya eksternal tidak teratur (pin 30). Ini memerlukan input tegangan antara 7-20 volt karena tegangan ini dijalankan melalui regulator papan Arduino. Jika Anda ingin menyediakan daya melalui USB, Anda hanya perlu memastikan bahwa Anda menyediakan 5 Volt, BUKAN 7-20 Volt.
Pastikan untuk menyolder resistor 10k Ohm antara pin Vcc dan Signal pada sensor hall. Jika tidak, Anda tidak akan mendapatkan pembacaan. Jangan lupa untuk menghubungkan semua komponen ke ground umum. Saya melakukan kesalahan dengan melewatkan salah satu ground dan sistem saya akan bekerja untuk sementara waktu, tetapi sensor hall akhirnya akan mulai gagal. Itu adalah hari yang sangat bagus untuk anjing saya.
Saya akhirnya membuat konektor kustom untuk koneksi sensor hall dan servo. Saya menyolder kabel ke pin header pria. Ini tergantung di bawah kotak. Untuk sensor hall, saya membuat adapter female kustom dengan memotong, mencabut, dan menyolder beberapa kabel konektor Dupont yang saya miliki di sekitar.
Untuk rel Vcc dan ground, saya memotong bagian rel daya dari beberapa perma-proto breadboard tambahan yang saya miliki di sekitar. Apa pun yang serupa yang Anda miliki akan bekerja. Cukup periksa kembali koneksi Anda sebelum meletakkan rel di slotnya. Ini adalah hal yang paling sulit untuk dikeluarkan setelah masuk. Saya juga mempelajarinya dengan cara yang sulit.
Itu saja! Semoga kalian memiliki kesenangan yang sama dalam membangun ini seperti saya. Ini tampak menantang di awal, tetapi semakin Anda terjun, semakin mudahnya. Beri tahu saya jika Anda memiliki pertanyaan tentang apapun. Senang bercoba!
Kode
Kode pemakan
// Versi akhir untuk pemakan hewan
/* Fitur:
- Menu yang mudah dinavigasi
- Gambaran waktu pakan, waktu saat ini, penyelesaian pakan, dan porsi pakan pada layar utama
- Porsi yang dapat dikontrol menggunakan sensor hall untuk umpan balik
- Pengawetan waktu akurat dengan chip DS1307
- Dapat mengubah waktu yang diatur secara manual di chip DS1307
- Dua pakan per hari
- Opsi pakan manual
- Restart pakan dalam kasus padam listrik
- Indikator LED sensor hall dan jam waktu nyata
- Waktu pakan dan penyelesaian disimpan dengan aman di EEPROM
- Servo "getaran" jika makanan macet saat mencair */
#include <JC_Button.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidMenu.h>
#include <RTClib.h>
#include <Servo.h>
#include <EEPROM.h>
// Membuat objek servo untuk mengontrol servo Anda
Servo myservo;
// Menetapkan semua pin input dan I/O saya
#define ENTER_BUTTON_PIN 11
#define UP_BUTTON_PIN 10
#define DOWN_BUTTON_PIN 9
#define BACK_BUTTON_PIN 8
#define POWER_LED_PIN 5
#define MANUAL_BUTTON_PIN A1
#define hallPin 2
#define HALL_LED_PIN 7
#define SERVO_PIN 6
// Mendefinisikan semua Tombol, bekerja dengan perpustakaan JC_Button
Button enterBtn (ENTER_BUTTON_PIN);
Button upBtn (UP_BUTTON_PIN);
Button downBtn (DOWN_BUTTON_PIN);
Button backBtn (BACK_BUTTON_PIN);
Button manualFeedBtn (MANUAL_BUTTON_PIN);
// Mendefinisikan LCD I2C dan RTC
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS1307 rtc;
// Variabel yang digunakan di seluruh kode
unsigned long hallSensorTime;
unsigned long rotationTime = 400;
unsigned long led_previousMillis = 0;
const long interval_delay = 1000;
unsigned long delay_interval = 2000;
int ledState = LOW;
boolean manualFeed = false;
boolean hall_sensor_fail = false;
unsigned long blink_previousMillis = 0;
unsigned long blink_currentMillis = 0;
unsigned long blink_interval = 500;
unsigned long delay_currentMillis = 0;
unsigned long delay_previousMillis = 0;
boolean blink_state = false;
int count;
boolean feeding1_complete = false;
boolean feeding2_complete = false;
boolean feeding1_trigger = false;
boolean feeding2_trigger = false;
boolean servoOn = true;
// Interupsi sensor hall
volatile boolean hallSensorActivated = false;
volatile int isr_count = 1;
void hallActiveISR() {
hallSensorActivated = true;
digitalWrite(HALL_LED_PIN, HIGH);
isr_count = isr_count + 1;
}
/* Saya menggunakan enum di sini untuk melacak tombol mana yang ditekan dengan lebih baik daripada hanya menetapkan setiap tombol ke nilai integer. */
enum { btnENTER, btnUP, btnDOWN, btnBACK, };
/* Status Mesin Status. Hal yang sama di sini dengan tipe enum. Ini membuatnya lebih mudah untuk melacak menu mana yang Anda ada atau ingin pergi daripada memberikan setiap menu nilai integer */
enum STATES { MAIN, MENU_EDIT_FEED1, MENU_EDIT_FEED2, MENU_EDIT_TIME, MENU_EDIT_PORTION, EDIT_FEED1_HOUR, EDIT_FEED1_MIN, EDIT_FEED2_HOUR, EDIT_FEED2_MIN, EDIT_HOUR, EDIT_MIN, EDIT_PORTION };
// Menyimpan status mesin status
STATES state;
// Variabel input pengguna
int Hour;
int Minute;
int portion;
int feed_time1_hour;
int feed_time1_min;
int feed_time2_hour;
int feed_time2_min;
int userInput;
// Pemeriksa karakter khusus centang byte
byte check_Char[8] = { B00000, B00000, B00001, B00011, B10110, B11100, B11000, B00000 };
//======================The Setup===========================
void setup() {
Wire.begin();
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.createChar(0, check_Char);
if (!rtc.begin()) {
Serial.println("Tidak dapat menemukan RTC!");
}
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Tombol
enterBtn.begin();
upBtn.begin();
downBtn.begin();
backBtn.begin();
manualFeedBtn.begin();
// Mengatur status awal Mesin Status
state = MAIN;
// Mengatur input dan output
pinMode(POWER_LED_PIN, OUTPUT);
pinMode(HALL_LED_PIN, OUTPUT);
pinMode(hallPin, INPUT);
/* Melampirkan interupsi ke pin yang terhubung ke sensor hall. Sensor hall yang saya gunakan diatur ke HIGH dan berubah menjadi LOW saat menemukan magnet. Itulah sebabnya diatur ke FALLING */
attachInterrupt(digitalPinToInterrupt(hallPin), hallActiveISR, FALLING);
// status default LED
digitalWrite(POWER_LED_PIN, HIGH);
digitalWrite(HALL_LED_PIN, LOW);
/* Fungsi ini mengambil waktu pakan yang disimpan, pakan selesai, dan ukuran porsi dari EEPROM saat start-up. Saya melakukannya karena saya mendapatkan padam listrik secara acak di tempat saya tinggal. */
get_feed_time1();
get_feed_time2();
get_completed_feedings();
get_portion();
}
//======================The Loop===========================
void loop() {
changing_states();
check_buttons();
check_feedtime();
check_rtc();
manual_feed_check();
}
//=============== The Functions =======================
/* Menggunakan perpustakaan JC_Button untuk terus memeriksa apakah tombol ditekan. Bergantung pada tombol mana yang ditekan, ini menetapkan variabel userInput untuk digunakan dalam fungsi menu_transitions */
void check_buttons() {
enterBtn.read();
upBtn.read();
downBtn.read();
backBtn.read();
manualFeedBtn.read();
if (enterBtn.wasPressed()) {
Serial.println("Anda menekan Enter!");
userInput = btnENTER;
menu_transitions(userInput);
}
if (upBtn.wasPressed()) {
Serial.println("Anda menekan Up!");
userInput = btnUP;
menu_transitions(userInput);
}
if (downBtn.wasPressed()) {
Serial.println("Anda menekan Down!");
userInput = btnDOWN;
menu_transitions(userInput);
}
if (backBtn.wasPressed()) {
Serial.println("Anda menekan Back!");
userInput = btnBACK;
menu_transitions(userInput);
}
if (manualFeedBtn.wasPressed()) {
Serial.println("Anda sedang memberi makan secara manual!");
manualFeed = true;
}
}
//=====================================================
/* Fungsi ini menentukan apa yang ditampilkan, bergantung pada menu atau "status" mana yang Anda ada. Setiap menu memiliki fungsi yang menampilkan menu tersebut */
void changing_states() {
switch (state) {
case MAIN:
display_current_time();
display_feeding_times();
display_portion();
break;
case MENU_EDIT_FEED1:
display_set_feed_time1_menu();
break;
case MENU_EDIT_FEED2:
display_set_feed_time2_menu();
break;
case MENU_EDIT_TIME:
display_set_time_menu();
break;
case MENU_EDIT_PORTION:
display_set_portion_menu();
break;
case EDIT_FEED1_HOUR:
set_feed_time1();
break;
case EDIT_FEED1_MIN:
set_feed_time1();
break;
case EDIT_FEED2_HOUR:
set_feed_time2();
break;
case EDIT_FEED2_MIN:
set_feed_time2();
break;
case EDIT_HOUR:
set_the_time();
break;
case EDIT_MIN:
set_the_time();
break;
case EDIT_PORTION:
set_the_portion();
break;
}
}
//=====================================================
/* Ini adalah bagian transisi dari mesin status. Ini yang memungkinkan Anda pergi dari satu menu ke menu lain dan mengubah nilai */
void menu_transitions(int input) {
switch (state) {
case MAIN:
if (input == btnENTER) {
lcd.clear();
state = MENU_EDIT_FEED1;
}
if (input == btnBACK) {
hall_sensor_fail = false;
}
break;
case MENU_EDIT_FEED1:
if (input == btnBACK) {
lcd.clear();
state = MAIN;
} else if (input == btnENTER) {
lcd.clear();
state = EDIT_FEED1_HOUR;
} else if (input == btnDOWN) {
lcd.clear();
state = MENU_EDIT_FEED2;
}
break;
case EDIT_FEED1_HOUR:
servoOn = false;
if (input == btnUP) {
feed_time1_hour++;
if (feed_time1_hour > 23) feed_time1_hour = 0;
} else if (input == btnDOWN) {
feed_time1_hour--;
if (feed_time1_hour < 0) feed_time1_hour = 23;
} else if (input == btnBACK) {
lcd.clear();
servoOn = true;
state = MENU_EDIT_FEED1;
} else if (input == btnENTER) {
state = EDIT_FEED1_MIN;
}
break;
case EDIT_FEED1_MIN:
if (input == btnUP) {
feed_time1_min++;
if (feed_time1_min > 59) feed_time1_min = 0;
} else if (input == btnDOWN) {
feed_time1_min--;
if (feed_time1_min < 0) feed_time1_min = 59;
} else if (input == btnBACK) {
state = EDIT_FEED1_HOUR;
} else if (input == btnENTER) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("*Settings Saved*");
delay(1000);
lcd.clear();
servoOn = true;
write_feeding_time1();
state = MAIN;
}
break;
case MENU_EDIT_FEED2:
if (input == btnUP) {
lcd.clear();
state = MENU_EDIT_FEED1;
} else if (input == btnENTER) {
lcd.clear();
state = EDIT_FEED2_HOUR;
} else if (input == btnDOWN) {
lcd.clear();
state = MENU_EDIT_TIME;
}
break;
case EDIT_FEED2_HOUR:
servoOn = false;
if (input == btnUP) {
feed_time2_hour++;
if (feed_time2_hour > 23) feed_time2_hour = 0;
} else if (input == btnDOWN) {
feed_time2_hour--;
if (feed_time2_hour < 0) feed_time2_hour = 23;
} else if (input == btnBACK) {
lcd.clear();
servoOn = true;
state = MENU_EDIT_FEED2;
} else if (input == btnENTER) {
state = EDIT_FEED2_MIN;
}
break;
case EDIT_FEED2_MIN:
if (input == btnUP) {
feed_time2_min++;
if (feed_time2_min > 59) feed_time2_min = 0;
} else if (input == btnDOWN) {
feed_time2_min--;
if (feed_time2_min < 0) feed_time2_min = 59;
} else if (input == btnBACK) {
state = EDIT_FEED2_HOUR;
} else if (input == btnENTER) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("*Settings Saved*");
delay(1000);
lcd.clear();
servoOn = true;
write_feeding_time2();
state = MAIN;
}
break;
case MENU_EDIT_TIME:
if (input == btnUP) {
lcd.clear();
state = MENU_EDIT_FEED2;
} else if (input == btnENTER) {
lcd.clear();
state = EDIT_HOUR;
} else if (input == btnDOWN) {
lcd.clear();
state = MENU_EDIT_PORTION;
}
break;
case EDIT_HOUR:
if (input == btnUP) {
Hour++;
if (Hour > 23) Hour = 0;
} else if (input == btnDOWN) {
Hour--;
if (Hour < 0) Hour = 23;
} else if (input == btnBACK) {
lcd.clear();
state = MENU_EDIT_TIME;
} else if (input == btnENTER) {
state = EDIT_MIN;
}
break;
case EDIT_MIN:
if (input == btnUP) {
Minute++;
if (Minute > 59) Minute = 0;
} else if (input == btnDOWN) {
Minute--;
if (Minute < 0) Minute = 59;
} else if (input == btnBACK) {
state = EDIT_HOUR;
} else if (input == btnENTER) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("*Settings Saved*");
delay(1000);
lcd.clear();
rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0));
state = MAIN;
}
break;
case MENU_EDIT_PORTION:
if (input == btnUP) {
lcd.clear();
state = MENU_EDIT_TIME;
} else if (input == btnENTER) {
lcd.clear();
state = EDIT_PORTION;
}
break;
case EDIT_PORTION:
if (input == btnUP) {
portion++;
if (portion > 20) portion = 1;
} else if (input == btnDOWN) {
portion--;
if (portion < 1) portion = 20;
} else if (input == btnBACK) {
lcd.clear();
state = MENU_EDIT_PORTION;
} else if (input == btnENTER) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("*Settings Saved*");
delay(1000);
lcd.clear();
write_portion();
state = MAIN;
}
break;
}
}
//=====================================================
// Fungsi ini memeriksa waktu pakan terhadap waktu saat ini
void check_feedtime() {
DateTime now = rtc.now();
if (now.second() == 0) {
if ((now.hour() == feed_time1_hour) && (now.minute() == feed_time1_min)) {
feeding1_trigger = true;
if (servoOn) {
if (feeding1_complete == false) {
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("Dispensing");
lcd.setCursor(1, 1);
lcd.print("First Feeding");
startFeeding();
}
}
} else if ((now.hour() == feed_time2_hour) && (now.minute() == feed_time2_min)) {
feeding2_trigger = true;
if (servoOn) {
if (feeding2_complete == false) {
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("Dispensing");
lcd.setCursor(0, 1);
lcd.print("Second Feeding");
startFeeding();
}
}
}
}
// Reset Tengah Malam
if ((now.hour() == 0) && (now.minute() == 0)) {
feeding1_complete = false;
feeding2_complete = false;
EEPROM.write(4, feeding1_complete);
EEPROM.write(5, feeding2_complete);
}
/* Jika padam listrik terjadi selama waktu pakan, ini memeriksa apakah waktu pakan telah lewat dan apakah pakan telah terjadi. Jika tidak, memberi makan. */
if ((now.hour() >= feed_time1_hour) && (now.minute() > feed_time1_min)) {
if ((feeding1_complete == 0) && (feeding1_trigger == 0)) {
lcd.clear();
lcd.setCursor(5, 0);
lcd.print("Uh-Oh!");
lcd.setCursor(2, 1);
lcd.print("Power Outage");
startFeeding();
}
}
if ((now.hour() >= feed_time2_hour) && (now.minute() > feed_time2_min)) {
if ((feeding2_complete == 0) && (feeding2_trigger == 0)) {
lcd.clear();
lcd.setCursor(5, 0);
lcd.print("Uh-Oh!");
lcd.setCursor(2, 1);
lcd.print("Power Outage");
startFeeding();
}
}
}
//=====================================================
// Menampilkan opsi menu porsi yang diatur
void display_set_portion_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Options");
lcd.setCursor(0, 1);
lcd.print("Set the Portion");
}
//=====================================================
// Menampilkan menu di mana Anda mengubah waktu saat ini
void set_the_time() {
lcd.setCursor(2, 0);
lcd.print("Set the Time");
switch (state) {
case EDIT_HOUR:
if (blink_state == 0) {
lcd.setCursor(5, 1);
add_leading_zero(Hour);
} else {
lcd.setCursor(5, 1);
lcd.print(" ");
}
lcd.print(":");
add_leading_zero(Minute);
break;
case EDIT_MIN:
lcd.setCursor(5, 1);
add_leading_zero(Hour);
lcd.print(":");
if (blink_state == 0) {
lcd.setCursor(8, 1);
add_leading_zero(Minute);
} else {
lcd.setCursor(8, 1);
lcd.print(" ");
}
break;
}
blinkFunction();
}
//=====================================================
// Menampilkan menu di mana Anda mengubah porsi pakan
void set_the_portion() {
lcd.setCursor(0, 0);
lcd.print("Set the Portion");
switch (state) {
case EDIT_PORTION:
if (blink_state == 0) {
lcd.setCursor(7, 1);
add_leading_zero(portion);
} else {
lcd.setCursor(7, 1);
lcd.print(" ");
}
}
blinkFunction();
}
//=====================================================
// Menampilkan opsi menu untuk pengaturan waktu
void display_set_time_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Options");
lcd.setCursor(2, 1);
lcd.print("Set the Time");
}
//=====================================================
// Menampilkan menu di mana Anda mengubah waktu pakan kedua
void set_feed_time2() {
lcd.setCursor(0, 0);
lcd.print("Set Feed Time 2");
switch (state) {
case EDIT_FEED2_HOUR:
if (blink_state == 0) {
lcd.setCursor(5, 1);
add_leading_zero(feed_time2_hour);
} else {
lcd.setCursor(5, 1);
lcd.print(" ");
}
lcd.print(":");
add_leading_zero(feed_time2_min);
break;
case EDIT_FEED2_MIN:
lcd.setCursor(5, 1);
add_leading_zero(feed_time2_hour);
lcd.print(":");
if (blink_state == 0) {
lcd.setCursor(8, 1);
add_leading_zero(feed_time2_min);
} else {
lcd.setCursor(8, 1);
lcd.print(" ");
}
break;
}
blinkFunction();
}
//=====================================================
// Menampilkan menu di mana Anda mengubah waktu pakan pertama
void set_feed_time1() {
lcd.setCursor(0, 0);
lcd.print("Set Feed Time 1");
switch (state) {
case EDIT_FEED1_HOUR:
if (blink_state == 0) {
lcd.setCursor(5, 1);
add_leading_zero(feed_time1_hour);
} else {
lcd.setCursor(5, 1);
lcd.print(" ");
}
lcd.print(":");
add_leading_zero(feed_time1_min);
break;
case EDIT_FEED1_MIN:
lcd.setCursor(5, 1);
add_leading_zero(feed_time1_hour);
lcd.print(":");
if (blink_state == 0) {
lcd.setCursor(8, 1);
add_leading_zero(feed_time1_min);
} else {
lcd.setCursor(8, 1);
lcd.print(" ");
}
break;
}
blinkFunction();
}
//=====================================================
// Menambahkan nol di depan untuk angka satu digit
void add_leading_zero(int num) {
if (num < 10) {
lcd.print("0");
}
lcd.print(num);
}
//=====================================================
/* Menampilkan waktu pakan pada menu utama serta tanda centang untuk konfirmasi visual pakan yang selesai */
void display_feeding_times() {
lcd.setCursor(0, 0);
lcd.print("F1:");
add_leading_zero(feed_time1_hour);
lcd.print(":");
add_leading_zero(feed_time1_min);
lcd.print(" ");
if (feeding1_complete == true) {
lcd.write(0);
} else {
lcd.print(" ");
}
lcd.setCursor(0, 1);
lcd.print("F2:");
add_leading_zero(feed_time2_hour);
lcd.print(":");
add_leading_zero(feed_time2_min);
lcd.print(" ");
if (feeding2_complete == true) {
lcd.write(0);
} else {
lcd.print(" ");
}
}
//=====================================================
// Menampilkan waktu saat ini di menu utama
void display_current_time() {
DateTime now = rtc.now();
lcd.setCursor(11, 0);
add_leading_zero(now.hour());
lcd.print(":");
add_leading_zero(now.minute());
}
//=====================================================
// Menampilkan opsi menu untuk pengaturan waktu pakan pertama
void display_set_feed_time1_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Options");
lcd.setCursor(0, 1);
lcd.print("Set Feed Time 1");
}
//=====================================================
// Menampilkan opsi menu untuk pengaturan waktu pakan kedua
void display_set_feed_time2_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Options");
lcd.setCursor(0, 1);
lcd.print("Set Feed Time 2");
}
//=====================================================
// Menampilkan porsi pakan di menu utama
void display_portion() {
lcd.setCursor(12, 1);
lcd.print("P:");
add_leading_zero(portion);
}
//=====================================================
// Memulai proses pakan. void startFeeding() {
void startFeeding() {
myservo.attach(SERVO_PIN);
count = 1;
hallSensorTime = millis();
// loop sehingga servo berjalan sampai porsi yang diinginkan tercapai
while (count <= portion) {
servoStart();
if (hallSensorActivated == true) {
count = count + 1;
hallSensorTime = millis();
hallSensorActivated = false;
digitalWrite(HALL_LED_PIN, LOW);
}
/* Gerakkan servo searah jarum jam sedikit untuk melepaskan makanan yang macet dalam mekanisme pengisar */
if (millis() - hallSensorTime > rotationTime) {
hall_sensor_fail = true;
Serial.println("I'm in Jiggle");
jiggle();
}
}
// Melacak pakan mana yang baru saja terjadi dan menuliskannya ke EEPROM
if ((feeding1_complete == false) && (feeding2_complete == false)) {
feeding1_complete = true;
EEPROM.write(4, feeding1_complete);
} else if ((feeding1_complete == true) && (feeding2_complete == false)) {
feeding2_complete = true;
EEPROM.write(5, feeding2_complete);
}
servoStop();
digitalWrite(HALL_LED_PIN, LOW);
myservo.detach();
lcd.clear();
delay_currentMillis = millis();
while (millis() - delay_currentMillis <= delay_interval) {
lcd.setCursor(2, 0);
lcd.print("Feeding Done");
}
lcd.clear();
}
//=====================================================
void servoStart() {
myservo.write(180);
}
//=====================================================
void servoStop() {
myservo.write(94);
}
//=====================================================
// "getar" servo jika makanan macet
void jiggle() {
myservo.write(80);
delay(30);
myservo.write(93);
delay(30);
myservo.write(180);
}
//=====================================================
// Menulis nilai jam dan menit yang diatur untuk pakan pertama ke EEPROM
void write_feeding_time1() {
EEPROM.write(0, feed_time1_hour);
EEPROM.write(1, feed_time1_min);
}
//=====================================================
// Menulis nilai jam dan menit yang diatur untuk pakan kedua ke EEPROM
void write_feeding_time2() {
EEPROM.write(2, feed_time2_hour);
EEPROM.write(3, feed_time2_min);
}
//=====================================================
// Menulis nilai porsi yang diatur ke EEPROM
void write_portion() {
EEPROM.write(6, portion);
}
//=====================================================
// Membaca nilai jam dan menit dari waktu pakan pertama dari EEPROM
void get_feed_time1() {
feed_time1_hour = EEPROM.read(0);
if (feed_time1_hour > 23) feed_time1_hour = 0;
feed_time1_min = EEPROM.read(1);
if (feed_time1_min > 59) feed_time1_min = 0;
}
//=====================================================
// Membaca nilai jam dan menit dari waktu pakan kedua dari EEPROM
void get_feed_time2() {
feed_time2_hour = EEPROM.read(2);
if (feed_time2_hour > 23) feed_time2_hour = 0;
feed_time2_min = EEPROM.read(3);
if (feed_time2_min > 59) feed_time2_min = 0;
}
//=====================================================
// Membaca nilai porsi yang diatur dari EEPROM
void get_portion() {
portion = EEPROM.read(6);
}
//=====================================================
// Membaca nilai boolean apakah pakan telah terjadi atau tidak dari EEPROM
void get_completed_feedings() {
feeding1_complete = EEPROM.read(4);
feeding2_complete = EEPROM.read(5);
}
//=====================================================
/* Memeriksa apakah sensor hall gagal membaca magnet dan membedakan LED merah */
void check_hall_sensor() {
if (hall_sensor_fail == true) {
if (blink_state == 0) {
digitalWrite(HALL_LED_PIN, HIGH);
} else {
digitalWrite(HALL_LED_PIN, LOW);
}
blinkFunction();
} else {
digitalWrite(HALL_LED_PIN, LOW);
hall_sensor_fail = false;
}
}
//=====================================================
// Memeriksa apakah Anda menekan tombol pakan manual
void manual_feed_check() {
if (manualFeed == true) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Manual Feeding");
startFeeding();
manualFeed = false;
}
}
//=====================================================
// memeriksa apakah RTC berjalan
void check_rtc() {
if (!rtc.isrunning()) {
led_blink();
}
}
//=====================================================
/* Membedakan LED merah saat RTC gagal. Catatan: LED akan berkedip dengan kecepatan yang berbeda dari saat sensor hall gagal */
void led_blink() {
unsigned long led_currentMillis = millis();
if (led_currentMillis - led_previousMillis >= interval_delay) {
led_previousMillis = led_currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(HALL_LED_PIN, ledState);
}
}
//=====================================================
// Membuat efek berkedip saat mengubah nilai
void blinkFunction() {
blink_currentMillis = millis();
if (blink_currentMillis - blink_previousMillis > blink_interval) {
blink_previousMillis = blink_currentMillis;
blink_state = !blink_state;
}
}
Ini adalah semua file Fusion 360 jika Anda ingin menyesuaikannya untuk ukuran komponen yang berbeda. Saya juga telah menyediakan file STL. Satu-satunya model yang tidak ada di sana adalah tabung untuk sensor hall. Itu seharusnya cukup mudah untuk dimodelkan dan dicetak.https://github.com/russo08/Pet-feeder.git
Skematik
Ini adalah skematik rangkaian. Anda dapat mengubahnya jika perlu. Jika Anda melakukannya, ingatlah untuk melakukan penyesuaian yang sama dalam kode.