Components and supplies
Continuous rotation Servo
8mm magnets
Arduino Nano R3
DS 1307 real time clock module
5 mm LED: Green
Pushbutton Switch, Momentary
5 mm LED: Red
Resistor 330 ohm
RGB Backlight LCD - 16x2
I2C LCD Backpack
Resistor 10k ohm
Female DC power jack adapter
Cereal Dispenser
9-12 Volt wall wart
Hall sensor (unipolar)
LM-317 Voltage regulator module
Tools and machines
Circular Saw
Drill / Driver, Cordless
3D Printer (generic)
Project description
Code
Feeder code
c_cpp
1// Final version for the pet feeder 2 3/* Features: 4 - Easy to navigate menu 5 - Overview of feed times, current time, feeding comletions, 6 and feeding portion on the main screen 7 - Controllable portions using a hall sensor for feedback 8 - Accurate time keeping with DS1307 chip 9 - Can manually change set time in DS1307 chip 10 - Two feedings per day 11 - Manual feeding option 12 - Feeding restart in case of power outtage 13 - LED indication of hall sensor and real time clock 14 - Feeding times and completions safetly store in EEPROM 15 - Servo "jiggle" in the event of food getting stuck 16*/ 17 18#include <JC_Button.h> 19#include <Wire.h> 20#include <LiquidCrystal_I2C.h> 21#include <LiquidMenu.h> 22#include <RTClib.h> 23#include <Servo.h> 24#include <EEPROM.h> 25 26// Creates servo object to control your servo 27Servo myservo; 28 29// Assigning all my input and I/O pins 30#define ENTER_BUTTON_PIN 11 31#define UP_BUTTON_PIN 10 32#define DOWN_BUTTON_PIN 9 33#define BACK_BUTTON_PIN 8 34#define POWER_LED_PIN 5 35#define MANUAL_BUTTON_PIN A1 36#define hallPin 2 37#define HALL_LED_PIN 7 38#define SERVO_PIN 6 39 40// Defining all the Buttons, works with the JC_Button library 41Button enterBtn (ENTER_BUTTON_PIN); 42Button upBtn (UP_BUTTON_PIN); 43Button downBtn (DOWN_BUTTON_PIN); 44Button backBtn (BACK_BUTTON_PIN); 45Button manualFeedBtn (MANUAL_BUTTON_PIN); 46 47// Defining LCD I2C and RTC 48LiquidCrystal_I2C lcd(0x27, 16, 2); 49RTC_DS1307 rtc; 50 51//Variables used throughout the code 52 53unsigned long hallSensorTime; 54unsigned long rotationTime = 400; 55unsigned long led_previousMillis = 0; 56const long interval_delay = 1000; 57unsigned long delay_interval = 2000; 58int ledState = LOW; 59 60boolean manualFeed = false; 61boolean hall_sensor_fail = false; 62 63unsigned long blink_previousMillis = 0; 64unsigned long blink_currentMillis = 0; 65unsigned long blink_interval = 500; 66 67unsigned long delay_currentMillis = 0; 68unsigned long delay_previousMillis = 0; 69 70boolean blink_state = false; 71int count; 72boolean feeding1_complete = false; 73boolean feeding2_complete = false; 74boolean feeding1_trigger = false; 75boolean feeding2_trigger = false; 76boolean servoOn = true; 77 78//Hall sensor interrupt 79 80volatile boolean hallSensorActivated = false; 81volatile int isr_count = 1; 82void hallActiveISR() 83{ 84 hallSensorActivated = true; 85 digitalWrite(HALL_LED_PIN, HIGH); 86 isr_count = isr_count + 1; 87} 88 89/* I use enums here to keep better track of what button is 90 being pushed as opposed to just having each button set to 91 an interger value. 92*/ 93enum { 94 btnENTER, 95 btnUP, 96 btnDOWN, 97 btnBACK, 98}; 99 100/* States of the State Machine. Same thing here with the enum 101 type. It makes it easier to keep track of what menu you are 102 in or want to go to instead of giving each menu an intreger value 103*/ 104enum STATES { 105 MAIN, 106 MENU_EDIT_FEED1, 107 MENU_EDIT_FEED2, 108 MENU_EDIT_TIME, 109 MENU_EDIT_PORTION, 110 111 EDIT_FEED1_HOUR, 112 EDIT_FEED1_MIN, 113 114 EDIT_FEED2_HOUR, 115 EDIT_FEED2_MIN, 116 117 EDIT_HOUR, 118 EDIT_MIN, 119 120 EDIT_PORTION 121}; 122 123// Holds state of the state machine 124STATES state; 125 126//User input variables 127int Hour; 128int Minute; 129int portion; 130 131int feed_time1_hour; 132int feed_time1_min; 133int feed_time2_hour; 134int feed_time2_min; 135 136int userInput; 137 138// Special character check mark 139byte check_Char[8] = { 140 B00000, 141 B00000, 142 B00001, 143 B00011, 144 B10110, 145 B11100, 146 B11000, 147 B00000 148}; 149//======================The Setup=========================== 150 151void setup() { 152 Wire.begin(); 153 Serial.begin(9600); 154 lcd.init(); 155 lcd.backlight(); 156 lcd.createChar(0, check_Char); 157 158 if (!rtc.begin()) { 159 Serial.println("Couldn't find RTC!"); 160 } 161 162 // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 163 164 //The buttons 165 enterBtn.begin(); 166 upBtn.begin(); 167 downBtn.begin(); 168 backBtn.begin(); 169 manualFeedBtn.begin(); 170 171 //Setting up initial state of State Machine 172 state = MAIN; 173 174 //Setting up inputs and outputs 175 pinMode(POWER_LED_PIN, OUTPUT); 176 pinMode(HALL_LED_PIN, OUTPUT); 177 pinMode(hallPin, INPUT); 178 179 /* Attatching interrupt to the pin that connects to the hall sensor. 180 The hall sensor I used is set to HIGH and goes LOW when it encounters 181 a magnet. Thats why its set to FALLING 182 */ 183 attachInterrupt (digitalPinToInterrupt(hallPin), hallActiveISR, FALLING); 184 // default state of LEDs 185 digitalWrite(POWER_LED_PIN, HIGH); 186 digitalWrite (HALL_LED_PIN, LOW); 187 188 /* These functions get the stored feed time, completed feeding, 189 and portion size from EEPROM on start-up. I did this because I get random 190 power outtages here where I live. 191 */ 192 get_feed_time1(); 193 get_feed_time2(); 194 get_completed_feedings(); 195 get_portion(); 196 197} 198 199//======================The Loop=========================== 200void loop() { 201 changing_states(); 202 203 check_buttons(); 204 205 check_feedtime (); 206 207 check_rtc(); 208 209 manual_feed_check (); 210} 211 212//=============== The Functions ======================= 213 214/* Uses the JC_Button Library to continually check if a button 215 was pressed. Depending on what button is pressed, it sets the 216 variable userInput to be used in the fucntion menu_transitions 217*/ 218void check_buttons () { 219 enterBtn.read(); 220 upBtn.read(); 221 downBtn.read(); 222 backBtn.read(); 223 manualFeedBtn.read(); 224 225 if (enterBtn.wasPressed()) { 226 Serial.println ("You Pressed Enter!"); 227 userInput = btnENTER; 228 menu_transitions(userInput); 229 } 230 if (upBtn.wasPressed()) { 231 Serial.println ("You Pressed Up!"); 232 userInput = btnUP; 233 menu_transitions(userInput); 234 } 235 if (downBtn.wasPressed()) { 236 Serial.println ("You Pressed Down!"); 237 userInput = btnDOWN; 238 menu_transitions(userInput); 239 } 240 if (backBtn.wasPressed()) { 241 Serial.println ("You Pressed Back!"); 242 userInput = btnBACK; 243 menu_transitions(userInput); 244 } 245 if (manualFeedBtn.wasPressed()) { 246 Serial.println ("You Are Manually Feeding!"); 247 manualFeed = true; 248 } 249} 250//===================================================== 251 252/* This funcion determines what is displayed, depending on what menu 253 or "state" you are in. Each menu has a function that displays the 254 respective menu 255*/ 256void changing_states() { 257 258 switch (state) { 259 case MAIN: 260 display_current_time(); 261 display_feeding_times(); 262 display_portion(); 263 break; 264 265 case MENU_EDIT_FEED1: 266 display_set_feed_time1_menu(); 267 break; 268 269 case MENU_EDIT_FEED2: 270 display_set_feed_time2_menu(); 271 break; 272 273 case MENU_EDIT_TIME: 274 display_set_time_menu(); 275 break; 276 277 case MENU_EDIT_PORTION: 278 display_set_portion_menu (); 279 break; 280 281 case EDIT_FEED1_HOUR: 282 set_feed_time1(); 283 break; 284 285 case EDIT_FEED1_MIN: 286 set_feed_time1(); 287 break; 288 289 case EDIT_FEED2_HOUR: 290 set_feed_time2(); 291 break; 292 293 case EDIT_FEED2_MIN: 294 set_feed_time2(); 295 break; 296 297 case EDIT_HOUR: 298 set_the_time(); 299 break; 300 301 case EDIT_MIN: 302 set_the_time(); 303 break; 304 305 case EDIT_PORTION: 306 set_the_portion(); 307 break; 308 } 309} 310//===================================================== 311/* This is the transitional part of the state machine. This is 312 what allows you to go from one menu to another and change values 313*/ 314void menu_transitions(int input) { 315 316 switch (state) { 317 case MAIN: 318 if (input == btnENTER) { 319 lcd.clear(); 320 state = MENU_EDIT_FEED1; 321 } 322 if (input == btnBACK) 323 { 324 hall_sensor_fail = false; 325 } 326 break; 327 //---------------------------------------------------- 328 case MENU_EDIT_FEED1: 329 if (input == btnBACK) { 330 lcd.clear(); 331 state = MAIN; 332 } 333 else if (input == btnENTER) { 334 lcd.clear(); 335 state = EDIT_FEED1_HOUR; 336 } 337 else if (input == btnDOWN) { 338 lcd.clear(); 339 state = MENU_EDIT_FEED2; 340 } 341 break; 342 //---------------------------------------------------- 343 case EDIT_FEED1_HOUR: 344 // Need this to prevent servo going off while setting time 345 servoOn = false; 346 if (input == btnUP) { 347 feed_time1_hour++; 348 if (feed_time1_hour > 23) { 349 feed_time1_hour = 0; 350 } 351 } 352 else if (input == btnDOWN) { 353 feed_time1_hour--; 354 if (feed_time1_hour < 0) { 355 feed_time1_hour = 23; 356 } 357 } 358 else if (input == btnBACK) { 359 lcd.clear(); 360 servoOn = true; 361 state = MENU_EDIT_FEED1; 362 } 363 else if (input == btnENTER) { 364 state = EDIT_FEED1_MIN; 365 } 366 break; 367 //---------------------------------------------------- 368 case EDIT_FEED1_MIN: 369 if (input == btnUP) { 370 feed_time1_min++; 371 if (feed_time1_min > 59) { 372 feed_time1_min = 0; 373 } 374 } 375 else if (input == btnDOWN) { 376 feed_time1_min--; 377 if (feed_time1_min < 0) { 378 feed_time1_min = 59; 379 } 380 } 381 else if (input == btnBACK) { 382 state = EDIT_FEED1_HOUR; 383 } 384 else if (input == btnENTER) { 385 lcd.clear(); 386 lcd.setCursor(0, 0); 387 lcd.print( "*Settings Saved*"); 388 delay(1000); 389 lcd.clear(); 390 servoOn = true; 391 write_feeding_time1 (); 392 state = MAIN; 393 } 394 break; 395 //---------------------------------------------------- 396 case MENU_EDIT_FEED2: 397 if (input == btnUP) { 398 lcd.clear(); 399 state = MENU_EDIT_FEED1; 400 } 401 else if (input == btnENTER) { 402 lcd.clear(); 403 state = EDIT_FEED2_HOUR; 404 } 405 else if (input == btnDOWN) { 406 lcd.clear(); 407 state = MENU_EDIT_TIME; 408 } 409 break; 410 //---------------------------------------------------- 411 case EDIT_FEED2_HOUR: 412 servoOn = false; 413 if (input == btnUP) { 414 feed_time2_hour++; 415 if (feed_time2_hour > 23) { 416 feed_time2_hour = 0; 417 } 418 } 419 else if (input == btnDOWN) { 420 feed_time2_hour--; 421 if (feed_time2_hour < 0) { 422 feed_time2_hour = 23; 423 } 424 } 425 else if (input == btnBACK) { 426 lcd.clear(); 427 servoOn = true; 428 state = MENU_EDIT_FEED2; 429 } 430 else if (input == btnENTER) { 431 state = EDIT_FEED2_MIN; 432 } 433 break; 434 //---------------------------------------------------- 435 case EDIT_FEED2_MIN: 436 if (input == btnUP) { 437 feed_time2_min++; 438 if (feed_time2_min > 59) { 439 feed_time2_min = 0; 440 } 441 } 442 else if (input == btnDOWN) { 443 feed_time2_min--; 444 if (feed_time2_min < 0) { 445 feed_time2_min = 59; 446 } 447 } 448 else if (input == btnBACK) { 449 state = EDIT_FEED2_HOUR; 450 } 451 else if (input == btnENTER) { 452 lcd.clear(); 453 lcd.setCursor(0, 0); 454 lcd.print( "*Settings Saved*"); 455 delay(1000); 456 lcd.clear(); 457 servoOn = true; 458 write_feeding_time2 (); 459 state = MAIN; 460 } 461 break; 462 //---------------------------------------------------- 463 case MENU_EDIT_TIME: 464 if (input == btnUP) { 465 lcd.clear(); 466 state = MENU_EDIT_FEED2; 467 } 468 else if (input == btnENTER) { 469 lcd.clear(); 470 state = EDIT_HOUR; 471 } 472 else if (input == btnDOWN) { 473 lcd.clear(); 474 state = MENU_EDIT_PORTION; 475 } 476 break; 477 //---------------------------------------------------- 478 case EDIT_HOUR: 479 if (input == btnUP) { 480 Hour++; 481 if (Hour > 23) { 482 Hour = 0; 483 } 484 } 485 else if (input == btnDOWN) { 486 Hour--; 487 if (Hour < 0) { 488 Hour = 23; 489 } 490 } 491 else if (input == btnBACK) { 492 lcd.clear(); 493 state = MENU_EDIT_TIME; 494 } 495 else if (input == btnENTER) { 496 state = EDIT_MIN; 497 } 498 break; 499 //---------------------------------------------------- 500 case EDIT_MIN: 501 if (input == btnUP) { 502 Minute++; 503 if (Minute > 59) { 504 Minute = 0; 505 } 506 } 507 else if (input == btnDOWN) { 508 Minute--; 509 if (Minute < 0) { 510 Minute = 59; 511 } 512 } 513 else if (input == btnBACK) { 514 state = EDIT_HOUR; 515 } 516 else if (input == btnENTER) { 517 lcd.clear(); 518 lcd.setCursor(0, 0); 519 lcd.print( "*Settings Saved*"); 520 delay(1000); 521 lcd.clear(); 522 rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); 523 state = MAIN; 524 } 525 break; 526 //---------------------------------------------------- 527 case MENU_EDIT_PORTION: 528 if (input == btnUP) { 529 lcd.clear(); 530 state = MENU_EDIT_TIME; 531 } 532 else if (input == btnENTER) { 533 lcd.clear(); 534 state = EDIT_PORTION; 535 } 536 break; 537 //---------------------------------------------------- 538 case EDIT_PORTION: 539 if (input == btnUP) { 540 portion++; 541 if (portion > 20) { 542 portion = 1; 543 } 544 } 545 else if (input == btnDOWN) { 546 portion--; 547 if (portion < 1) { 548 portion = 20; 549 } 550 } 551 else if (input == btnBACK) { 552 lcd.clear(); 553 state = MENU_EDIT_PORTION; 554 } 555 else if (input == btnENTER) { 556 lcd.clear(); 557 lcd.setCursor(0, 0); 558 lcd.print( "*Settings Saved*"); 559 delay(1000); 560 lcd.clear(); 561 write_portion(); 562 state = MAIN; 563 } 564 break; 565 } 566} 567//===================================================== 568// This function checks the feed time against the current time 569 570void check_feedtime () 571{ 572 DateTime now = rtc.now(); 573 if (now.second() == 0) { 574 if ((now.hour() == feed_time1_hour) && (now.minute() == feed_time1_min)) 575 { 576 feeding1_trigger = true; 577 if (servoOn) 578 { 579 if (feeding1_complete == false) 580 { 581 lcd.clear(); 582 lcd.setCursor(3, 0); 583 lcd.print ("Dispensing"); 584 lcd.setCursor(1, 1); 585 lcd.print("First Feeding"); 586 startFeeding(); 587 } 588 } 589 } 590 else if ((now.hour() == feed_time2_hour) && (now.minute () == feed_time2_min)) 591 { 592 feeding2_trigger = true; 593 if (servoOn) 594 { 595 if ( feeding2_complete == false) 596 { 597 lcd.clear(); 598 lcd.setCursor(3, 0); 599 lcd.print ("Dispensing"); 600 lcd.setCursor(0, 1); 601 lcd.print("Second Feeding"); 602 startFeeding(); 603 } 604 } 605 } 606 } 607 // Midnight Reset 608 if ( (now.hour() == 0) && (now.minute() == 0)) 609 { 610 feeding1_complete = false; 611 feeding2_complete = false; 612 EEPROM.write(4, feeding1_complete); 613 EEPROM.write(5, feeding2_complete); 614 } 615 /*If power outtage happens during a feed time, this checks to see if the 616 feed time has passed and if the feeding occurred. If not, it feeds. 617 */ 618 if ( (now.hour() >= feed_time1_hour) && (now.minute() > feed_time1_min)) 619 { 620 if ((feeding1_complete == 0) && (feeding1_trigger == 0)) 621 { 622 lcd.clear(); 623 lcd.setCursor(5, 0); 624 lcd.print ("Uh-Oh!"); 625 lcd.setCursor(2, 1); 626 lcd.print("Power Outage"); 627 startFeeding(); 628 } 629 } 630 if ( (now.hour() >= feed_time2_hour) && (now.minute() > feed_time2_min)) 631 { 632 if ((feeding2_complete == 0) && (feeding2_trigger == 0)) 633 { 634 lcd.clear(); 635 lcd.setCursor(5, 0); 636 lcd.print ("Uh-Oh!"); 637 lcd.setCursor(2, 1); 638 lcd.print("Power Outage"); 639 startFeeding(); 640 } 641 } 642} 643 644//===================================================== 645// Displays the set portion menu option 646void display_set_portion_menu () { 647 lcd.setCursor(2, 0); 648 lcd.print("Menu Options"); 649 lcd.setCursor(0, 1); 650 lcd.print("Set the Portion"); 651} 652//===================================================== 653// Displays the menu where you change the current time 654void set_the_time () 655{ 656 lcd.setCursor(2, 0); 657 lcd.print("Set the Time"); 658 switch (state) 659 { 660 //---------------------------------------------------- 661 case EDIT_HOUR: 662 663 if (blink_state == 0) 664 { 665 lcd.setCursor(5, 1); 666 add_leading_zero(Hour); 667 } 668 else 669 { 670 lcd.setCursor(5, 1); 671 lcd.print(" "); 672 } 673 lcd.print(":"); 674 add_leading_zero(Minute); 675 break; 676 //---------------------------------------------------- 677 case EDIT_MIN: 678 lcd.setCursor(5, 1); 679 add_leading_zero(Hour); 680 lcd.print(":"); 681 if (blink_state == 0) 682 { 683 lcd.setCursor(8, 1); 684 add_leading_zero(Minute); 685 } 686 else 687 { 688 lcd.setCursor(8, 1); 689 lcd.print(" "); 690 } 691 break; 692 } 693 blinkFunction(); 694} 695//===================================================== 696// Displays the menu where you change the feeding portion 697void set_the_portion () 698{ 699 lcd.setCursor (0, 0); 700 lcd.print("Set the Portion"); 701 switch (state) 702 { 703 case EDIT_PORTION: 704 if (blink_state == 0) 705 { 706 lcd.setCursor(7, 1); 707 add_leading_zero(portion); 708 } 709 else 710 { 711 lcd.setCursor(7, 1); 712 lcd.print(" "); 713 } 714 } 715 blinkFunction(); 716} 717//===================================================== 718//Displays the menu option for setting the time 719void display_set_time_menu () { 720 lcd.setCursor(2, 0); 721 lcd.print("Menu Options"); 722 lcd.setCursor(2, 1); 723 lcd.print("Set the Time"); 724} 725//===================================================== 726// Displays the menu where you change the second feeding time 727void set_feed_time2 () 728{ 729 lcd.setCursor(0, 0); 730 lcd.print("Set Feed Time 2"); 731 switch (state) 732 { 733 //---------------------------------------------------- 734 case EDIT_FEED2_HOUR: 735 736 if (blink_state == 0) 737 { 738 lcd.setCursor(5, 1); 739 add_leading_zero(feed_time2_hour); 740 } 741 else 742 { 743 lcd.setCursor(5, 1); 744 lcd.print(" "); 745 } 746 lcd.print(":"); 747 add_leading_zero(feed_time2_min); 748 break; 749 //---------------------------------------------------- 750 case EDIT_FEED2_MIN: 751 lcd.setCursor(5, 1); 752 add_leading_zero(feed_time2_hour); 753 lcd.print(":"); 754 if (blink_state == 0) 755 { 756 lcd.setCursor(8, 1); 757 add_leading_zero(feed_time2_min); 758 } 759 else 760 { 761 lcd.setCursor(8, 1); 762 lcd.print(" "); 763 } 764 break; 765 } 766 blinkFunction(); 767} 768//===================================================== 769// Displays the menu where you change the first feeding time 770void set_feed_time1 () 771{ 772 lcd.setCursor(0, 0); 773 lcd.print("Set Feed Time 1"); 774 switch (state) 775 { 776 //---------------------------------------------------- 777 case EDIT_FEED1_HOUR: 778 779 if (blink_state == 0) 780 { 781 lcd.setCursor(5, 1); 782 add_leading_zero(feed_time1_hour); 783 } 784 else 785 { 786 lcd.setCursor(5, 1); 787 lcd.print(" "); 788 } 789 lcd.print(":"); 790 add_leading_zero(feed_time1_min); 791 break; 792 //---------------------------------------------------- 793 case EDIT_FEED1_MIN: 794 lcd.setCursor(5, 1); 795 add_leading_zero(feed_time1_hour); 796 lcd.print(":"); 797 if (blink_state == 0) 798 { 799 lcd.setCursor(8, 1); 800 add_leading_zero(feed_time1_min); 801 } 802 else 803 { 804 lcd.setCursor(8, 1); 805 lcd.print(" "); 806 } 807 break; 808 } 809 blinkFunction(); 810} 811//===================================================== 812// Adds a leading zero to single digit numbers 813void add_leading_zero (int num) { 814 if (num < 10) { 815 lcd.print("0"); 816 } 817 lcd.print(num); 818} 819//===================================================== 820/* Displays the feeding time on the main menu as well as the 821 check mark for visual comfirmation of a completed feeding 822*/ 823void display_feeding_times () { 824 //Displaying first feed time 825 lcd.setCursor(0, 0); 826 lcd.print ("F1:"); 827 add_leading_zero(feed_time1_hour); 828 lcd.print(":"); 829 add_leading_zero(feed_time1_min); 830 lcd.print(" "); 831 if (feeding1_complete == true) 832 { 833 lcd.write(0); 834 } 835 else 836 { 837 lcd.print(" "); 838 } 839 //Displaying second feed time 840 lcd.setCursor(0, 1); 841 lcd.print("F2:"); 842 add_leading_zero(feed_time2_hour); 843 lcd.print(":"); 844 add_leading_zero(feed_time2_min); 845 lcd.print(" "); 846 if (feeding2_complete == true) 847 { 848 lcd.write(0); 849 } 850 else 851 { 852 lcd.print(" "); 853 } 854} 855//===================================================== 856// Displays the current time in the main menu 857void display_current_time () { 858 DateTime now = rtc.now(); 859 lcd.setCursor(11, 0); 860 add_leading_zero(now.hour()); 861 lcd.print(":"); 862 add_leading_zero(now.minute()); 863} 864//===================================================== 865// Displays the menu option for setting the first feed time 866void display_set_feed_time1_menu () { 867 lcd.setCursor(2, 0); 868 lcd.print("Menu Options"); 869 lcd.setCursor(0, 1); 870 lcd.print("Set Feed Time 1"); 871} 872//===================================================== 873// Displays the meny option for setting the second feed time 874void display_set_feed_time2_menu () { 875 lcd.setCursor(2, 0); 876 lcd.print("Menu Options"); 877 lcd.setCursor(0, 1); 878 lcd.print("Set Feed Time 2"); 879} 880//===================================================== 881// Displays the feeding portion in the main menu 882void display_portion () 883{ 884 lcd.setCursor (12, 1); 885 lcd.print("P:"); 886 add_leading_zero(portion); 887} 888//===================================================== 889// Starts the feeding process. 890 891void startFeeding() 892{ 893 // attach the servo to the pin 894 myservo.attach(SERVO_PIN); 895 count = 1; 896 hallSensorTime = millis(); 897 // loop so that the servo runs until desired portion is reached 898 while (count <= portion) 899 { 900 servoStart(); 901 if (hallSensorActivated == true) 902 { 903 // digitalWrite(LED_PIN,HIGH); 904 count = count + 1; 905 //resetting for next interrupt 906 hallSensorTime = millis(); 907 hallSensorActivated = false; 908 digitalWrite(HALL_LED_PIN, LOW); 909 } 910 /* Moved the servo clockwise a bit to dislodge food stuck in the 911 dispensing mechanism 912 */ 913 if (millis() - hallSensorTime > rotationTime) 914 { 915 hall_sensor_fail = true; 916 Serial.println("I'm in Jiggle"); 917 jiggle(); 918 } 919 } 920 // Keeps track of which feeding just happened and writes it to EEPROM 921 if ((feeding1_complete == false) && (feeding2_complete == false)) 922 { 923 feeding1_complete = true; 924 EEPROM.write(4, feeding1_complete); 925 } 926 else if ((feeding1_complete == true) && (feeding2_complete == false)) 927 { 928 feeding2_complete = true; 929 EEPROM.write(5, feeding2_complete); 930 } 931 servoStop(); 932 digitalWrite(HALL_LED_PIN, LOW); 933 /* Detaches the servo from the pin so that it is no longer recieving a signal. 934 You may have to add a delay before this so the sensor stops when a magnet is over 935 the hall sensor. There was significant momentum left in my system that I did not need it 936 */ 937 myservo.detach(); 938 lcd.clear(); 939 delay_currentMillis = millis(); 940 while (millis() - delay_currentMillis <= delay_interval) 941 { 942 lcd.setCursor(2, 0); 943 lcd.print ("Feeding Done"); 944 } 945 lcd.clear(); 946} 947//===================================================== 948 949void servoStart() 950{ 951 myservo.write(180); 952} 953//===================================================== 954 955void servoStop() 956{ 957 // this value will vary, you have to find it through trial and error 958 myservo.write(94); 959} 960//===================================================== 961// "jiggles" the servo in case food gets stuck 962 963void jiggle() 964{ 965 myservo.write(80); 966 delay(30); 967 myservo.write(93); 968 delay(30); 969 myservo.write(180); 970} 971//===================================================== 972// Writes the hour and minute valies set for 1st feeding to the EEPROM 973 974void write_feeding_time1 () 975{ 976 EEPROM.write(0, feed_time1_hour); 977 EEPROM.write(1, feed_time1_min); 978} 979//===================================================== 980// Writes the hour and minute values set for 2nd feeding to the EEPROM 981 982void write_feeding_time2 () { 983 EEPROM.write(2, feed_time2_hour); 984 EEPROM.write(3, feed_time2_min); 985} 986//===================================================== 987// Writes portion value set to the EEPROM 988 989void write_portion () 990{ 991 EEPROM.write(6, portion); 992} 993//===================================================== 994// Reads the hour and minute values from 1st feed time from EEPROM 995 996void get_feed_time1 () 997{ 998 feed_time1_hour = EEPROM.read(0); 999 if (feed_time1_hour > 23) feed_time1_hour = 0; 1000 feed_time1_min = EEPROM.read(1); 1001 if (feed_time1_min > 59) feed_time1_min = 0; 1002 1003} 1004//===================================================== 1005// Reads the hour and minute values from 2nd feed time from EEPROM 1006 1007void get_feed_time2 () 1008{ 1009 feed_time2_hour = EEPROM.read(2); 1010 if (feed_time2_hour > 23) feed_time2_hour = 0; 1011 feed_time2_min = EEPROM.read(3); 1012 if (feed_time2_min > 59) feed_time2_min = 0; 1013} 1014//===================================================== 1015// Reads portion set value from EEPROM 1016 1017void get_portion () 1018{ 1019 portion = EEPROM.read(6); 1020} 1021//===================================================== 1022// Reads boolean value of whether or not feedings have occured from EEPROM 1023 1024void get_completed_feedings() 1025{ 1026 feeding1_complete = EEPROM.read(4); 1027 feeding2_complete = EEPROM.read(5); 1028} 1029//===================================================== 1030/* Checks to see if the hall sensor has failed to read a magnet and 1031 blinks the red LED 1032*/ 1033 1034void check_hall_sensor () { 1035 if (hall_sensor_fail == true) 1036 { 1037 if (blink_state == 0) 1038 { 1039 digitalWrite(HALL_LED_PIN, HIGH); 1040 } 1041 else 1042 { 1043 digitalWrite(HALL_LED_PIN, LOW); 1044 } 1045 blinkFunction(); 1046 } 1047 else 1048 { 1049 digitalWrite(HALL_LED_PIN, LOW); 1050 hall_sensor_fail = false; 1051 } 1052} 1053//===================================================== 1054// Checks if you push the manual feed button 1055 1056void manual_feed_check () { 1057 if (manualFeed == true) 1058 { 1059 lcd.clear(); 1060 lcd.setCursor(0, 0); 1061 lcd.print(" Manual Feeding"); 1062 startFeeding(); 1063 manualFeed = false; 1064 } 1065} 1066//===================================================== 1067// checks to see if RTC is running 1068 1069void check_rtc () { 1070 if (!rtc.isrunning()) 1071 { 1072 led_blink(); 1073 } 1074} 1075//===================================================== 1076/* Blinks the red led when RTC has failed. Note: the led 1077 will be blinking at a different rate than when the hall 1078 sensor fails 1079*/ 1080 1081void led_blink() 1082{ 1083 unsigned long led_currentMillis = millis(); 1084 if (led_currentMillis - led_previousMillis >= interval_delay) 1085 { 1086 led_previousMillis = led_currentMillis; 1087 if (ledState == LOW) 1088 { 1089 ledState = HIGH; 1090 } 1091 else 1092 { 1093 ledState = LOW; 1094 } 1095 digitalWrite(HALL_LED_PIN, ledState); 1096 } 1097} 1098//===================================================== 1099// Creates the blinking effect when changing values 1100 1101void blinkFunction() 1102{ 1103 blink_currentMillis = millis(); 1104 1105 if (blink_currentMillis - blink_previousMillis > blink_interval) 1106 { 1107 blink_previousMillis = blink_currentMillis; 1108 blink_state = !blink_state; 1109 } 1110} 1111//===================================================== 1112
Link to Code on my Github
Feeder code
c_cpp
1// Final version for the pet feeder 2 3/* Features: 4 - Easy 5 to navigate menu 6 - Overview of feed times, current time, feeding comletions, 7 8 and feeding portion on the main screen 9 - Controllable portions using 10 a hall sensor for feedback 11 - Accurate time keeping with DS1307 chip 12 - 13 Can manually change set time in DS1307 chip 14 - Two feedings per day 15 - 16 Manual feeding option 17 - Feeding restart in case of power outtage 18 - 19 LED indication of hall sensor and real time clock 20 - Feeding times and completions 21 safetly store in EEPROM 22 - Servo "jiggle" in the event of food getting stuck 23*/ 24 25#include 26 <JC_Button.h> 27#include <Wire.h> 28#include <LiquidCrystal_I2C.h> 29#include 30 <LiquidMenu.h> 31#include <RTClib.h> 32#include <Servo.h> 33#include <EEPROM.h> 34 35// 36 Creates servo object to control your servo 37Servo myservo; 38 39// Assigning 40 all my input and I/O pins 41#define ENTER_BUTTON_PIN 11 42#define UP_BUTTON_PIN 43 10 44#define DOWN_BUTTON_PIN 9 45#define BACK_BUTTON_PIN 8 46#define POWER_LED_PIN 47 5 48#define MANUAL_BUTTON_PIN A1 49#define hallPin 2 50#define HALL_LED_PIN 7 51#define 52 SERVO_PIN 6 53 54// Defining all the Buttons, works with the JC_Button library 55Button 56 enterBtn (ENTER_BUTTON_PIN); 57Button upBtn (UP_BUTTON_PIN); 58Button downBtn 59 (DOWN_BUTTON_PIN); 60Button backBtn (BACK_BUTTON_PIN); 61Button manualFeedBtn 62 (MANUAL_BUTTON_PIN); 63 64// Defining LCD I2C and RTC 65LiquidCrystal_I2C lcd(0x27, 66 16, 2); 67RTC_DS1307 rtc; 68 69//Variables used throughout the code 70 71unsigned 72 long hallSensorTime; 73unsigned long rotationTime = 400; 74unsigned long led_previousMillis 75 = 0; 76const long interval_delay = 1000; 77unsigned long delay_interval = 2000; 78int 79 ledState = LOW; 80 81boolean manualFeed = false; 82boolean hall_sensor_fail = 83 false; 84 85unsigned long blink_previousMillis = 0; 86unsigned long blink_currentMillis 87 = 0; 88unsigned long blink_interval = 500; 89 90unsigned long delay_currentMillis 91 = 0; 92unsigned long delay_previousMillis = 0; 93 94boolean blink_state = false; 95int 96 count; 97boolean feeding1_complete = false; 98boolean feeding2_complete = false; 99boolean 100 feeding1_trigger = false; 101boolean feeding2_trigger = false; 102boolean servoOn 103 = true; 104 105//Hall sensor interrupt 106 107volatile boolean hallSensorActivated 108 = false; 109volatile int isr_count = 1; 110void hallActiveISR() 111{ 112 hallSensorActivated 113 = true; 114 digitalWrite(HALL_LED_PIN, HIGH); 115 isr_count = isr_count + 1; 116} 117 118/* 119 I use enums here to keep better track of what button is 120 being pushed as opposed 121 to just having each button set to 122 an interger value. 123*/ 124enum { 125 126 btnENTER, 127 btnUP, 128 btnDOWN, 129 btnBACK, 130}; 131 132/* States of the 133 State Machine. Same thing here with the enum 134 type. It makes it easier to keep 135 track of what menu you are 136 in or want to go to instead of giving each menu 137 an intreger value 138*/ 139enum STATES { 140 MAIN, 141 MENU_EDIT_FEED1, 142 MENU_EDIT_FEED2, 143 144 MENU_EDIT_TIME, 145 MENU_EDIT_PORTION, 146 147 EDIT_FEED1_HOUR, 148 EDIT_FEED1_MIN, 149 150 151 EDIT_FEED2_HOUR, 152 EDIT_FEED2_MIN, 153 154 EDIT_HOUR, 155 EDIT_MIN, 156 157 158 EDIT_PORTION 159}; 160 161// Holds state of the state machine 162STATES state; 163 164//User 165 input variables 166int Hour; 167int Minute; 168int portion; 169 170int feed_time1_hour; 171int 172 feed_time1_min; 173int feed_time2_hour; 174int feed_time2_min; 175 176int userInput; 177 178// 179 Special character check mark 180byte check_Char[8] = { 181 B00000, 182 B00000, 183 184 B00001, 185 B00011, 186 B10110, 187 B11100, 188 B11000, 189 B00000 190}; 191//======================The 192 Setup=========================== 193 194void setup() { 195 Wire.begin(); 196 Serial.begin(9600); 197 198 lcd.init(); 199 lcd.backlight(); 200 lcd.createChar(0, check_Char); 201 202 203 if (!rtc.begin()) { 204 Serial.println("Couldn't find RTC!"); 205 } 206 207 208 // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 209 210 //The buttons 211 212 enterBtn.begin(); 213 upBtn.begin(); 214 downBtn.begin(); 215 backBtn.begin(); 216 217 manualFeedBtn.begin(); 218 219 //Setting up initial state of State Machine 220 221 state = MAIN; 222 223 //Setting up inputs and outputs 224 pinMode(POWER_LED_PIN, 225 OUTPUT); 226 pinMode(HALL_LED_PIN, OUTPUT); 227 pinMode(hallPin, INPUT); 228 229 230 /* Attatching interrupt to the pin that connects to the hall sensor. 231 The 232 hall sensor I used is set to HIGH and goes LOW when it encounters 233 a magnet. 234 Thats why its set to FALLING 235 */ 236 attachInterrupt (digitalPinToInterrupt(hallPin), 237 hallActiveISR, FALLING); 238 // default state of LEDs 239 digitalWrite(POWER_LED_PIN, 240 HIGH); 241 digitalWrite (HALL_LED_PIN, LOW); 242 243 /* These functions get the 244 stored feed time, completed feeding, 245 and portion size from EEPROM on start-up. 246 I did this because I get random 247 power outtages here where I live. 248 */ 249 250 get_feed_time1(); 251 get_feed_time2(); 252 get_completed_feedings(); 253 get_portion(); 254 255} 256 257//======================The 258 Loop=========================== 259void loop() { 260 changing_states(); 261 262 263 check_buttons(); 264 265 check_feedtime (); 266 267 check_rtc(); 268 269 manual_feed_check 270 (); 271} 272 273//=============== The Functions ======================= 274 275/* 276 Uses the JC_Button Library to continually check if a button 277 was pressed. Depending 278 on what button is pressed, it sets the 279 variable userInput to be used in the 280 fucntion menu_transitions 281*/ 282void check_buttons () { 283 enterBtn.read(); 284 285 upBtn.read(); 286 downBtn.read(); 287 backBtn.read(); 288 manualFeedBtn.read(); 289 290 291 if (enterBtn.wasPressed()) { 292 Serial.println ("You Pressed Enter!"); 293 294 userInput = btnENTER; 295 menu_transitions(userInput); 296 } 297 if (upBtn.wasPressed()) 298 { 299 Serial.println ("You Pressed Up!"); 300 userInput = btnUP; 301 menu_transitions(userInput); 302 303 } 304 if (downBtn.wasPressed()) { 305 Serial.println ("You Pressed Down!"); 306 307 userInput = btnDOWN; 308 menu_transitions(userInput); 309 } 310 if (backBtn.wasPressed()) 311 { 312 Serial.println ("You Pressed Back!"); 313 userInput = btnBACK; 314 315 menu_transitions(userInput); 316 } 317 if (manualFeedBtn.wasPressed()) { 318 319 Serial.println ("You Are Manually Feeding!"); 320 manualFeed = true; 321 322 } 323} 324//===================================================== 325 326/* This 327 funcion determines what is displayed, depending on what menu 328 or "state" 329 you are in. Each menu has a function that displays the 330 respective menu 331*/ 332void 333 changing_states() { 334 335 switch (state) { 336 case MAIN: 337 display_current_time(); 338 339 display_feeding_times(); 340 display_portion(); 341 break; 342 343 344 case MENU_EDIT_FEED1: 345 display_set_feed_time1_menu(); 346 break; 347 348 349 case MENU_EDIT_FEED2: 350 display_set_feed_time2_menu(); 351 break; 352 353 354 case MENU_EDIT_TIME: 355 display_set_time_menu(); 356 break; 357 358 359 case MENU_EDIT_PORTION: 360 display_set_portion_menu (); 361 break; 362 363 364 case EDIT_FEED1_HOUR: 365 set_feed_time1(); 366 break; 367 368 case 369 EDIT_FEED1_MIN: 370 set_feed_time1(); 371 break; 372 373 case EDIT_FEED2_HOUR: 374 375 set_feed_time2(); 376 break; 377 378 case EDIT_FEED2_MIN: 379 set_feed_time2(); 380 381 break; 382 383 case EDIT_HOUR: 384 set_the_time(); 385 break; 386 387 388 case EDIT_MIN: 389 set_the_time(); 390 break; 391 392 case EDIT_PORTION: 393 394 set_the_portion(); 395 break; 396 } 397} 398//===================================================== 399/* 400 This is the transitional part of the state machine. This is 401 what allows you 402 to go from one menu to another and change values 403*/ 404void menu_transitions(int 405 input) { 406 407 switch (state) { 408 case MAIN: 409 if (input == btnENTER) 410 { 411 lcd.clear(); 412 state = MENU_EDIT_FEED1; 413 } 414 if 415 (input == btnBACK) 416 { 417 hall_sensor_fail = false; 418 } 419 420 break; 421 //---------------------------------------------------- 422 case 423 MENU_EDIT_FEED1: 424 if (input == btnBACK) { 425 lcd.clear(); 426 state 427 = MAIN; 428 } 429 else if (input == btnENTER) { 430 lcd.clear(); 431 432 state = EDIT_FEED1_HOUR; 433 } 434 else if (input == btnDOWN) 435 { 436 lcd.clear(); 437 state = MENU_EDIT_FEED2; 438 } 439 break; 440 441 //---------------------------------------------------- 442 case EDIT_FEED1_HOUR: 443 444 // Need this to prevent servo going off while setting time 445 servoOn 446 = false; 447 if (input == btnUP) { 448 feed_time1_hour++; 449 if 450 (feed_time1_hour > 23) { 451 feed_time1_hour = 0; 452 } 453 } 454 455 else if (input == btnDOWN) { 456 feed_time1_hour--; 457 if (feed_time1_hour 458 < 0) { 459 feed_time1_hour = 23; 460 } 461 } 462 else 463 if (input == btnBACK) { 464 lcd.clear(); 465 servoOn = true; 466 state 467 = MENU_EDIT_FEED1; 468 } 469 else if (input == btnENTER) { 470 state 471 = EDIT_FEED1_MIN; 472 } 473 break; 474 //---------------------------------------------------- 475 476 case EDIT_FEED1_MIN: 477 if (input == btnUP) { 478 feed_time1_min++; 479 480 if (feed_time1_min > 59) { 481 feed_time1_min = 0; 482 } 483 484 } 485 else if (input == btnDOWN) { 486 feed_time1_min--; 487 if 488 (feed_time1_min < 0) { 489 feed_time1_min = 59; 490 } 491 } 492 493 else if (input == btnBACK) { 494 state = EDIT_FEED1_HOUR; 495 } 496 497 else if (input == btnENTER) { 498 lcd.clear(); 499 lcd.setCursor(0, 500 0); 501 lcd.print( "*Settings Saved*"); 502 delay(1000); 503 lcd.clear(); 504 505 servoOn = true; 506 write_feeding_time1 (); 507 state = MAIN; 508 509 } 510 break; 511 //---------------------------------------------------- 512 513 case MENU_EDIT_FEED2: 514 if (input == btnUP) { 515 lcd.clear(); 516 517 state = MENU_EDIT_FEED1; 518 } 519 else if (input == btnENTER) 520 { 521 lcd.clear(); 522 state = EDIT_FEED2_HOUR; 523 } 524 else 525 if (input == btnDOWN) { 526 lcd.clear(); 527 state = MENU_EDIT_TIME; 528 529 } 530 break; 531 //---------------------------------------------------- 532 533 case EDIT_FEED2_HOUR: 534 servoOn = false; 535 if (input == btnUP) 536 { 537 feed_time2_hour++; 538 if (feed_time2_hour > 23) { 539 feed_time2_hour 540 = 0; 541 } 542 } 543 else if (input == btnDOWN) { 544 feed_time2_hour--; 545 546 if (feed_time2_hour < 0) { 547 feed_time2_hour = 23; 548 } 549 550 } 551 else if (input == btnBACK) { 552 lcd.clear(); 553 servoOn 554 = true; 555 state = MENU_EDIT_FEED2; 556 } 557 else if (input == 558 btnENTER) { 559 state = EDIT_FEED2_MIN; 560 } 561 break; 562 //---------------------------------------------------- 563 564 case EDIT_FEED2_MIN: 565 if (input == btnUP) { 566 feed_time2_min++; 567 568 if (feed_time2_min > 59) { 569 feed_time2_min = 0; 570 } 571 572 } 573 else if (input == btnDOWN) { 574 feed_time2_min--; 575 if 576 (feed_time2_min < 0) { 577 feed_time2_min = 59; 578 } 579 } 580 581 else if (input == btnBACK) { 582 state = EDIT_FEED2_HOUR; 583 } 584 585 else if (input == btnENTER) { 586 lcd.clear(); 587 lcd.setCursor(0, 588 0); 589 lcd.print( "*Settings Saved*"); 590 delay(1000); 591 lcd.clear(); 592 593 servoOn = true; 594 write_feeding_time2 (); 595 state = MAIN; 596 597 } 598 break; 599 //---------------------------------------------------- 600 601 case MENU_EDIT_TIME: 602 if (input == btnUP) { 603 lcd.clear(); 604 605 state = MENU_EDIT_FEED2; 606 } 607 else if (input == btnENTER) 608 { 609 lcd.clear(); 610 state = EDIT_HOUR; 611 } 612 else 613 if (input == btnDOWN) { 614 lcd.clear(); 615 state = MENU_EDIT_PORTION; 616 617 } 618 break; 619 //---------------------------------------------------- 620 621 case EDIT_HOUR: 622 if (input == btnUP) { 623 Hour++; 624 if 625 (Hour > 23) { 626 Hour = 0; 627 } 628 } 629 else if (input 630 == btnDOWN) { 631 Hour--; 632 if (Hour < 0) { 633 Hour = 634 23; 635 } 636 } 637 else if (input == btnBACK) { 638 lcd.clear(); 639 640 state = MENU_EDIT_TIME; 641 } 642 else if (input == btnENTER) 643 { 644 state = EDIT_MIN; 645 } 646 break; 647 //---------------------------------------------------- 648 649 case EDIT_MIN: 650 if (input == btnUP) { 651 Minute++; 652 if 653 (Minute > 59) { 654 Minute = 0; 655 } 656 } 657 else if 658 (input == btnDOWN) { 659 Minute--; 660 if (Minute < 0) { 661 Minute 662 = 59; 663 } 664 } 665 else if (input == btnBACK) { 666 state 667 = EDIT_HOUR; 668 } 669 else if (input == btnENTER) { 670 lcd.clear(); 671 672 lcd.setCursor(0, 0); 673 lcd.print( "*Settings Saved*"); 674 delay(1000); 675 676 lcd.clear(); 677 rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); 678 679 state = MAIN; 680 } 681 break; 682 //---------------------------------------------------- 683 684 case MENU_EDIT_PORTION: 685 if (input == btnUP) { 686 lcd.clear(); 687 688 state = MENU_EDIT_TIME; 689 } 690 else if (input == btnENTER) 691 { 692 lcd.clear(); 693 state = EDIT_PORTION; 694 } 695 break; 696 697 //---------------------------------------------------- 698 case EDIT_PORTION: 699 700 if (input == btnUP) { 701 portion++; 702 if (portion > 20) { 703 704 portion = 1; 705 } 706 } 707 else if (input == btnDOWN) 708 { 709 portion--; 710 if (portion < 1) { 711 portion = 20; 712 713 } 714 } 715 else if (input == btnBACK) { 716 lcd.clear(); 717 718 state = MENU_EDIT_PORTION; 719 } 720 else if (input == btnENTER) 721 { 722 lcd.clear(); 723 lcd.setCursor(0, 0); 724 lcd.print( 725 "*Settings Saved*"); 726 delay(1000); 727 lcd.clear(); 728 write_portion(); 729 730 state = MAIN; 731 } 732 break; 733 } 734} 735//===================================================== 736// 737 This function checks the feed time against the current time 738 739void check_feedtime 740 () 741{ 742 DateTime now = rtc.now(); 743 if (now.second() == 0) { 744 if ((now.hour() 745 == feed_time1_hour) && (now.minute() == feed_time1_min)) 746 { 747 feeding1_trigger 748 = true; 749 if (servoOn) 750 { 751 if (feeding1_complete == false) 752 753 { 754 lcd.clear(); 755 lcd.setCursor(3, 0); 756 lcd.print 757 ("Dispensing"); 758 lcd.setCursor(1, 1); 759 lcd.print("First 760 Feeding"); 761 startFeeding(); 762 } 763 } 764 } 765 else 766 if ((now.hour() == feed_time2_hour) && (now.minute () == feed_time2_min)) 767 { 768 769 feeding2_trigger = true; 770 if (servoOn) 771 { 772 if ( 773 feeding2_complete == false) 774 { 775 lcd.clear(); 776 lcd.setCursor(3, 777 0); 778 lcd.print ("Dispensing"); 779 lcd.setCursor(0, 1); 780 781 lcd.print("Second Feeding"); 782 startFeeding(); 783 } 784 785 } 786 } 787 } 788 // Midnight Reset 789 if ( (now.hour() == 0) && (now.minute() 790 == 0)) 791 { 792 feeding1_complete = false; 793 feeding2_complete = false; 794 795 EEPROM.write(4, feeding1_complete); 796 EEPROM.write(5, feeding2_complete); 797 798 } 799 /*If power outtage happens during a feed time, this checks to see if the 800 801 feed time has passed and if the feeding occurred. If not, it feeds. 802 */ 803 804 if ( (now.hour() >= feed_time1_hour) && (now.minute() > feed_time1_min)) 805 { 806 807 if ((feeding1_complete == 0) && (feeding1_trigger == 0)) 808 { 809 lcd.clear(); 810 811 lcd.setCursor(5, 0); 812 lcd.print ("Uh-Oh!"); 813 lcd.setCursor(2, 814 1); 815 lcd.print("Power Outage"); 816 startFeeding(); 817 } 818 819 } 820 if ( (now.hour() >= feed_time2_hour) && (now.minute() > feed_time2_min)) 821 822 { 823 if ((feeding2_complete == 0) && (feeding2_trigger == 0)) 824 { 825 826 lcd.clear(); 827 lcd.setCursor(5, 0); 828 lcd.print ("Uh-Oh!"); 829 830 lcd.setCursor(2, 1); 831 lcd.print("Power Outage"); 832 startFeeding(); 833 834 } 835 } 836} 837 838//===================================================== 839// 840 Displays the set portion menu option 841void display_set_portion_menu () { 842 lcd.setCursor(2, 843 0); 844 lcd.print("Menu Options"); 845 lcd.setCursor(0, 1); 846 lcd.print("Set 847 the Portion"); 848} 849//===================================================== 850// 851 Displays the menu where you change the current time 852void set_the_time () 853{ 854 855 lcd.setCursor(2, 0); 856 lcd.print("Set the Time"); 857 switch (state) 858 859 { 860 //---------------------------------------------------- 861 case EDIT_HOUR: 862 863 864 if (blink_state == 0) 865 { 866 lcd.setCursor(5, 1); 867 add_leading_zero(Hour); 868 869 } 870 else 871 { 872 lcd.setCursor(5, 1); 873 lcd.print(" 874 "); 875 } 876 lcd.print(":"); 877 add_leading_zero(Minute); 878 879 break; 880 //---------------------------------------------------- 881 case 882 EDIT_MIN: 883 lcd.setCursor(5, 1); 884 add_leading_zero(Hour); 885 lcd.print(":"); 886 887 if (blink_state == 0) 888 { 889 lcd.setCursor(8, 1); 890 add_leading_zero(Minute); 891 892 } 893 else 894 { 895 lcd.setCursor(8, 1); 896 lcd.print(" 897 "); 898 } 899 break; 900 } 901 blinkFunction(); 902} 903//===================================================== 904// 905 Displays the menu where you change the feeding portion 906void set_the_portion () 907{ 908 909 lcd.setCursor (0, 0); 910 lcd.print("Set the Portion"); 911 switch (state) 912 913 { 914 case EDIT_PORTION: 915 if (blink_state == 0) 916 { 917 lcd.setCursor(7, 918 1); 919 add_leading_zero(portion); 920 } 921 else 922 { 923 924 lcd.setCursor(7, 1); 925 lcd.print(" "); 926 } 927 } 928 929 blinkFunction(); 930} 931//===================================================== 932//Displays 933 the menu option for setting the time 934void display_set_time_menu () { 935 lcd.setCursor(2, 936 0); 937 lcd.print("Menu Options"); 938 lcd.setCursor(2, 1); 939 lcd.print("Set 940 the Time"); 941} 942//===================================================== 943// 944 Displays the menu where you change the second feeding time 945void set_feed_time2 946 () 947{ 948 lcd.setCursor(0, 0); 949 lcd.print("Set Feed Time 2"); 950 switch 951 (state) 952 { 953 //---------------------------------------------------- 954 955 case EDIT_FEED2_HOUR: 956 957 if (blink_state == 0) 958 { 959 lcd.setCursor(5, 960 1); 961 add_leading_zero(feed_time2_hour); 962 } 963 else 964 { 965 966 lcd.setCursor(5, 1); 967 lcd.print(" "); 968 } 969 lcd.print(":"); 970 971 add_leading_zero(feed_time2_min); 972 break; 973 //---------------------------------------------------- 974 975 case EDIT_FEED2_MIN: 976 lcd.setCursor(5, 1); 977 add_leading_zero(feed_time2_hour); 978 979 lcd.print(":"); 980 if (blink_state == 0) 981 { 982 lcd.setCursor(8, 983 1); 984 add_leading_zero(feed_time2_min); 985 } 986 else 987 { 988 989 lcd.setCursor(8, 1); 990 lcd.print(" "); 991 } 992 break; 993 994 } 995 blinkFunction(); 996} 997//===================================================== 998// 999 Displays the menu where you change the first feeding time 1000void set_feed_time1 1001 () 1002{ 1003 lcd.setCursor(0, 0); 1004 lcd.print("Set Feed Time 1"); 1005 switch 1006 (state) 1007 { 1008 //---------------------------------------------------- 1009 1010 case EDIT_FEED1_HOUR: 1011 1012 if (blink_state == 0) 1013 { 1014 lcd.setCursor(5, 1015 1); 1016 add_leading_zero(feed_time1_hour); 1017 } 1018 else 1019 { 1020 1021 lcd.setCursor(5, 1); 1022 lcd.print(" "); 1023 } 1024 lcd.print(":"); 1025 1026 add_leading_zero(feed_time1_min); 1027 break; 1028 //---------------------------------------------------- 1029 1030 case EDIT_FEED1_MIN: 1031 lcd.setCursor(5, 1); 1032 add_leading_zero(feed_time1_hour); 1033 1034 lcd.print(":"); 1035 if (blink_state == 0) 1036 { 1037 lcd.setCursor(8, 1038 1); 1039 add_leading_zero(feed_time1_min); 1040 } 1041 else 1042 { 1043 1044 lcd.setCursor(8, 1); 1045 lcd.print(" "); 1046 } 1047 break; 1048 1049 } 1050 blinkFunction(); 1051} 1052//===================================================== 1053// 1054 Adds a leading zero to single digit numbers 1055void add_leading_zero (int num) { 1056 1057 if (num < 10) { 1058 lcd.print("0"); 1059 } 1060 lcd.print(num); 1061} 1062//===================================================== 1063/* 1064 Displays the feeding time on the main menu as well as the 1065 check mark for visual 1066 comfirmation of a completed feeding 1067*/ 1068void display_feeding_times () { 1069 1070 //Displaying first feed time 1071 lcd.setCursor(0, 0); 1072 lcd.print ("F1:"); 1073 1074 add_leading_zero(feed_time1_hour); 1075 lcd.print(":"); 1076 add_leading_zero(feed_time1_min); 1077 1078 lcd.print(" "); 1079 if (feeding1_complete == true) 1080 { 1081 lcd.write(0); 1082 1083 } 1084 else 1085 { 1086 lcd.print(" "); 1087 } 1088 //Displaying second feed 1089 time 1090 lcd.setCursor(0, 1); 1091 lcd.print("F2:"); 1092 add_leading_zero(feed_time2_hour); 1093 1094 lcd.print(":"); 1095 add_leading_zero(feed_time2_min); 1096 lcd.print(" "); 1097 1098 if (feeding2_complete == true) 1099 { 1100 lcd.write(0); 1101 } 1102 else 1103 1104 { 1105 lcd.print(" "); 1106 } 1107} 1108//===================================================== 1109// 1110 Displays the current time in the main menu 1111void display_current_time () { 1112 1113 DateTime now = rtc.now(); 1114 lcd.setCursor(11, 0); 1115 add_leading_zero(now.hour()); 1116 1117 lcd.print(":"); 1118 add_leading_zero(now.minute()); 1119} 1120//===================================================== 1121// 1122 Displays the menu option for setting the first feed time 1123void display_set_feed_time1_menu 1124 () { 1125 lcd.setCursor(2, 0); 1126 lcd.print("Menu Options"); 1127 lcd.setCursor(0, 1128 1); 1129 lcd.print("Set Feed Time 1"); 1130} 1131//===================================================== 1132// 1133 Displays the meny option for setting the second feed time 1134void display_set_feed_time2_menu 1135 () { 1136 lcd.setCursor(2, 0); 1137 lcd.print("Menu Options"); 1138 lcd.setCursor(0, 1139 1); 1140 lcd.print("Set Feed Time 2"); 1141} 1142//===================================================== 1143// 1144 Displays the feeding portion in the main menu 1145void display_portion () 1146{ 1147 1148 lcd.setCursor (12, 1); 1149 lcd.print("P:"); 1150 add_leading_zero(portion); 1151} 1152//===================================================== 1153// 1154 Starts the feeding process. 1155 1156void startFeeding() 1157{ 1158 // attach the servo 1159 to the pin 1160 myservo.attach(SERVO_PIN); 1161 count = 1; 1162 hallSensorTime = 1163 millis(); 1164 // loop so that the servo runs until desired portion is reached 1165 1166 while (count <= portion) 1167 { 1168 servoStart(); 1169 if (hallSensorActivated 1170 == true) 1171 { 1172 // digitalWrite(LED_PIN,HIGH); 1173 count = 1174 count + 1; 1175 //resetting for next interrupt 1176 hallSensorTime = millis(); 1177 1178 hallSensorActivated = false; 1179 digitalWrite(HALL_LED_PIN, LOW); 1180 1181 } 1182 /* Moved the servo clockwise a bit to dislodge food stuck in the 1183 1184 dispensing mechanism 1185 */ 1186 if (millis() - hallSensorTime > rotationTime) 1187 1188 { 1189 hall_sensor_fail = true; 1190 Serial.println("I'm in Jiggle"); 1191 1192 jiggle(); 1193 } 1194 } 1195 // Keeps track of which feeding just happened 1196 and writes it to EEPROM 1197 if ((feeding1_complete == false) && (feeding2_complete 1198 == false)) 1199 { 1200 feeding1_complete = true; 1201 EEPROM.write(4, feeding1_complete); 1202 1203 } 1204 else if ((feeding1_complete == true) && (feeding2_complete == false)) 1205 1206 { 1207 feeding2_complete = true; 1208 EEPROM.write(5, feeding2_complete); 1209 1210 } 1211 servoStop(); 1212 digitalWrite(HALL_LED_PIN, LOW); 1213 /* Detaches the 1214 servo from the pin so that it is no longer recieving a signal. 1215 You may have 1216 to add a delay before this so the sensor stops when a magnet is over 1217 the 1218 hall sensor. There was significant momentum left in my system that I did not need 1219 it 1220 */ 1221 myservo.detach(); 1222 lcd.clear(); 1223 delay_currentMillis = millis(); 1224 1225 while (millis() - delay_currentMillis <= delay_interval) 1226 { 1227 lcd.setCursor(2, 1228 0); 1229 lcd.print ("Feeding Done"); 1230 } 1231 lcd.clear(); 1232} 1233//===================================================== 1234 1235void 1236 servoStart() 1237{ 1238 myservo.write(180); 1239} 1240//===================================================== 1241 1242void 1243 servoStop() 1244{ 1245 // this value will vary, you have to find it through trial 1246 and error 1247 myservo.write(94); 1248} 1249//===================================================== 1250// 1251 "jiggles" the servo in case food gets stuck 1252 1253void jiggle() 1254{ 1255 myservo.write(80); 1256 1257 delay(30); 1258 myservo.write(93); 1259 delay(30); 1260 myservo.write(180); 1261} 1262//===================================================== 1263// 1264 Writes the hour and minute valies set for 1st feeding to the EEPROM 1265 1266void 1267 write_feeding_time1 () 1268{ 1269 EEPROM.write(0, feed_time1_hour); 1270 EEPROM.write(1, 1271 feed_time1_min); 1272} 1273//===================================================== 1274// 1275 Writes the hour and minute values set for 2nd feeding to the EEPROM 1276 1277void 1278 write_feeding_time2 () { 1279 EEPROM.write(2, feed_time2_hour); 1280 EEPROM.write(3, 1281 feed_time2_min); 1282} 1283//===================================================== 1284// 1285 Writes portion value set to the EEPROM 1286 1287void write_portion () 1288{ 1289 EEPROM.write(6, 1290 portion); 1291} 1292//===================================================== 1293// 1294 Reads the hour and minute values from 1st feed time from EEPROM 1295 1296void get_feed_time1 1297 () 1298{ 1299 feed_time1_hour = EEPROM.read(0); 1300 if (feed_time1_hour > 23) feed_time1_hour 1301 = 0; 1302 feed_time1_min = EEPROM.read(1); 1303 if (feed_time1_min > 59) feed_time1_min 1304 = 0; 1305 1306} 1307//===================================================== 1308// Reads 1309 the hour and minute values from 2nd feed time from EEPROM 1310 1311void get_feed_time2 1312 () 1313{ 1314 feed_time2_hour = EEPROM.read(2); 1315 if (feed_time2_hour > 23) feed_time2_hour 1316 = 0; 1317 feed_time2_min = EEPROM.read(3); 1318 if (feed_time2_min > 59) feed_time2_min 1319 = 0; 1320} 1321//===================================================== 1322// Reads 1323 portion set value from EEPROM 1324 1325void get_portion () 1326{ 1327 portion = EEPROM.read(6); 1328} 1329//===================================================== 1330// 1331 Reads boolean value of whether or not feedings have occured from EEPROM 1332 1333void 1334 get_completed_feedings() 1335{ 1336 feeding1_complete = EEPROM.read(4); 1337 feeding2_complete 1338 = EEPROM.read(5); 1339} 1340//===================================================== 1341/* 1342 Checks to see if the hall sensor has failed to read a magnet and 1343 blinks the 1344 red LED 1345*/ 1346 1347void check_hall_sensor () { 1348 if (hall_sensor_fail == true) 1349 1350 { 1351 if (blink_state == 0) 1352 { 1353 digitalWrite(HALL_LED_PIN, HIGH); 1354 1355 } 1356 else 1357 { 1358 digitalWrite(HALL_LED_PIN, LOW); 1359 } 1360 1361 blinkFunction(); 1362 } 1363 else 1364 { 1365 digitalWrite(HALL_LED_PIN, LOW); 1366 1367 hall_sensor_fail = false; 1368 } 1369} 1370//===================================================== 1371// 1372 Checks if you push the manual feed button 1373 1374void manual_feed_check () { 1375 1376 if (manualFeed == true) 1377 { 1378 lcd.clear(); 1379 lcd.setCursor(0, 0); 1380 1381 lcd.print(" Manual Feeding"); 1382 startFeeding(); 1383 manualFeed = false; 1384 1385 } 1386} 1387//===================================================== 1388// checks 1389 to see if RTC is running 1390 1391void check_rtc () { 1392 if (!rtc.isrunning()) 1393 1394 { 1395 led_blink(); 1396 } 1397} 1398//===================================================== 1399/* 1400 Blinks the red led when RTC has failed. Note: the led 1401 will be blinking at 1402 a different rate than when the hall 1403 sensor fails 1404*/ 1405 1406void led_blink() 1407{ 1408 1409 unsigned long led_currentMillis = millis(); 1410 if (led_currentMillis - led_previousMillis 1411 >= interval_delay) 1412 { 1413 led_previousMillis = led_currentMillis; 1414 if 1415 (ledState == LOW) 1416 { 1417 ledState = HIGH; 1418 } 1419 else 1420 { 1421 1422 ledState = LOW; 1423 } 1424 digitalWrite(HALL_LED_PIN, ledState); 1425 1426 } 1427} 1428//===================================================== 1429// Creates 1430 the blinking effect when changing values 1431 1432void blinkFunction() 1433{ 1434 blink_currentMillis 1435 = millis(); 1436 1437 if (blink_currentMillis - blink_previousMillis > blink_interval) 1438 1439 { 1440 blink_previousMillis = blink_currentMillis; 1441 blink_state = !blink_state; 1442 1443 } 1444} 1445//===================================================== 1446
Link to Code on my Github
Downloadable files
Feeder Shcematic
This is the circuit schematic. You can change it up if you need to. If you do, just remember to make the same adjustments in the code.
Feeder Shcematic
Documentation
Fusion 360 and STL files on my Github
Here are all the fusion 360 files in case you want to customize them for different component sizes. I have also provided the STL files. The only model not on there is the tube for the hall sensor. That should be pretty easy to model and print.
https://github.com/russo08/Pet-feeder.git
Fusion 360 and STL files on my Github
Here are all the fusion 360 files in case you want to customize them for different component sizes. I have also provided the STL files. The only model not on there is the tube for the hall sensor. That should be pretty easy to model and print.
https://github.com/russo08/Pet-feeder.git
Comments
Only logged in users can leave comments
Anonymous user
2 years ago
instead using arduino nano R3, can I use arduino uno R3 ?
Anonymous user
2 years ago
hey can i know how does your female dc power jack adapter looks like ?
Anonymous user
2 years ago
Hello ! When i try to compile your code i get an error message saying: "In file included from F:\\Users\\user\\Documents\\Arduino\\Programmata_Arduino\\Pet-feeder\\feeder_final\\feeder_final.ino:21:0: F:\\Users\\user\\Documents\\Arduino\\libraries\\LiquidMenu\\src/LiquidMenu.h:54:123: note: #pragma message: LiquidMenu: Selected 'LiquidCrystal' (parallel) library. Edit 'LiquidMenu_config.h' file to change it. # pragma message ("LiquidMenu: Selected 'LiquidCrystal' (parallel) library. Edit 'LiquidMenu_config.h' file to change it.")"
Anonymous user
2 years ago
kindly add lib
russo08
2 years ago
I would say delete the libraries and reinstall them. Did you install them in the right place? Try some of the examples that come with the library and see if you are still having the same issue.
Anonymous user
2 years ago
Everything is connected, the display shows the first feeding ready but does not respond to any button. Any advice?
russo08
2 years ago
Very sorry for the late reply. Were you able to get it working? What are you attempting to do with the buttons? I would say use the serial monitor to check and make sure that your button pushes are being registered and that they are only being registered once.
elcabochon2
2 years ago
In fact it is display show" Dispensing First Feeding"
elcabochon2
2 years ago
Hi, I do have the same problem. I looked in the serial monitor and it is written: ''I'm in Jiggle". No signal from any bottom if I pushed.
tasior
2 years ago
Hi. I have the same problem. How you fix it? I hope someone answer me after all this time :D
Anonymous user
3 years ago
instead using arduino nano R3, can I use arduino uno R3 ?
Anonymous user
3 years ago
hey can i know how does your female dc power jack adapter looks like ?
Anonymous user
4 years ago
Hello ! I really like your project. For my final paper I have to make very similar project. I have to set up web server on raspberry pi zero w. When I go to server there has to be button (feed the pet) and pic taken with camera connected to rpi zero w. When I press the button the food should fall out of the dispenser. Do you have any advice? How to create web server on rpi. Should I connect rpi and servo directly or via arduino? Ty!!
hordubal
4 years ago
Everything is connected, the display shows the first feeding ready but does not respond to any button. Any advice?
elcabochon2
2 years ago
In fact it is display show" Dispensing First Feeding"
elcabochon2
2 years ago
Hi, I do have the same problem. I looked in the serial monitor and it is written: ''I'm in Jiggle". No signal from any bottom if I pushed.
russo08
2 years ago
Very sorry for the late reply. Were you able to get it working? What are you attempting to do with the buttons? I would say use the serial monitor to check and make sure that your button pushes are being registered and that they are only being registered once.
Anonymous user
4 years ago
Hello ! When i try to compile your code i get an error message saying: "In file included from F:\\Users\\user\\Documents\\Arduino\\Programmata_Arduino\\Pet-feeder\\feeder_final\\feeder_final.ino:21:0: F:\\Users\\user\\Documents\\Arduino\\libraries\\LiquidMenu\\src/LiquidMenu.h:54:123: note: #pragma message: LiquidMenu: Selected 'LiquidCrystal' (parallel) library. Edit 'LiquidMenu_config.h' file to change it. # pragma message ("LiquidMenu: Selected 'LiquidCrystal' (parallel) library. Edit 'LiquidMenu_config.h' file to change it.")"
Anonymous user
2 years ago
kindly add lib
russo08
2 years ago
I would say delete the libraries and reinstall them. Did you install them in the right place? Try some of the examples that come with the library and see if you are still having the same issue.
Anonymous user
4 years ago
This is one Cool Project. Way to look out for your furry member of the family. I had been thinking of making a fish feeder; the only ones I could find were either for a pond (too big, too expensive) or too small (made for a tank less than 100 gallons). Thank you soo much for sharing. I don't know what it will look like but however it turns out I will be sure to share. thanks again, you are a great inspiration.
russo08
2 years ago
Thanks so much for the kind words! Good luck with your project!
Anonymous user
2 years ago
Hello ! I really like your project. For my final paper I have to make very similar project. I have to set up web server on raspberry pi zero w. When I go to server there has to be button (feed the pet) and pic taken with camera connected to rpi zero w. When I press the button the food should fall out of the dispenser. Do you have any advice? How to create web server on rpi. Should I connect rpi and servo directly or via arduino? Ty!!