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で印刷する予定がない限り、お湯ではなく温水を使用できます。
機能の完全なリストは次のとおりです。
- 1日2回の給餌
- リアルタイムクロックによる正確な計時
- リアルタイムクロックの手動時刻変更
- 手動給餌オプション
- ホールセンサーとリアルタイムクロック障害のLED表示
- メイン画面での給餌時間、現在の時刻、給餌完了の概要
- メニューの操作が簡単
- 停電の供給が再開されます(電源が再びオンになったときに供給されます)
- EEPROMに安全に保存された給餌時間と完了
- 調剤中に食べ物が詰まった場合のサーボ「ジグル」
デモ
デモビデオは近日公開予定です!
他に何が必要ですか?
- 4x M3-0.5 x 16 mmネジ(エンコーダーホイール)
- 4x M3-0.5 x 10 mmネジ(サーボブラケット)
- 12xM3六角ナット
- 4xM3ロックナット
- 16x 1-1 / 2インチ(38 mm)多目的木ネジ
- 木の接着剤
- 6x 0.315 x 0.118インチ(8 x 3 mm)の磁石
- 脚とサーボマウント用の1-1 / 2 x 1-1 / 2インチ(3.8 x 3.8 cm)の木材
- バックとベースに1 / 2x 6インチ(1.27 x 15.24 cm)の木材
3Dプリントパーツ
私は最近3Dプリンターを入手し、ペットフィーダー用のカスタムパーツを印刷するよりも、それを学び、使用するためのより良い方法を考えました。印刷されるものはすべてPLA +であり、Curaをスライサーとして使用するEnder 3Proを使用して印刷されました。すべてのパーツのSTLは、Githubプロジェクトページにあります。以下にリンクしてください。印刷手順とスライサー設定については、READMEファイルをお読みください。
<図>
住宅
住宅は便利さとシンプルさを念頭に置いて設計されています。ネジやナットは必要ありません。コンポーネントを出し入れするだけです。各インサートには、コンポーネントを所定の位置に保持する4つのタブがあります。
<図> <図> <図> <図>最初はLCD画面スロットを台無しにしましたが、戻ってFusion 360でモデルを修正しました。怠惰すぎて、戻って再印刷することができませんでした。私はそれを固定するために私が周りに置いていたいくつかの余分なM30.5 x6mmネジを使用しました。 LCDスロットの各コーナーに4つのスペーサーがあり、穴が開いているため、必要に応じて画面を固定できます。残念ながら、すべてを入れる前にエンクロージャーの蓋の写真を撮ることはありませんでした。
エンコーダーホイールアタッチメント
ホイールエンコーダホイールには2つの目的があります。
- サーボからの位置フィードバックをArduinoに提供します
- ゴム製のパドルをサーボに取り付けます
1回の送り部分はホイールの1/6(60度)回転に相当します。フードスケールを使用して、ペットが1回の給餌で受け取る餌の量を測定し、その量を満たす範囲が得られるまで部分番号を調整します。私の餌は約173グラムだったと思います。 17のポーションサイズは私に1回の給餌あたり170-177グラムの範囲を与えました。それはすべてあなたのキブルのサイズに依存します。ホーンが8つのM3六角ナットの間にあることを確認してください。
<図> <図> <図>
サーボブラケットとホールセンサーマウント
このカスタムサーボブラケットは、ホールセンサーも保持し、サーボを1-1 / 2 x 1-1 / 2インチ(3.8 x 3.8 cm)の木片に取り付けます。木の長さはサーボがどこにあるかによって異なります(これについては後で詳しく説明します)。マウントにはエラーの余地がたくさんあるので、長さを完璧にすることについてあまり心配しないでください。
<図> <図> <図> <図> <図>
じょうご、フードシュート、シュートレール
これがフードデリバリーシステムを構成しています。食品はディスペンサーから漏斗を通ってシュートに流れ込み、フードボウルに流れ込みます。残念ながら、シュートレールを取り付ける前に写真を撮りませんでした。
<図> <図> <図> <図>
フレーム
*免責事項* 組み立て工程のすべてのステップの写真を撮ったわけではありません。これらの写真の一部は手順をスキップしていますが、フレームの組み立てについては段階的に説明します。
使用するネジ用に事前にドリル穴を開けてください。木材を分割する必要があります!
これは私がアマゾンから購入したシリアルディスペンサーです。複数のペットがいて、複数のフィーダーが必要な場合は、複数のディスペンサーがあるものもありますが、必要なのは1つだけです。ブランドはHoneyCan Doですが、どのブランドでも機能すると確信しています。
<図>私が最初に始めたのは、ディスペンサーのパドルホイールに接続されているロッドからノブを取り外すことでした。手のこぎりを使ってノブを取り外しました。電動工具を持っている場合は、それを使用できます。銀色の部分が終わるところでロッドをさらに下に切断しないでください。ノブの付け根で右にカットします。
<図> <図> <図>ノブを切り落とした後、上の最後の写真にある3つのサポートを切断して、残りの材料を取り除きます。これにはかなり時間がかかりました。適切な電動工具があればもっと速くなると思います。これらの3つのサポートを取り外すと、ロッド自体に残っている残りの部分が少量のエルボーグリースで剥がれるはずです。ロッドのノブに最も近い部分を研磨して、エンコーダーホイールにぴったりとはまるようにする必要があります。
<図> <図> <図>次に、フレームのベースの構築を開始します。私は1 / 2x 6インチ(1.27 x 15.24 cm)の木を取り、それらを2つの8インチ(20.32 cm)に切りました。これは、フィーダーのフレームのベースと背面を構成します。いくつかの木の接着剤を適用し、L字型にそれらを結合するために多目的釘の2つを使用してください。背面パネルと下部パネルの接続を強化するために、いくつかの直角サポートを追加する必要があります。 4本の多目的ネジといくつかの木の接着剤を使用して、背面パネルと下部パネルの両方に固定します。このステップの写真はありませんが、さらに下の写真で見ることができます。
<図>ここから、ディスペンサースタンドを約4.5インチ(11.43 cm)の高さにカットする必要があります。完璧である必要はありません。できるだけ近づけてみてください。じょうごは、いくらかの小刻みに動く部屋を可能にします。スタンドをサイズに合わせてカットしたら、後ろに立てて置き、ベースに平らに置かれていることを確認します。所定の位置に配置したら、鉛筆またはペンを使用して、容器の分注端の中心に印を付けます。次に、ベースパネル全体に2インチ(5.08 cm)の穴を開ける必要があります。これを2回測定し、1回カットすることが重要です。それが上の写真に見られる穴になります。
ベースパネルに穴を開けたら、ディスペンサースタンドをバックパネルに取り付ける準備ができています。あなたがしたいのは、バックパネル(下の最初の写真に示されている)に対してスタンドをセットすることです。ディスペンサースタンドのリングの下に2つのカビーエリアがあります。これは、穴を配置する場所です(下の図を参照)。鉛筆またはペンを使用して、背面パネルの2つの穴の高さをマークします。ディスペンサースタンドの中央にできるだけ近づけてください。スタンドの上部リングを切断した下部に接続する2本のネジがあります。穴あけ時に当たらないように注意してください。繰り返しになりますが、2回測定し、1回ドリルすることを忘れないでください。または2回、この場合。
<図> <図> <図>穴が開けられたので、5/16インチの六角ボルト、5/16インチの六角ナット、および5/16インチのワッシャーを使用してスタンドを背面パネルに取り付けることができます。ボルトを押し込む前に、ボルトの六角端にワッシャーが配置されていることを確認して、ボルトを押し込みます。反対側に出てきたら、もう一方のワッシャーをネジ側に置き、六角ナットを手で締め始めます。これは少し注意が必要です。ナットを手で締めた後、ソケットを使用してナットを保持し、さらに締める必要があります。約1/4回転程度です。締めすぎないように注意してください。
<図> <図>スタンドが安全に固定されたので、サーボが置かれる1/2 x1 / 2インチの木片を追加できます。これの長さは、サーボが配置される場所によって異なります。サーボをエンコーダーホイールのホーンに取り付け、エンコーダーホイールをプラスチック容器内のゴム製パドルに取り付けて、送り機構を組み立てます。コンテナをトップリングにセットし、ベースパネルからサーボがどこにあるかを測定します。それが完璧であることを心配しないでください。サーボブラケットはかなりの小刻みに動く部屋を可能にします。木製の接着剤と1本の多目的ネジを使用して木製のスタンドを固定します。
<図> <図> <図>次のステップは、フィーダーのベースパネルに脚を取り付けることです。脚の長さは、ペットのフードボウルの高さによって異なります。私の犬は上げられたスタンドにフードボウルを持っています。したがって、フィーダーをかなり高く座らせる必要がありました。 4本の多目的ネジといくつかの木の接着剤を使用して、それらを所定の位置に固定します。安定性を高めるために、下に示すように、2つの前脚と2つの後脚の間にクロスビームを配置し、クロスビームの間に別のクロスビームを配置することをお勧めします。合計6本の多目的ネジといくつかの木の接着剤を使用して、部品を脚に固定します。
<図>次のステップは次のとおりです。
- ベースパネルに開けた穴にじょうごを挿入します
- サーボをサーボブラケットに取り付けます
- ブラケットを木製スタンドに固定します
4本のM3x 10mmネジと4本のM3六角ナットを使用してサーボをブラケットに取り付けます。サーボが固定されたら、ブラケットを木製のサーボスタンドに固定できます。多目的ネジ2本を使用して、ブラケットを木製スタンドに軽く固定します。締めすぎないでください。ブラケットが損傷します。必ずこの順序で手順を実行してください。じょうごはサーボを少し上げようとしていて、プラスチック容器の端にかなりぴったりとフィットするので、プラスチック容器がすでにトップリングスタンドに座っている場合は装着できません。
<図> <図> <図> <図>最後のステップは、シュートのスライドインブラケットとスライド自体を取り付けることです。ベースパネルの穴の少し後ろにブラケットを配置します。スライドがフィーダーのフレームをクリアするように、できるだけ前方に配置する必要があります。 2本の多目的ネジを使用して、ブラケットをフレームのベースパネルの下部に固定します。ブラケットにはある程度の屈曲があり、スライドとブラケットの間にかなりしっかりとフィットする必要があるため、ブラケット内のスライドを使用してこれを行うのが最善です。
<図> <図>
エレクトロニクス
残念ながら、私ははんだ付けプロセスの写真を撮ったことがありません。しかし、それほど多くはありません。各コンポーネントを対応するピンにはんだ付けするだけで、準備完了です。代わりにピンヘッダーを使用したい場合は、そのようにすることもできます。 Arduinoスロットの下と上には、ピンとコネクタを入れるのに十分なクリアランスがあります。すべてのコンポーネントをそれぞれのスロットにセットする前に、すべてをはんだ付けすることを強くお勧めします。
調整されていない外部電源ピン(ピン30)を介してArduinoに電力を供給しました。この電圧はArduinoのオンボードレギュレータを介して供給されるため、これには7〜20ボルトの入力電圧が必要です。 USB経由で電力を供給したい場合は、7〜20ボルトではなく、5ボルトで供給していることを確認する必要があります。
ホールセンサーのVccピンと信号ピンの間に10kオームの抵抗をはんだ付けしてください。そうでなければ、あなたは読書をするつもりはありません。また、すべてのコンポーネントを共通のアースに接続することを忘れないでください。理由の1つを見逃してしまい、システムはしばらくは機能していましたが、最終的にホールセンサーが故障し始めました。それは私の犬にとって本当に良い日でした。
結局、ホールセンサーとサーボ接続用のカスタムコネクタを作成しました。ワイヤーをオスのピンヘッダーにはんだ付けしました。これらはエンクロージャーの底からぶら下がっています。ホールセンサーについては、私が横になっていたデュポンコネクタワイヤーをいくつか切断、剥ぎ取り、はんだ付けして、カスタムのメスアダプターを作成しました。
Vccとグラウンドレールについては、パワーレールセクションを、私が横になっていた余分なパーマプロトブレッドボードから切り離しました。あなたが周りに持っているものと同様のものは何でも動作します。レールをスロットにセットする前に、接続を再確認してください。これは、一度入ったら外に出るのが最も難しいことです。私はまた、難しい方法であることを学びました。
<図> <図> <図> <図> <図> <図>それでおしまい!皆さんが私と同じようにこれを作成するのを楽しんでくれることを願っています。最初はやりがいがあるように見えますが、深く理解すればするほど簡単になります。ご不明な点がございましたら、お気軽にお問い合わせください。幸せないじくり回し!
コード
- フィーダーコード
フィーダーコード C / C ++
//ペットフィーダーの最終バージョン/ *機能:-ナビゲートしやすいメニュー-メイン画面の給餌時間、現在時刻、給餌完了、給餌部分の概要-フィードバック用のホールセンサーを使用した制御可能な部分-正確DS1307チップとの時間管理-DS1307チップの設定時間を手動で変更できます-1日2回の供給-手動供給オプション-停電時の供給再開-ホールセンサーとリアルタイムクロックのLED表示-供給時間と完了はEEPROMに安全に保存されます-食べ物が詰まった場合のサーボ「ジグル」* /#include#include #include #include #include # include #include //サーボサーボmyservoを制御するサーボオブジェクトを作成します; //すべての入力ピンとI / Oピンを割り当てます#defineENTER_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 //すべてのボタンを定義し、JC_Buttonライブラリで動作しますButton enterBtn(ENTER_BUTTON_PIN); Button upBtn(UP_BUTTON_PIN); Button downBtn(DOWN_BUTTON_PIN); Button backBtn(BACK_ manualFeedBtn(MANUAL_BUTTON_PIN); // LCDI2CおよびRTCLiquidCrystal_I2Clcd(0x27、16、2); RTC_DS1307rtc; //コード全体で使用される変数unsignedlong hallSensorTime; unsigned long rotationTime =400; unsigned long led_previousMillis =0; 1000; unsigned long delay_interval =2000; int ledState =LOW; boolean manualFeed =false; boolean hall_sensor_fail =false; unsigned long flash_previousMillis =0; unsigned long flash_currentMillis =0; unsigned long flash_interval =500; unsigned long delay_currentMillis =0; unsigned long =0; boolean bright_state =false; int count; boolean Feeding1_complete =false; boolean Feeding2_complete =false; boolean Feeding1_trigger =false; boolean Feeding 2_trigger =false; booleanservoOn =true; //ホールセンサー割り込みvolatileboolean hallSensorActivated =false; volatile int isr_count =1; void hallActiveISR(){hallSensorActivated =true; digitalWrite(HALL_LED_PIN、HIGH); isr_count =isr_count + 1;} / *ここでは列挙型を使用して、各ボタンを割り込み値に設定するのではなく、押されているボタンをより正確に追跡します。* / enum {btnENTER、btnUP、btnDOWN、btnBACK、}; / *ステートマシンの状態。ここでも列挙型と同じです。各メニューに整数値を与える代わりに、現在のメニューまたは行きたいメニューを追跡しやすくなります* / enum STATES {MAIN、MENU_EDIT_FEED1、MENU_EDIT_FEED2、MENU_EDIT_TIME、MENU_EDIT_PORTION、EDIT_FEED1_HOUR、EDIT_FEED1_MIN、EDIT_FEED2 、EDIT_MIN、EDIT_PORTION}; //状態machineSTATES状態の状態を保持します; //ユーザー入力変数intHour; int Minute; int part; int feed_time1_hour; int feed_time1_min; int feed_time2_hour; int feed_time2_min; int userInput; //特殊文字チェックマークバイトcheck_Char [8] ={B00000、B00000、B00001、B00011、B10110、B11100、B11000、B00000}; // ======================セットアップ===========================void setup(){Wire.begin(); Serial.begin(9600); lcd.init(); lcd.backlight(); 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、OUTPUT); pinMode(hallPin、INPUT); / *ホールセンサーに接続するピンに割り込みを取り付けます。私が使用したホールセンサーはHIGHに設定されており、磁石にぶつかるとLOWになります。そのため、FALLINGに設定されています* / attachInterrupt(digitalPinToInterrupt(hallPin)、hallActiveISR、FALLING); // LEDのデフォルト状態digitalWrite(POWER_LED_PIN、HIGH); digitalWrite(HALL_LED_PIN、LOW); / *これらの関数は、起動時にEEPROMから保存されたフィード時間、完了したフィード、およびポーションサイズを取得します。私が住んでいる場所でランダムに停電が発生したため、これを行いました。 * / get_feed_time1(); get_feed_time2(); get_completed_feedings(); get_portion();} // ======================The Loop ===========================void loop(){changeing_states(); check_buttons(); check_feedtime(); check_rtc(); manual_feed_check();} // ===============関数=======================/ *使用ボタンが押されたかどうかを継続的にチェックするJC_Buttonライブラリ。押されたボタンに応じて、関数menu_transitions * / void check_buttons(){enterBtn.read();で使用される変数userInputを設定します。 upBtn.read(); downBtn.read(); backBtn.read(); manualFeedBtn.read(); if(enterBtn.wasPressed()){Serial.println( "You Pressed Enter!"); userInput =btnENTER; menu_transitions(userInput); } if(upBtn.wasPressed()){Serial.println( "You Pressed Up!"); userInput =btnUP; menu_transitions(userInput); } if(downBtn.wasPressed()){Serial.println( "You Pressed Down!"); userInput =btnDOWN; menu_transitions(userInput); } if(backBtn.wasPressed()){Serial.println( "You Pressed Back!"); userInput =btnBACK; menu_transitions(userInput); } if(manualFeedBtn.wasPressed()){Serial.println( "手動でフィードしています!"); manualFeed =true; }} // =====================================================/ *この関数は、現在のメニューまたは「状態」に応じて、表示される内容を決定します。各メニューには、それぞれのメニューを表示する関数があります* / void change_states(){switch(state){ケースMAIN:display_current_time(); display_feeding_times(); display_portion();壊す;ケースMENU_EDIT_FEED1:display_set_feed_time1_menu();壊す;ケースMENU_EDIT_FEED2:display_set_feed_time2_menu();壊す;ケースMENU_EDIT_TIME:display_set_time_menu();壊す;ケース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; } 壊す; // ------------------------------------------------ ----ケースMENU_EDIT_FEED1:if(input ==btnBACK){lcd.clear();状態=MAIN; } 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 ++; if(feed_time1_hour> 23){feed_time1_hour =0; } } else if (input ==btnDOWN) { feed_time1_hour--; if (feed_time1_hour <0) { feed_time1_hour =23; } } else if (input ==btnBACK) { lcd.clear(); servoOn =true; state =MENU_EDIT_FEED1; } else if (input ==btnENTER) { state =EDIT_FEED1_MIN; } break; //---------------------------------------------------- case EDIT_FEED1_MIN:if (input ==btnUP) { feed_time1_min++; if (feed_time1_min> 59) { feed_time1_min =0; } } else if (input ==btnDOWN) { feed_time1_min--; if (feed_time1_min <0) { feed_time1_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED1_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0、0); lcd.print( "*Settings Saved*"); delay(1000); lcd.clear(); servoOn =true; write_feeding_time1 (); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_FEED2:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_FEED1; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_FEED2_HOUR; } else if (input ==btnDOWN) { lcd.clear(); state =MENU_EDIT_TIME; } break; //---------------------------------------------------- case EDIT_FEED2_HOUR:servoOn =false; if (input ==btnUP) { feed_time2_hour++; if (feed_time2_hour> 23) { feed_time2_hour =0; } } else if (input ==btnDOWN) { feed_time2_hour--; if (feed_time2_hour <0) { feed_time2_hour =23; } } else if (input ==btnBACK) { lcd.clear(); servoOn =true; state =MENU_EDIT_FEED2; } else if (input ==btnENTER) { state =EDIT_FEED2_MIN; } break; //---------------------------------------------------- case EDIT_FEED2_MIN:if (input ==btnUP) { feed_time2_min++; if (feed_time2_min> 59) { feed_time2_min =0; } } else if (input ==btnDOWN) { feed_time2_min--; if (feed_time2_min <0) { feed_time2_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED2_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0、0); lcd.print( "*Settings Saved*"); delay(1000); lcd.clear(); servoOn =true; write_feeding_time2 (); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_TIME:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_FEED2; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_HOUR; } else if (input ==btnDOWN) { lcd.clear(); state =MENU_EDIT_PORTION; } break; //---------------------------------------------------- case EDIT_HOUR:if (input ==btnUP) { Hour++; if (Hour> 23) { Hour =0; } } else if (input ==btnDOWN) { Hour--; if (Hour <0) { Hour =23; } } else if (input ==btnBACK) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { state =EDIT_MIN; } break; //---------------------------------------------------- case EDIT_MIN:if (input ==btnUP) { Minute++; if (Minute> 59) { Minute =0; } } else if (input ==btnDOWN) { Minute--; if (Minute <0) { Minute =59; } } else if (input ==btnBACK) { state =EDIT_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0、0); lcd.print( "*Settings Saved*"); delay(1000); lcd.clear(); rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_PORTION:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_PORTION; } break; //---------------------------------------------------- case EDIT_PORTION:if (input ==btnUP) { portion++; if (portion> 20) { portion =1; } } else if (input ==btnDOWN) { portion--; if (portion <1) { portion =20; } } else if (input ==btnBACK) { lcd.clear(); state =MENU_EDIT_PORTION; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor(0、0); lcd.print( "*Settings Saved*"); delay(1000); lcd.clear(); write_portion(); state =MAIN; } break; }}//=====================================================// 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(" "); } break; } 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(" "); } break; } 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(" "); } break; } 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(); lcd.clear(); delay_currentMillis =millis(); while (millis() - delay_currentMillis <=delay_interval) { lcd.setCursor(2, 0); lcd.print ("Feeding Done"); } lcd.clear();}//=====================================================void servoStart(){ myservo.write(180);}//=====================================================void servoStop(){ // 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); delay(30); myservo.write(93); delay(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; if (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. 製造プロセス