带 3D 打印部件的宠物喂食器
组件和用品
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 6 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 |
必要的工具和机器
| ||||
| ||||
|
关于这个项目
宠物喂食器背后的故事
我一直在玩微控制器一段时间,并想尝试从教程中扩展出来。在 Covid-19 之前,我真的没有固定的时间表并且工作时间很长。我的狗的喂食计划真的开始受到影响,并导致一些明显的不适。我所在的地区在飓风季节也容易发生洪水。不幸的是,有几次我无法回家喂我的老女儿。我需要找到一个解决方案,这样,如果我不能回家,我的狗就不会挨饿。与其花 30-40 美元买一个,为什么不花 100 美元以上建一个?只是在开玩笑。
喂食器!
我的喂狗器使用 Arduino 微控制器。我需要一些大多数其他版本的狗喂食器没有的关键功能。即停电恢复喂料解决方案,解决食物卡在分配机构中的问题。我所在的地区也遭受随机停电的困扰。停电通常不会持续很长时间,所以我认为不需要电源冗余。我还想要一些易于拆卸和清洁的东西。清洗 3D 打印部件时要小心,您可以使用温水,而不是热水,除非您打算使用 ABS 或 PET-G 进行打印。
完整的功能列表如下:
- 每天两次喂食
- 使用实时时钟准确计时
- 实时时钟的手动时间更改
- 手动喂食选项
- 霍尔传感器和实时时钟故障的 LED 指示
- 主屏幕上的进料时间、当前时间和进料完成概览
- 易于导航的菜单
- 断电恢复供电(恢复供电时会供电)
- 送料时间和完成情况安全地存储在 EEPROM 中
- 如果食物在分配时卡住,伺服会“摇晃”
演示
演示视频即将推出!
您还需要什么?
- 4x M3-0.5 x 16 mm 螺丝(编码器轮)
- 4x M3-0.5 x 10 mm 螺丝(伺服支架)
- 12 个 M3 六角螺母
- 4 个 M3 锁紧螺母
- 16x 1-1/2 英寸(38 毫米)多用途木螺钉
- 木胶
- 6 个 0.315 x 0.118 英寸(8 x 3 毫米)磁铁
- 用于支腿和伺服支架的 1-1/2 x 1-1/2 英寸(3.8 x 3.8 厘米)木材
- 1/2 x 6 英寸(1.27 x 15.24 厘米)木材用于背板和底座
3D 打印部件
我最近获得了一台 3D 打印机,我想,还有什么比打印宠物喂食器的定制部件更好的学习和使用方法呢?打印的所有内容都是 PLA+,并使用 Ender 3 Pro 打印,使用 Cura 作为切片机。所有部分的 STL 都可以在 Github 项目页面上找到。往下链接。阅读 README 文件以了解打印说明和切片器设置。
房屋
外壳的设计考虑到了方便和简单。无需螺钉或螺母。只需将组件放入和取出即可。每个插件都有 4 个固定组件的卡舌。
图> 图> 图>虽然我一开始把 LCD 屏幕插槽弄乱了,但我还是回去在 Fusion 360 中修复了模型。我懒得回去重新打印它。我使用了一些额外的 M3 0.5 x 6mm 螺钉来固定它。 LCD 插槽的每个角都有四个垫片,其中有孔,以便您可以在需要时固定屏幕。不幸的是,在我把所有东西都放进去之前,我从来没有拍过外壳盖的照片。
编码器轮附件
车轮编码轮有两个用途:
- 为 Arduino 提供来自伺服的位置反馈
- 将橡胶桨连接到伺服器
一个进料部分等于轮子的 1/6th(60 度)圈。使用食物秤来测量您的宠物每次喂食的食物量,然后调整份数,直到获得满足该数量的范围。我相信一次喂我大约是 173 克食物。 17 份的份量给了我每次喂食 170-177 克的范围。这一切都取决于你粗磨的大小。确保喇叭位于 8 个 M3 六角螺母之间。
图> 图>
伺服支架和霍尔传感器支架
这个定制的伺服支架还固定霍尔传感器并将伺服安装在一块 1-1/2 x 1-1/2 英寸(3.8 x 3.8 厘米)的木头上。木材的长度将取决于伺服器所在的位置(稍后会详细介绍)。安装座有很大的误差空间,所以不要太担心长度是否完美。
图> 图> 图> 图>
漏斗、食物斜槽和斜槽导轨
这构成了食品配送系统。食物从分配器通过漏斗下降到斜槽并进入食物碗。不幸的是,我没有在安装之前拍下滑槽导轨的照片。
图> 图> 图>
框架
*免责声明* 组装过程的每一步我都没有拍照。这些图片中的一些会跳过步骤,但我仍然会带您一步一步地进行框架组装。
确保为您使用的任何螺钉预先钻孔。您想劈开木头!
这是我从亚马逊购买的谷物分配器。如果您有多个宠物并且需要多个喂食器,那么有些有多个分配器,我只需要一个。品牌是 Honey Can Do,但我相信任何品牌都可以。
我开始的第一件事是从连接到分配器上的桨轮的杆上取下旋钮。我用手锯卸下旋钮。如果您碰巧有电动工具,则可以使用电动工具。不要在银色部分的末端进一步向下切割杆。在旋钮的底部切开。
图> 图>锯掉旋钮后,通过切割上图最后一张图中的 3 个支撑来去除剩余的材料。这花了相当长的时间。我想如果你有合适的电动工具它会更快。卸下这三个支架后,杆上剩下的剩余部分应该会沾上少许肘部油脂。您必须打磨最靠近旋钮的杆部分,以便它可以很好地装入编码器轮中。
图> 图>接下来,我们开始构建框架的基础。我把 1/2 x 6 英寸(1.27 x 15.24 厘米)的木头切成两块 8 英寸(20.32 厘米)的块。这将构成馈线框架的底座和背面。涂上一些木胶,然后使用 2 个多用途钉将它们连接成 L 形。您将需要添加几个直角支撑以加强后面板和底部面板的连接。使用 4 个多用途螺丝和一些木胶将它们固定在背板和底板上。我没有这一步的图片,但你可以在下面的图片中看到它们。
从这里开始,您必须将分配器支架切割成大约 4.5 英寸(11.43 厘米)的高度。它不必是完美的。尽量靠近它。漏斗允许一些摆动空间。将支架剪成合适的尺寸后,将其靠在背面并确保它平放在底座上。将其放置到位后,用铅笔或钢笔标记容器分配端的中心位置。然后,您需要在底板上钻一个 2 英寸(5.08 厘米)的孔。测量两次并用这个切割一次至关重要。那将是上图中看到的孔。
在底板上钻孔后,我们就可以将分配器支架连接到后面板了。您要做的是将支架靠在后面板上(如下面的第一张图片所示)。分配器支架环下方有两个小房间区域。这是您想要孔的位置(见下图)。使用铅笔或钢笔在后面板上标记两个孔的高度。您希望它们尽可能靠近分配器支架的中心。有两个螺钉将支架的顶环连接到您切断的下部。钻孔时小心不要撞到那些。再次,记住测量两次并钻一次。或者两次,在这种情况下。
图> 图>现在我们已经钻孔了,我们可以使用 5/16 英寸六角螺栓、5/16 英寸六角螺母和 5/16 英寸垫圈将支架连接到后面板。您想继续将螺栓推过,确保在将它们推过之前将垫圈放在螺栓的六角端。从另一侧出来后,将另一组垫圈放在螺纹侧,然后开始手动拧紧六角螺母。这会有点棘手。用手拧紧螺母后,您必须使用套筒来固定螺母并进一步拧紧。只有大约 1/4 圈左右。注意不要拧得太紧。
图>现在支架已安全固定,我们现在可以添加伺服器将要放置的 1/2 x 1/2 英寸木块。这的长度将取决于您的伺服器所在的位置。通过将伺服器连接到编码器轮上的喇叭,将编码器轮连接到塑料容器内的橡胶桨来组装进给机构。将容器放入顶环中,并测量伺服器在底板上的位置。不要担心它是完美的。伺服支架允许相当多的摆动空间。使用木胶和一个多用途螺丝固定木架。
图> 图>下一步是将支腿连接到进纸器的底板上。腿的长度将取决于宠物食碗的高度。我的狗把她的食物碗放在一个凸起的架子上;因此,我需要我的喂食器坐得很高。使用 4 个多用途螺钉和一些木胶将它们固定到位。我确实建议在两条前腿和两条后腿之间放置一个横梁,并在横梁之间放置另一个横梁,如下所示,以增加稳定性。总共使用 6 个多用途螺丝和一些木胶将这些碎片固定到腿上。
接下来的步骤是:
- 将漏斗插入我们在底板上钻的孔中
- 将舵机连接到舵机支架
- 将支架固定到木架上
使用 4 个 M3 x 10 毫米螺钉和 4 个 M3 六角螺母将伺服器连接到支架上。一旦舵机固定好,我们就可以将支架固定到木制舵机支架上。使用两个多用途螺钉将支架轻轻固定到木支架上。不要拧得过紧,否则会损坏支架。确保按该顺序执行这些步骤。漏斗会稍微升高伺服器,它非常贴合塑料容器的末端,因此如果塑料容器已经放在顶环支架上,就不可能戴上。
图> 图> 图>最后的步骤是连接滑槽的滑入式支架和滑道本身。您希望将支架稍微放在底板孔的后面。您希望它尽可能地向前移动,以便滑轨能够避开进纸器的框架。使用两个多用途螺钉将支架固定到框架底板的底部。最好使用支架中的滑轨来执行此操作,因为支架有一定的弯曲度,并且您需要在滑轨和支架之间非常牢固地配合。
图>
电子产品
不幸的是,我从来没有拍过焊接过程的照片。虽然没有太多。只需将每个组件焊接到相应的引脚上,您就可以开始使用了。如果您想改用 pin header,也可以这样做。 Arduino 插槽下方和上方有足够的间隙以容纳引脚和连接器。我绝对建议在将所有组件设置在各自的插槽中之前将所有内容焊接在一起。
我通过未调节的外部电源引脚(引脚 30)为我的 Arduino 供电。这需要 7-20 伏之间的输入电压,因为该电压是通过 Arduino 的板载稳压器馈送的。如果您希望通过 USB 为其供电,您只需确保为它提供 5 伏电压,而不是 7-20 伏电压。
确保在霍尔传感器上的 Vcc 和 Signal 引脚之间焊接 10k Ohm 电阻器。否则,您将无法阅读。另外,不要忘记将所有组件连接到公共地。我犯了一个错误,错过了一个理由,我的系统可以工作一段时间,但霍尔传感器最终会开始出现故障。这对我的狗来说真是美好的一天。
我最终为霍尔传感器和伺服连接制作了自定义连接器。我将电线焊接到公针头上。它们挂在外壳底部。对于霍尔传感器,我通过切割、剥皮和焊接我周围的几根杜邦连接器线制作了一个定制的母适配器。
对于 Vcc 和接地轨,我将电源轨部分从我躺在周围的一些额外的永久原型面包板上切掉了。你身边的任何类似的东西都可以使用。在将导轨放入其插槽之前,只需仔细检查您的连接。这是进入后最难摆脱的事情。我也以艰难的方式学到了这一点。
图> 图> 图> 图> 图>而已!希望你们能像我一样在构建它时玩得开心。一开始看起来很有挑战性,但你越深入它就越容易。如果您对任何事情有任何疑问,请告诉我。快乐修修补补!
代码
- 馈线代码
Feeder 代码C/C++
// 宠物喂食器的最终版本/* 特点: - 易于导航的菜单 - 主屏幕上的喂食时间、当前时间、喂食完成和喂食部分概览 - 使用霍尔传感器进行反馈的可控部分 - 准确与 DS1307 芯片保持时间 - 可以手动更改 DS1307 芯片中的设定时间 - 每天两次进料 - 手动进料选项 - 断电时重新开始进料 - 霍尔传感器和实时时钟的 LED 指示 - 进料时间和完成情况安全地存储在 EEPROM 中- 食物卡住时伺服“抖动”*/#include#include #include #include #include # include #include // 创建伺服对象来控制您的servoServo myservo;// 分配我所有的输入和I/O 引脚#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#definehallPin 2#define HALL_LE D_PIN 7#define SERVO_PIN 6//定义所有Button,与JC_Button库一起使用Button enterBtn(ENTER_BUTTON_PIN);Button upBtn(UP_BUTTON_PIN);Button downBtn(DOWN_BUTTON_PIN);Button backBtn(BACK_BUTTON_PIN);//ButtonmanualBtn_PIN); LCD I2C和RTCLiquidCrystal_I2C lcd(0x27, 16, 2);RTC_DS1307 rtc;//整个代码中用到的变量unsigned long hallSensorTime;unsigned long rotationTime =400;unsigned long led_previousMillis =0;const long interval_delay0; unsigned long time_delay =1000 int ledState =LOW;boolean manualFeed =false;boolean hall_sensor_fail =false;unsigned longblink_previousMillis =0;unsigned longblink_currentMillis =0;unsigned longblink_interval =500;unsigned long delay_currentMillis =0;unsigned long delay_previousMillis =0;booleanblink_state =false int count;boolean feed1_complete =false;boolean feed2_complete =false;boolean feed1_trigger =false;boolean feed2_trigger =false;booleanservoOn =tru e;//霍尔传感器中断volatile boolean hallSensorActivated =false;volatile int isr_count =1;void hallActiveISR(){hallSensorActivated =true;数字写入(HALL_LED_PIN,高); isr_count =isr_count + 1;}/* 我在这里使用枚举来更好地跟踪正在按下的按钮,而不是将每个按钮设置为整数值。*/enum { btnENTER, btnUP, btnDOWN, btnBACK,}; /* 状态机的状态。与 enum 类型相同。它可以更轻松地跟踪您所在或想要访问的菜单,而不是为每个菜单提供一个整数值*/enum STATES { MAIN, MENU_EDIT_FEED1, MENU_EDIT_FEED2, MENU_EDIT_TIME, MENU_EDIT_PORTION, EDIT_FEED1_HOUR, EDIT_FEED1_FEED2 , EDIT_MIN, EDIT_PORTION};//保持状态机的状态STATES state;//用户输入变量int Hour;int Minute;int part;int feed_time1_hour;int feed_time1_min;int feed_time2_hour;int feed_time2_min;int userInput;//特殊字符校验markbyte check_Char[8] ={ B00000, B00000, B00001, B00011, B10110, B11100, B11000, B00000};//====================设置===========================void setup() { Wire.begin(); Serial.begin(9600);液晶显示器.init();液晶背光(); lcd.createChar(0, check_Char); if (!rtc.begin()) { Serial.println("找不到 RTC!"); } // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); //按钮enterBtn.begin(); upBtn.begin(); downBtn.begin(); backBtn.begin(); manualFeedBtn.begin(); //设置状态机的初始状态 state =MAIN; //设置输入和输出 pinMode(POWER_LED_PIN, OUTPUT); pinMode(HALL_LED_PIN,输出); pinMode(hallPin, INPUT); /* 将中断附加到连接到霍尔传感器的引脚。我使用的霍尔传感器设置为高电平,遇到磁铁时变为低电平。这就是为什么它设置为 FALLING */ attachInterrupt (digitalPinToInterrupt(hallPin), hallActiveISR, FALLING); // LED 的默认状态 digitalWrite(POWER_LED_PIN, HIGH);数字写入(HALL_LED_PIN,低); /* 这些函数在启动时从 EEPROM 中获取存储的进料时间、完成的进料和份量。我这样做是因为我住的地方随机停电。 */ get_feed_time1(); get_feed_time2(); get_completed_feedings(); get_partion();}//======================循环==========================void loop() { changes_states(); check_buttons(); check_feedtime(); check_rtc(); manual_feed_check();} // ===============中的作用=======================/ *用途JC_Button 库不断检查按钮是否被按下。根据按下的按钮,它设置变量 userInput 用于功能 menu_transitions*/void check_buttons () { enterBtn.read(); upBtn.read(); downBtn.read(); backBtn.read(); manualFeedBtn.read(); if (enterBtn.wasPressed()) { Serial.println ("你按下了 Enter!");用户输入 =btnENTER;菜单转换(用户输入); } if (upBtn.wasPressed()) { Serial.println ("你按下了!");用户输入 =btnUP;菜单转换(用户输入); } if (downBtn.wasPressed()) { Serial.println ("你按下了!");用户输入 =btnDOWN;菜单转换(用户输入); } if (backBtn.wasPressed()) { Serial.println ("你回压了!");用户输入 =btnBACK;菜单转换(用户输入); } if (manualFeedBtn.wasPressed()) { Serial.println ("你正在手动喂食!"); manualFeed =真; }}//======================================================/* 这个函数决定显示什么,取决于你所处的菜单或“状态”。每个菜单都有一个函数,显示各自的菜单*/void changed_states() { switch (state) {案例主要:display_current_time(); display_feeding_times();显示部分();休息;案例 MENU_EDIT_FEED1:display_set_feed_time1_menu();休息;案例 MENU_EDIT_FEED2:display_set_feed_time2_menu();休息;案例 MENU_EDIT_TIME:display_set_time_menu();休息; case MENU_EDIT_PORTION:display_set_portion_menu();休息;案例 EDIT_FEED1_HOUR:set_feed_time1();休息;案例 EDIT_FEED1_MIN:set_feed_time1();休息;案例 EDIT_FEED2_HOUR:set_feed_time2();休息;案例 EDIT_FEED2_MIN:set_feed_time2();休息;案例 EDIT_HOUR:set_the_time();休息;案例 EDIT_MIN:set_the_time();休息;案例 EDIT_PORTION:set_the_portion();休息; }}//======================================================/* 这是状态机的过渡部分。这允许您从一个菜单转到另一个菜单并更改值*/void menu_transitions(int input) { switch (state) { case MAIN:if (input ==btnENTER) { lcd.clear();状态 =MENU_EDIT_FEED1; } if (input ==btnBACK) {hall_sensor_fail =false; } 休息; //------------------------------------------------ ---- case MENU_EDIT_FEED1:if (input ==btnBACK) { lcd.clear();状态 =主要; } else if (input ==btnENTER) { lcd.clear();状态 =EDIT_FEED1_HOUR; } else if (input ==btnDOWN) { lcd.clear();状态 =MENU_EDIT_FEED2; } 休息; //------------------------------------------------ ---- case EDIT_FEED1_HOUR:// 在设置时间servoOn =false 时需要这个来防止伺服关闭; if (input ==btnUP) { feed_time1_hour++;如果(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; } 休息; //---------------------------------------------------- case EDIT_FEED1_MIN:if (input ==btnUP) { feed_time1_min++; if (feed_time1_min> 59) { feed_time1_min =0; } } else if (input ==btnDOWN) { feed_time1_min--; if (feed_time1_min <0) { feed_time1_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED1_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0, 0); lcd.print( "*Settings Saved*");延迟(1000);液晶显示器(); servoOn =true; write_feeding_time1 (); state =MAIN; } 休息; //---------------------------------------------------- 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; } 休息; //---------------------------------------------------- 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; } 休息; //---------------------------------------------------- case EDIT_FEED2_MIN:if (input ==btnUP) { feed_time2_min++; if (feed_time2_min> 59) { feed_time2_min =0; } } else if (input ==btnDOWN) { feed_time2_min--; if (feed_time2_min <0) { feed_time2_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED2_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0, 0); lcd.print( "*Settings Saved*");延迟(1000);液晶显示器(); servoOn =true; write_feeding_time2 (); state =MAIN; } 休息; //---------------------------------------------------- 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; } 休息; //---------------------------------------------------- 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; } 休息; //---------------------------------------------------- case EDIT_MIN:if (input ==btnUP) { Minute++; if (Minute> 59) { Minute =0; } } else if (input ==btnDOWN) { Minute--; if (Minute <0) { Minute =59; } } else if (input ==btnBACK) { state =EDIT_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0, 0); lcd.print( "*Settings Saved*");延迟(1000);液晶显示器(); rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); state =MAIN; } 休息; //---------------------------------------------------- case MENU_EDIT_PORTION:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_PORTION; } 休息; //---------------------------------------------------- case EDIT_PORTION:if (input ==btnUP) { portion++; if (portion> 20) { portion =1; } } else if (input ==btnDOWN) { portion--; if (portion <1) { portion =20; } } else if (input ==btnBACK) { lcd.clear(); state =MENU_EDIT_PORTION; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0, 0); lcd.print( "*Settings Saved*");延迟(1000);液晶显示器(); write_portion(); state =MAIN; } 休息; }}//=====================================================// This function checks the feed time against the current timevoid check_feedtime (){ DateTime now =rtc.now(); if (now.second() ==0) { if ((now.hour() ==feed_time1_hour) &&(now.minute() ==feed_time1_min)) { feeding1_trigger =true; if (servoOn) { if (feeding1_complete ==false) { lcd.clear(); lcd.setCursor(3, 0); lcd.print ("Dispensing"); lcd.setCursor(1, 1); lcd.print("First Feeding"); startFeeding(); } } } else if ((now.hour() ==feed_time2_hour) &&(now.minute () ==feed_time2_min)) { feeding2_trigger =true; if (servoOn) { if ( feeding2_complete ==false) { lcd.clear(); lcd.setCursor(3, 0); lcd.print ("Dispensing"); lcd.setCursor(0, 1); lcd.print("Second Feeding"); startFeeding(); } } } } // Midnight Reset if ( (now.hour() ==0) &&(now.minute() ==0)) { feeding1_complete =false; feeding2_complete =false; EEPROM.write(4, feeding1_complete); EEPROM.write(5, feeding2_complete); } /*If power outtage happens during a feed time, this checks to see if the feed time has passed and if the feeding occurred. If not, it feeds. */ if ( (now.hour()>=feed_time1_hour) &&(now.minute()> feed_time1_min)) { if ((feeding1_complete ==0) &&(feeding1_trigger ==0)) { lcd.clear(); lcd.setCursor(5, 0); lcd.print ("Uh-Oh!"); lcd.setCursor(2, 1); lcd.print("Power Outage"); startFeeding(); } } if ( (now.hour()>=feed_time2_hour) &&(now.minute()> feed_time2_min)) { if ((feeding2_complete ==0) &&(feeding2_trigger ==0)) { lcd.clear(); lcd.setCursor(5, 0); lcd.print ("Uh-Oh!"); lcd.setCursor(2, 1); lcd.print("Power Outage"); startFeeding(); } }}//=====================================================// Displays the set portion menu optionvoid display_set_portion_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor(0, 1); lcd.print("Set the Portion");}//=====================================================// Displays the menu where you change the current timevoid set_the_time (){ lcd.setCursor(2, 0); lcd.print("Set the Time"); switch (state) { //---------------------------------------------------- case EDIT_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(Hour); } else { lcd.setCursor(5, 1); lcd.print(" "); } lcd.print(":"); add_leading_zero(Minute);休息; //---------------------------------------------------- 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(" "); } 休息; } blinkFunction();}//=====================================================// Displays the menu where you change the feeding portionvoid set_the_portion (){ lcd.setCursor (0, 0); lcd.print("Set the Portion"); switch (state) { case EDIT_PORTION:if (blink_state ==0) { lcd.setCursor(7, 1); add_leading_zero(portion); } else { lcd.setCursor(7, 1); lcd.print(" "); } } blinkFunction();}//=====================================================//Displays the menu option for setting the timevoid display_set_time_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor(2, 1); lcd.print("Set the Time");}//=====================================================// Displays the menu where you change the second feeding timevoid set_feed_time2 (){ lcd.setCursor(0, 0); lcd.print("Set Feed Time 2"); switch (state) { //---------------------------------------------------- case EDIT_FEED2_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(feed_time2_hour); } else { lcd.setCursor(5, 1); lcd.print(" "); } lcd.print(":"); add_leading_zero(feed_time2_min);休息; //---------------------------------------------------- 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(" "); } 休息; } blinkFunction();}//=====================================================// Displays the menu where you change the first feeding timevoid set_feed_time1 (){ lcd.setCursor(0, 0); lcd.print("Set Feed Time 1"); switch (state) { //---------------------------------------------------- case EDIT_FEED1_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(feed_time1_hour); } else { lcd.setCursor(5, 1); lcd.print(" "); } lcd.print(":"); add_leading_zero(feed_time1_min);休息; //---------------------------------------------------- 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(" "); } 休息; } blinkFunction();}//=====================================================// Adds a leading zero to single digit numbersvoid add_leading_zero (int num) { if (num <10) { lcd.print("0"); } lcd.print(num);}//=====================================================/* Displays the feeding time on the main menu as well as the check mark for visual comfirmation of a completed feeding*/void display_feeding_times () { //Displaying first feed time lcd.setCursor(0, 0); lcd.print ("F1:"); add_leading_zero(feed_time1_hour); lcd.print(":"); add_leading_zero(feed_time1_min); lcd.print(" "); if (feeding1_complete ==true) { lcd.write(0); } else { lcd.print(" "); } //Displaying second feed time lcd.setCursor(0, 1); lcd.print("F2:"); add_leading_zero(feed_time2_hour); lcd.print(":"); add_leading_zero(feed_time2_min); lcd.print(" "); if (feeding2_complete ==true) { lcd.write(0); } else { lcd.print(" "); }}//=====================================================// Displays the current time in the main menuvoid display_current_time () { DateTime now =rtc.now(); lcd.setCursor(11, 0); add_leading_zero(now.hour()); lcd.print(":"); add_leading_zero(now.minute());}//=====================================================// Displays the menu option for setting the first feed timevoid display_set_feed_time1_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor(0, 1); lcd.print("Set Feed Time 1");}//=====================================================// Displays the meny option for setting the second feed timevoid display_set_feed_time2_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor(0, 1); lcd.print("Set Feed Time 2");}//=====================================================// Displays the feeding portion in the main menuvoid display_portion (){ lcd.setCursor (12, 1); lcd.print("P:"); add_leading_zero(portion);}//=====================================================// Starts the feeding process.void startFeeding(){ // attach the servo to the pin myservo.attach(SERVO_PIN); count =1; hallSensorTime =millis(); // loop so that the servo runs until desired portion is reached while (count <=portion) { servoStart(); if (hallSensorActivated ==true) { // digitalWrite(LED_PIN,HIGH); count =count + 1; //resetting for next interrupt hallSensorTime =millis(); hallSensorActivated =false; digitalWrite(HALL_LED_PIN, LOW); } /* Moved the servo clockwise a bit to dislodge food stuck in the dispensing mechanism */ if (millis() - hallSensorTime> rotationTime) { hall_sensor_fail =true; Serial.println("I'm in Jiggle"); jiggle(); } } // Keeps track of which feeding just happened and writes it to 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); /* Detaches the servo from the pin so that it is no longer recieving a signal. You may have to add a delay before this so the sensor stops when a magnet is over the hall sensor. There was significant momentum left in my system that I did not need it */ myservo.detach();液晶显示器(); delay_currentMillis =millis(); while (millis() - delay_currentMillis <=delay_interval) { lcd.setCursor(2, 0); lcd.print ("Feeding Done"); } lcd.clear();}//=====================================================void servoStart(){ myservo.write(180);}//=====================================================void servoStop(){ // this value will vary, you have to find it through trial and error myservo.write(94);}//=====================================================// "jiggles" the servo in case food gets stuckvoid jiggle(){ myservo.write(80);延迟(30); myservo.write(93);延迟(30); myservo.write(180);}//=====================================================// Writes the hour and minute valies set for 1st feeding to the EEPROMvoid write_feeding_time1 (){ EEPROM.write(0, feed_time1_hour); EEPROM.write(1, feed_time1_min);}//=====================================================// Writes the hour and minute values set for 2nd feeding to the EEPROMvoid write_feeding_time2 () { EEPROM.write(2, feed_time2_hour); EEPROM.write(3, feed_time2_min);}//=====================================================// Writes portion value set to the EEPROMvoid write_portion (){ EEPROM.write(6, portion);}//=====================================================// Reads the hour and minute values from 1st feed time from EEPROMvoid 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;}//=====================================================// Reads the hour and minute values from 2nd feed time from EEPROMvoid 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;}//=====================================================// Reads portion set value from EEPROMvoid get_portion (){ portion =EEPROM.read(6);}//=====================================================// Reads boolean value of whether or not feedings have occured from EEPROMvoid get_completed_feedings(){ feeding1_complete =EEPROM.read(4); feeding2_complete =EEPROM.read(5);}//=====================================================/* Checks to see if the hall sensor has failed to read a magnet and blinks the red LED*/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; }}//=====================================================// Checks if you push the manual feed buttonvoid manual_feed_check () { if (manualFeed ==true) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" Manual Feeding"); startFeeding(); manualFeed =false; }}//=====================================================// checks to see if RTC is runningvoid check_rtc () { if (!rtc.isrunning()) { led_blink(); }}//=====================================================/* Blinks the red led when RTC has failed. Note:the led will be blinking at a different rate than when the hall sensor fails*/void led_blink(){ unsigned long led_currentMillis =millis(); if (led_currentMillis - led_previousMillis>=interval_delay) { led_previousMillis =led_currentMillis;如果(ledState ==LOW){ ledState =HIGH; } else { ledState =LOW; } digitalWrite(HALL_LED_PIN, ledState); }}//=====================================================// Creates the blinking effect when changing valuesvoid blinkFunction(){ blink_currentMillis =millis(); if (blink_currentMillis - blink_previousMillis> blink_interval) { blink_previousMillis =blink_currentMillis; blink_state =!blink_state; }}//=====================================================
Link to Code on my Github
https://github.com/russo08/Pet-feeder/blob/main/feeder_final.ino定制零件和外壳
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示意图
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.制造工艺