Slimme 3D-geprint Dierenvoeder Aangedreven door Arduino Nano
Onderdelen en Benodigdheden
| Arduino Nano R3 | × | 1 |
| DS1307 Real Time Clock module | × | 1 |
| LM317 Spanningsregelaar module | × | 1 |
| Continu-rotatie Servo | × | 1 |
| GraanDispenser | × | 1 |
| 8mm magneten | × | 6 |
| Hall-sensor (unipolaar) | × | 1 |
| Adafruit RGB Backlight LCD - 16x2 | × | 1 |
| I2C LCD Backpack | × | 1 |
| 5mm LED: Rood | × | 1 |
| 5mm LED: Groen | × | 1 |
| Drukknop, Momentary | × | 5 |
| Female DC voedingsjack adapter | × | 1 |
| 9-12V voeding (wandstekker) | × | 1 |
| Weerstand 10k ohm (voor Hall-sensor, tussen Vcc en Signaal) | × | 1 |
| Weerstand 330 ohm (voor LEDs) | × | 2 |
Benodigde Tools en Machines
- 3D-printer (generiek)
- Draai/boormachine, accu
- Cirkelzaag
Over dit Project
Het Verhaal Achter de Dierenvoeder
Ik speel al een tijdje met microcontrollers en wilde uitbreiden vanaf tutorials. Voor Covid-19 had ik geen vast schema en werkte ik lange uren. Mijn hond zijn voedingsschema liep erg in de soep en leidde tot zichtbaar ongemak. Mijn gebied is ook gevoelig voor overstromingen tijdens orkanseizoen. Er waren gevallen dat ik niet thuis kon komen om mijn oude hond te voeren. Ik had een oplossing nodig zodat, als ik niet thuis kon komen, mijn hond niet hongergevoeld hoefde te raken. In plaats van er een te kopen voor $30-$40, waarom niet er een bouwen voor $100+? Grapje natuurlijk.
De Voeder!
Mijn hondvoeder gebruikt een Arduino-microcontroller. Er waren een paar sleutelfuncties die ik nodig had die de meeste andere versies van hondvoerders niet hadden. Namelijk een oplossing voor herstel na stroomuitval en het adreseren van het probleem dat voer vastloopt in het dosermechanisme. Mijn gebied heeft ook willekeurige stroomuitvallen. De uitvallen duren meestal niet lang, dus ik zag geen noodzaak voor stroomredundantie. Ik wilde ook iets wat makkelijk uit elkaar te halen en te reinigen is. Wees voorzichtig bij het wassen van de 3D-geprinte onderdelen: je kunt warm water gebruiken, GEEN heet water tenzij je met ABS of PET-G print.
De volledige lijst met functies:
- Twee voederingen per dag
- Accurate tijdmeting met real-time clock
- Handmatige tijdwijziging van real-time clock
- Handmatige voederoptie
- LED-indicatie van Hall-sensor en real-time clock fout
- Overzicht van voedtijden, huidige tijd en voedervoltooiingen op het hoofsscherm
- Gemakkelijk te navigeren menu
- Hervatten voeding na stroomuitval (voedt wanneer de stroom terugkomt)
- Voedtijden en voltooiingen veilig opgeslagen in EEPROM
- Servo "schudden" voor het geval voer vastloopt tijdens doseren
Demo
Demovideo komt binnenkort!
Wat Heb Je Nog Nodig?
- 4x M3-0.5 x 16mm schroeven (encoderwiel)
- 4x M3-0.5 x 10mm schroeven (servobeugel)
- 12x M3 zeskantmoeren
- 4x M3 borgmoeren
- 16x 1-1/2 inch (38mm) universal houtschroeven
- Houtlijm
- 6x 0.315 x 0.118 inch (8 x 3mm) magneten
- 1-1/2 x 1-1/2 inch (3.8 x 3.8cm) hout voor poten en servobeugel
- 1/2 x 6 inch (1.27 x 15.24cm) hout voor achterkant en basis
De 3D-geprinte Onderdelen
Ik heb onlangs een 3D-printer gekregen en dacht: wat voor een betere manier om te leren en het te gebruiken dan custom onderdelen printen voor mijn dierenvoeder. Alles is geprint in PLA+ op een Ender 3 Pro met Cura als slicer. De STL-bestanden voor alle onderdelen zijn te vinden op de Github-projectpagina. Link hieronder. Lees het README-bestand voor printinstructies en slicer-instellingen.

De Behuizing
De behuizing is ontworpen met gemak en eenvoud in gedachten. Geen schroeven of moeren nodig. Gewoon de componenten in en uit klikken. Elke insert heeft 4 tabs die de componenten op hun plek houden.




Hoewel ik de LCD-scherm sleuf initieel mislukte, heb ik het model in Fusion 360 gecorrigeerd. Ik was lui om het opnieuw te printen, dus ik gebruikte enkele extra M3 0.5 x 6mm schroeven die ik had liggen om het te bevestigen. Er zijn vier afstandhouders in elke hoek van de LCD-sleuf met gaten erin zodat je het scherm kunt bevestigen als nodig. Helaas heb ik nooit foto's gemaakt van de behuizingdeksel voordat ik alles erin had gezet.
De Encoderwiel Bevestiging
Het encoderwiel dient twee doeleinden:
- Geeft de Arduino positie-terugkoppeling van de servo
- Bevestigt de rubberen scheplepel aan de servo
Eén voederportion is gelijk aan 1/6e (60 graden) draai van het wiel. Gebruik een keukenweegschaal om het hoeveelheid voer dat je huisdier per voeding ontvangt te meten en pas dan het portionummer aan tot je een bereik hebt dat die hoeveelheid bevredigt. Ik geloof dat één voeding voor mij ongeveer 173 gram voer was. Een portiegrootte van 17 gaf me een bereik van 170-177 gram per voeding. Het hangt allemaal af van de grootte van je kroketjes. Zorg dat de hoorn tussen de 8 M3 zeskantmoeren zit.



De Servobeugel en Hall-sensor Beugel
Deze aangepaste servobeugel houdt ook de Hall-sensor en monteur de servo op een stuk 1-1/2 x 1-1/2 inch (3.8 x 3.8cm) hout. De lengte van het hout hangt af van waar de servo zit (meer over dat later). Er is voldoende ruimte voor fouten met de beugel, dus maak je geen zorgen over de perfecte lengte.





De Trechter, Voerchu en Chu Rail
Dit maakt het voerleveringssysteem uit. Het voer komt van de dispenser door de trechter op de chu en in de voerbak. Helaas heb ik geen foto's gemaakt van de chu-rail voordat ik hem monteurde.




Het Frame
*Disclaimer* Ik heb geen foto's gemaakt van elke stap van het montageproces. Sommige foto's zullen overgeslagen stappen hebben, maar ik zal je toch stap-voor-stap meenemen door de frame-montage.
ZORG DAT JE VOOR ELKE SCHROEF VOORBORST. JE WILT HET HOUT NIET SCHEUREN!
Dit is de graandispenser die ik van Amazon kocht. Er zijn er met meerdere dispensers als je meerdere huisdieren hebt en meerdere voeders nodig hebt, ik had er maar één nodig. Het merk is Honey Can Do, maar ik ben ervan overtuigd dat elk merk werkt.

Het eerste wat ik deed was de knop van de staaf verwijderen die verbonden was met het schepwiel op de dispenser. Ik gebruikte een handzaag om de knop te verwijderen. Je kunt een elektrische tool gebruiken als je er een hebt. Snijd niet verder naar beneden waar het zilvergedeelte eindigt. Snijd precies aan de basis van de knop.



Na het afzagen van de knop, verwijder het overgebleven materiaal door de 3 steunen in de laatste foto hierboven te doorzagen. Dit nam erg lang. Ik denk dat het sneller gaat als je de juiste elektrische tool hebt. Eenmaal de drie steunen verwijderd, zou het overgebleven stuk op de staaf met een beetje grease eraf moeten komen. Je moet het deel van de staaf dichtst bij de knop afschuren zodat het netjes in het encoderwiel past.



Vervolgens beginnen we met het bouwen van de frame-basis. Ik nam het 1/2 x 6 inch (1.27 x 15.24cm) hout en zaagde ze in twee stukken van 8 inch (20.32cm). Dit maakt de basis en de achterkant van het frame van de voeder. Breng houtlijm aan en gebruik 2 van de universal nagels om ze in een L-vorm te verbinden. Je moet een paar rechterhoekige steunen toevoegen om de verbinding tussen het achterpaneel en de bodem te versterken. Gebruik 4 universal schroeven en wat houtlijm om ze aan zowel het achter- als bodempaneel te bevestigen. Ik heb geen foto van deze stap, maar je kunt ze in latere foto's zien.

Vanaf hier moet je de dispenserstaaf op een hoogte van ongeveer 4.5 inch (11.43cm) zagen. Het hoeft niet perfect te zijn. Probeer zo dicht mogelijk te komen. De trechter laat wat speelruimte toe. Zodra je de staaf op maat hebt gezaagd, plaats hem tegen de achterkant en zorg dat hij plat op de basis zit. Zodra je hem in positie hebt, gebruik een potlood of pen om het midden te markeren waar het uiteinde van de dispensercontainer zit. Je moet dan een 2 inch (5.08cm) gat door de basispaneel boren. Het is cruciaal dat je twee keer meet en één keer zaagt. Dat zal het gat zijn dat in de foto hierboven te zien is.
Eenmaal het gat in het basispaneel geboord, zijn we klaar om de dispenserstaaf aan het achterpaneel te bevestigen. Wat je wilt doen is de staaf tegen het achterpaneel zetten (getoond in de eerste foto hieronder). Er zijn twee inhammen onder de ring van de dispenserstaaf. Dit is waar je de gaten wilt hebben (zie foto hieronder). Gebruik een potlood of pen om de hoogte te markeren waar de twee gaten in het achterpaneel moeten komen. Je wilt ze zo dicht mogelijk bij het midden van de dispenserstaaf. Er zitten twee schroeven die de bovenste ring van de staaf verbinden met het onderste deel dat je afzaagde. Wees voorzichtig dat je die niet raakt bij het boren. Opnieuw, onthoud: twee keer meten, één keer boren. Of tweemaal, in dit geval.



Nu de gaten geboord zijn, kunnen we de staaf aan het achterpaneel bevestigen met de 5/16 inch zeskantbouten, 5/16 inch zeskantmoeren en 5/16 inch ringetjes. Je wilt de bouten doorduwen met ringetjes aan de zeskantkant voordat je ze doorduwt. Als ze aan de andere kant uitkomen, zet de andere set ringetjes op de draadkant en begin de moeren met de hand vast te draaien. Dit gaat een beetje lastig zijn. Na handvast draaien moet je een bussleutel gebruiken om de moer vast te houden en ze verder vast te draaien. Slechts ongeveer 1/4 draai. Wees voorzichtig om niet te strak te draaien.


Nu de staaf veilig bevestigd is, kunnen we het 1/2 x 1/2 inch houtstuk toevoegen waarop de servo gaat zitten. De lengte hangt af van waar jouw servo zal zitten. Ga het voedermechanisme monteren door de servo aan de hoorn van het encoderwiel te bevestigen en het encoderwiel aan de rubberen scheplepel in de plastic container. Zet de container in de bovenste ring en meet waar de servo zit vanaf het basispaneel. Maak je geen zorgen over perfectie. De servobeugel laat voldoende speelruimte toe. Gebruik houtlijm en een enkele universal schroef om het houtstuk vast te zetten.



De volgende stap is de poten aan het basispaneel van de voeder bevestigen. De lengte van de poten hangt af van hoe hoog de voerbak van je huisdier is. Mijn hond heeft haar voerbak op een verhoogde stand; daarom moest mijn voeder vrij hoog zitten. Gebruik 4 universal schroeven en wat houtlijm om ze vast te zetten. Ik raad aan een dwarsbalk tussen de twee voorpoten en twee achterpoten te zetten en nog een dwarsbalk tussen die dwarsbalken voor extra stabiliteit. Gebruik in totaal 6 universal schroeven en wat houtlijm om de stukken aan de poten te bevestigen.

De volgende stappen zijn:
- Plaats de trechter in het gat dat we in het basispaneel boorden
- Bevestig de servo aan de servobeugel
- Bevestig de beugel aan het houten servostaand
Bevestig de servo aan de beugel met de 4 M3 x 10mm schroeven en 4 M3 zeskantmoeren. Eenmaal de servo bevestigd, kunnen we de beugel op het houten servostaand bevestigen. Gebruik twee universal schroeven om de beugel lichtjes op het houten staand te bevestigen. Draai niet te strak of je beschadigt de beugel. Zorg dat je de stappen in die volgorde doet. De trechter gaat de servo een beetje omhoog duwen en past best strak tegen het einde van de plastic container, dus het is onmogelijk om het erop te zetten als de plastic container al in de bovenste ring staat.




De laatste stappen zijn de chu's inschuifbeugel en de glijbaan zelf bevestigen. Je wilt de beugel iets achter het gat in het basispaneel positioneren. Je wilt het zo ver mogelijk naar voren zodat de glijbaan het frame van de voeder vrijmaakt. Gebruik twee universal schroeven om de beugel aan de onderkant van het frame's basispaneel te bevestigen. Het is het beste om dit te doen met de glijbaan in de beugel, omdat de beugel wat buigzaam is en je een redelijk stevige pasvorm wilt tussen de glijbaan en de beugel.


De Elektronica
Helaas heb ik nooit foto's gemaakt van het solderproces. Er zit niet veel in. Gewoon elke component aan de bijbehorende pinnen solderen en je zou goed moeten zijn. Als je liever pin headers wilt gebruiken, kan dat ook. Er is voldoende ruimte onder en boven de Arduino-sleuf voor de pinnen en connectoren. Ik raad zeker aan om alles samen te solderen voordat je alle componenten in hun respectieve sleuven zet.
Ik voedde mijn Arduino via de ongeregelde externe voedingspin (pin 30). Dit vereist een invoerspanning tussen 7-20 volt omdat deze spanning door de onboard regelaar van de Arduino gaat. Als je het via USB wilt voeden, hoef je alleen te zorgen dat je 5 Volt levert, NIET de 7-20 Volt.
Zorg dat je de 10k Ohm weerstand tussen de Vcc en Signaal pinnen op de Hall-sensor solderd. Anders krijg je geen meting. Vergeet ook niet om alle componenten aan gemeenschappelijke grond te verbinden. Ik maakte de fout om een van de gronden over te slaan en mijn systeem werkte een tijdje, maar de Hall-sensor begon uiteindelijk te falen. Dat was een heel goede dag voor mijn hond.
Ik maakte uiteindelijk custom connectoren voor de Hall-sensor en servo-verbindingen. Ik solderde de draden aan mannelijke pin headers. Deze steken uit de onderkant van de behuizing. Voor de Hall-sensor maakte ik een custom vrouwelijke adapter door een paar Dupont-connector draden die ik had liggen te knippen, af te isoleren en te solderen.
Voor de Vcc en ground rail knipte ik het voedingsrail-gedeelte af van een extra perma-proto printplaat die ik had liggen. Alles wat er op lijkt werkt. Controleer je verbindingen dubbel voordat je de rail in zijn sleuf zet. Dit is het moeilijkste om weer uit te halen als het er eenmaal in zit. Ik heb dat ook de harde manier geleerd.






Dat is het! Hopelijk hebben jullie evenveel plezier aan het bouwen van dit project als ik. Het ziet eruit als een uitdaging, maar hoe meer je erin duikt, hoe makkelijker het wordt. Laat me weten als je vragen hebt over iets. Veel plezier met knutselen!
Code
Voeder code C/C++
// Final version for the pet feeder
/* Features:
- Gemakkelijk te navigeren menu
- Overzicht van voedtijden, huidige tijd, voedervoltooiingen en voerportie op het hoofsscherm
- Bestuurbare porties met Hall-sensor voor terugkoppeling
- Accurate tijdmeting met DS1307 chip
- Handmatig ingestelde tijd in DS1307 chip wijzigen
- Twee voederingen per dag
- Handmatige voederoptie
- Voeding hervatten bij stroomuitval
- LED-indicatie van Hall-sensor en real-time clock
- Voedtijden en voltooiingen veilig opgeslagen in EEPROM
- Servo "schudden" voor het geval voer vastloopt tijdens doseren
*/
#include <JC_Button.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidMenu.h>
#include <RTClib.h>
#include <Servo.h>
#include <EEPROM.h>
// Maakt servo-object aan om je servo te besturen
Servo myservo;
// Alle input en I/O pinnen toewijzen
#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
// Alle knoppen definiëren, werkt met JC_Button bibliotheek
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);
// LCD I2C en RTC definiëren
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS1307 rtc;
// Variabelen die door de hele code gebruikt worden
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;
// Hall-sensor interrupt
volatile boolean hallSensorActivated = false;
volatile int isr_count = 1;
void hallActiveISR() {
hallSensorActivated = true;
digitalWrite(HALL_LED_PIN, HIGH);
isr_count = isr_count + 1;
}
/* Ik gebruik enums hier om beter bij te houden welke knop ingedrukt wordt
in plaats van elke knop een integer waarde te geven. */
enum { btnENTER, btnUP, btnDOWN, btnBACK };
/* States van de State Machine. Hetzelfde hier met enum type.
Het maakt het makkelijker om bij te houden in welk menu je zit of wilt gaan
in plaats van elk menu een integer waarde te geven */
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
};
// Houdt state van de state machine
STATES state;
// Gebruikersinput variabelen
int Hour;
int Minute;
int portion;
int feed_time1_hour;
int feed_time1_min;
int feed_time2_hour;
int feed_time2_min;
int userInput;
// Speciaal teken vinkje
byte check_Char[8] = {
B00000, B00000, B00001, B00011,
B10110, B11100, B11000, B00000
};
//======================De Setup===========================
void setup() {
Wire.begin();
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.createChar(0, check_Char);
if (!rtc.begin()) {
Serial.println("Kon RTC niet vinden!");
}
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// De knoppen
enterBtn.begin();
upBtn.begin();
downBtn.begin();
backBtn.begin();
manualFeedBtn.begin();
// Initiële state van State Machine instellen
state = MAIN;
// Inputs en outputs instellen
pinMode(POWER_LED_PIN, OUTPUT);
pinMode(HALL_LED_PIN, OUTPUT);
pinMode(hallPin, INPUT);
/* Interrupt koppelen aan de pin die verbonden is met de Hall-sensor.
De Hall-sensor die ik gebruik staat op HIGH en gaat LOW wanneer hij
een magneet ontmoet. Daarom staat het op FALLING */
attachInterrupt (digitalPinToInterrupt(hallPin), hallActiveISR, FALLING);
// Standaard state van LEDs
digitalWrite(POWER_LED_PIN, HIGH);
digitalWrite (HALL_LED_PIN, LOW);
/* Deze functies halen de opgeslagen voedtijd, voltooiing en portiegrootte
uit EEPROM op bij opstart. Ik deed dit omdat ik hier willekeurige
stroomuitvallen heb. */
get_feed_time1();
get_feed_time2();
get_completed_feedings();
get_portion();
}
//======================De Loop===========================
void loop() {
changing_states();
check_buttons();
check_feedtime();
check_rtc();
manual_feed_check();
}
//=============== De Functies =======================
/* Gebruikt de JC_Button bibliotheek om continu te checken of een knop ingedrukt is.
Afhankelijk van welke knop ingedrukt is, zet het de variabele userInput
voor gebruik in de functie menu_transitions */
void check_buttons() {
enterBtn.read();
upBtn.read();
downBtn.read();
backBtn.read();
manualFeedBtn.read();
if (enterBtn.wasPressed()) {
Serial.println("Je drukte op Enter!");
userInput = btnENTER;
menu_transitions(userInput);
}
if (upBtn.wasPressed()) {
Serial.println("Je drukte op Omhoog!");
userInput = btnUP;
menu_transitions(userInput);
}
if (downBtn.wasPressed()) {
Serial.println("Je drukte op Omlaag!");
userInput = btnDOWN;
menu_transitions(userInput);
}
if (backBtn.wasPressed()) {
Serial.println("Je drukte op Terug!");
userInput = btnBACK;
menu_transitions(userInput);
}
if (manualFeedBtn.wasPressed()) {
Serial.println("Je voedt handmatig!");
manualFeed = true;
}
}
//=====================================================
/* Deze functie bepaalt wat er getoond wordt, afhankelijk van welk menu
of "state" je in zit. Elk menu heeft een functie die het respectieve menu toont */
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:
case EDIT_FEED1_MIN:
set_feed_time1();
break;
case EDIT_FEED2_HOUR:
case EDIT_FEED2_MIN:
set_feed_time2();
break;
case EDIT_HOUR:
case EDIT_MIN:
set_the_time();
break;
case EDIT_PORTION:
set_the_portion();
break;
}
}
//=====================================================
/* Dit is het overgangsgedeelte van de state machine.
Dit stelt je in staat om van het ene menu naar het andere te gaan
en waarden te wijzigen */
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("*Instellingen Opgeslagen*");
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("*Instellingen Opgeslagen*");
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("*Instellingen Opgeslagen*");
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("*Instellingen Opgeslagen*");
delay(1000);
lcd.clear();
write_portion();
state = MAIN;
}
break;
}
}
//=====================================================
// Deze functie checkt de voedtijd tegen de huidige tijd
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("Dispenseren");
lcd.setCursor(1, 1);
lcd.print("Eerste Voeding");
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("Dispenseren");
lcd.setCursor(0, 1);
lcd.print("Tweede Voeding");
startFeeding();
}
}
}
}
// Middernacht reset
if ((now.hour() == 0) && (now.minute() == 0)) {
feeding1_complete = false;
feeding2_complete = false;
EEPROM.write(4, feeding1_complete);
EEPROM.write(5, feeding2_complete);
}
/* Als stroomuitval gebeurt tijdens een voedtijd, checkt dit of de
voedtijd voorbij is en of de voeding plaatsvond. Zo niet, dan voedt het. */
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("Oeps!");
lcd.setCursor(2, 1);
lcd.print("Stroomuitval");
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("Oeps!");
lcd.setCursor(2, 1);
lcd.print("Stroomuitval");
startFeeding();
}
}
}
//=====================================================
// Toont het ingestelde portie menu
void display_set_portion_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Opties");
lcd.setCursor(0, 1);
lcd.print("Stel de Portie in");
}
//=====================================================
// Toont het menu waar je de huidige tijd wijzigt
void set_the_time() {
lcd.setCursor(2, 0);
lcd.print("Stel de Tijd in");
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();
}
//=====================================================
// Toont het menu waar je de voerportie wijzigt
void set_the_portion() {
lcd.setCursor(0, 0);
lcd.print("Stel de Portie in");
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();
}
//=====================================================
// Toont de menuoptie voor het instellen van de tijd
void display_set_time_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Opties");
lcd.setCursor(2, 1);
lcd.print("Stel de Tijd in");
}
//=====================================================
// Toont het menu waar je de tweede voedtijd wijzigt
void set_feed_time2() {
lcd.setCursor(0, 0);
lcd.print("Stel Voedtijd 2 in");
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();
}
//=====================================================
// Toont het menu waar je de eerste voedtijd wijzigt
void set_feed_time1() {
lcd.setCursor(0, 0);
lcd.print("Stel Voedtijd 1 in");
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();
}
//=====================================================
// Voegt een leidende nul toe aan eencijferige getallen
void add_leading_zero(int num) {
if (num < 10) {
lcd.print("0");
}
lcd.print(num);
}
//=====================================================
/* Toont de voedtijd op het hoofdmenu alsmede het vinkje voor
visuele bevestiging van een voltooiide voeding */
void display_feeding_times() {
// Eerste voedtijd tonen
lcd.setCursor(0, 0);
lcd.print("V1:");
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(" ");
}
// Tweede voedtijd tonen
lcd.setCursor(0, 1);
lcd.print("V2:");
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(" ");
}
}
//=====================================================
// Toont de huidige tijd in het hoofdmenu
void display_current_time() {
DateTime now = rtc.now();
lcd.setCursor(11, 0);
add_leading_zero(now.hour());
lcd.print(":");
add_leading_zero(now.minute());
}
//=====================================================
// Toont de menuoptie voor het instellen van de eerste voedtijd
void display_set_feed_time1_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Opties");
lcd.setCursor(0, 1);
lcd.print("Stel Voedtijd 1 in");
}
//=====================================================
// Toont de menuoptie voor het instellen van de tweede voedtijd
void display_set_feed_time2_menu() {
lcd.setCursor(2, 0);
lcd.print("Menu Opties");
lcd.setCursor(0, 1);
lcd.print("Stel Voedtijd 2 in");
}
//=====================================================
// Toont de voerportie in het hoofdmenu
void display_portion() {
lcd.setCursor(12, 1);
lcd.print("P:");
add_leading_zero(portion);
}
//=====================================================
// Start het voederproces
void startFeeding() {
myservo.attach(SERVO_PIN);
count = 1;
hallSensorTime = millis();
while (count <= portion) {
servoStart();
if (hallSensorActivated == true) {
count = count + 1;
hallSensorTime = millis();
hallSensorActivated = false;
digitalWrite(HALL_LED_PIN, LOW);
}
/* Beweeg de servo een beetje met de klok mee om vastgelopen voer los te maken */
if (millis() - hallSensorTime > rotationTime) {
hall_sensor_fail = true;
Serial.println("Ik ben in Schud-modus");
jiggle();
}
}
// Houd bij welke voeding net gebeurd is en schrijf naar 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);
/* Los de servo van de pin zodat hij geen signaal meer ontvangt.
Je moet misschien een delay toevoegen voordat dit gebeurt zodat
de sensor stopt wanneer een magneet over de Hall-sensor is.
Er was significante momentum in mijn systeem dus ik had het niet nodig. */
myservo.detach();
lcd.clear();
delay_currentMillis = millis();
while (millis() - delay_currentMillis <= delay_interval) {
lcd.setCursor(2, 0);
lcd.print("Voeding Klaar");
}
lcd.clear();
}
//=====================================================
void servoStart() {
myservo.write(180);
}
//=====================================================
void servoStop() {
// Deze waarde varieert, je moet het door uitproberen vinden
myservo.write(94);
}
//=====================================================
// "Schudt" de servo voor het geval voer vastloopt
void jiggle() {
myservo.write(80);
delay(30);
myservo.write(93);
delay(30);
myservo.write(180);
}
//=====================================================
// Schrijft de uur en minuut waarden voor 1e voeding naar EEPROM
void write_feeding_time1() {
EEPROM.write(0, feed_time1_hour);
EEPROM.write(1, feed_time1_min);
}
//=====================================================
// Schrijft de uur en minuut waarden voor 2e voeding naar EEPROM
void write_feeding_time2() {
EEPROM.write(2, feed_time2_hour);
EEPROM.write(3, feed_time2_min);
}
//=====================================================
// Schrijft portiewaarde naar EEPROM
void write_portion() {
EEPROM.write(6, portion);
}
//=====================================================
// Leest uur en minuut van 1e voedtijd uit 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;
}
//=====================================================
// Leest uur en minuut van 2e voedtijd uit 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;
}
//=====================================================
// Leest ingestelde portiewaarde uit EEPROM
void get_portion() {
portion = EEPROM.read(6);
}
//=====================================================
// Leest boolean waarde of voedingen hebben plaatsgevonden uit EEPROM
void get_completed_feedings() {
feeding1_complete = EEPROM.read(4);
feeding2_complete = EEPROM.read(5);
}
//=====================================================
/* Checkt of de Hall-sensor een magneet niet heeft gelezen
en laat de rode LED knipperen */
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;
}
}
//=====================================================
// Checkt of je de handmatige voerknop indrukt
void manual_feed_check() {
if (manualFeed == true) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Handmatig Voeren");
startFeeding();
manualFeed = false;
}
}
//=====================================================
// Checkt of RTC draait
void check_rtc() {
if (!rtc.isrunning()) {
led_blink();
}
}
//=====================================================
/* Laat de rode LED knipperen wanneer RTC gefaald heeft.
Let op: de LED knippert met een andere frequentie dan
wanneer de Hall-sensor faalt */
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);
}
}
//=====================================================
// Maakt het knipper-effect bij het wijzigen van waarden
void blinkFunction() {
blink_currentMillis = millis();
if (blink_currentMillis - blink_previousMillis > blink_interval) {
blink_previousMillis = blink_currentMillis;
blink_state = !blink_state;
}
}
//=====================================================
Link naar code op mijn Github
https://github.com/russo08/Pet-feeder/blob/main/feeder_final.ino
Aangepaste Onderdelen en Behuizingen
Fusion 360 en STL bestanden op mijn Github
Hier zijn alle Fusion 360 bestanden voor het geval je ze wilt aanpassen voor andere componentformaten. Ik heb ook de STL-bestanden geleverd. Het enige model dat er niet op staat is de buis voor de Hall-sensor. Dat zou redelijk makkelijk te modelleren en printen moeten zijn.
https://github.com/russo08/Pet-feeder.git
Schema's
Dit is het schakelschema. Je kunt het aanpassen als je moet. Als je dat doet, onthoud dan om dezelfde aanpassingen in de code door te voeren.

Productieproces
- Lijnvolgsensor met Raspberry Pi – Simpele KY-033 deur/lijndetector
- Een robuuste temperatuur- en sensor-API bouwen op Raspberry Pi met de GY-91-module
- Integreren van een thermocouple-sensor met de Arduino Portenta H7 via de MAX6675 IC
- 3D-geprinte Onderdelen Kleuren met Was Crayons: Een Nieuwe Post-processing Techniek
- Kwaliteitscontrole Verhogen: 3D‑Geprint Tooling Verbetert Productieprecisie
- Functionele 3D‑Geprinte Fiets Pannier Adapters Ontwerpen Met Kevlar CFF
- De Go-Kart Heruitvinden: 3D-geprinte Koolstofvezel-onderdelen voor Ongekende Prestaties
- Webinar: Sterkere 3D-delen Bouwen met Eiger Software
- 3D-geprinte Drone Maakt Snelle, Kosteneffectieve Dataverzameling in Antarctica Mogelijk
- Hoe 3D-printen MIT Motorsports Team Een Competitieve Voordeel Gaf in Formula SAE
- 3D-geprinte deelssterkte optimaliseren met efficiënte vezelroutering – Deel 1