Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Кормушка для домашних животных с деталями, напечатанными на 3D-принтере

Компоненты и расходные материалы

Arduino Nano R3
× 1
Модуль часов реального времени DS 1307
Вы можете использовать RTC любой марки, я уверен, что есть более дешевые варианты
× 1
Модуль регулятора напряжения LM-317
× 1
Сервопривод непрерывного вращения
× 1
Дозатор зерновых
× 1
магниты 8 мм
× 6
Датчик Холла (униполярный)
Убедитесь, что датчик, который вы получаете, УНИПОЛЯРНЫЙ, чтобы вам не требовалась обратная полярность магнита для сбросить выход датчика
× 1
ЖК-экран с подсветкой Adafruit RGB - 16x2
× 1
Рюкзак с ЖК-дисплеем I2C
При желании вы можете купить ЖК-экран с уже прикрепленным рюкзаком
× 1
5 мм светодиод:красный
× 1
5 мм светодиод:зеленый
× 1
Кнопочный переключатель, мгновенный
× 5
Адаптер гнезда питания постоянного тока с гнездом
× 1
Настенная бородавка 9–12 В
× 1
Резистор 10 кОм
Для датчика Холла. Подключите Vcc к сигнальным контактам
× 1
Резистор 330 Ом
Для светодиодов. Вы можете использовать более высокое сопротивление, если не хотите, чтобы они были такими яркими
× 2

Необходимые инструменты и машины

3D-принтер (общий)
дрель / шуруповерт, беспроводной
Циркулярная пила

Об этом проекте

История кормушки для домашних животных

Я некоторое время играл с микроконтроллерами и хотел попытаться отойти от руководств. До Covid-19 у меня не было расписания, и я работал довольно много часов. График кормления моей собаки действительно начал нарушаться и приводил к некоторому видимому дискомфорту. Мой район также подвержен наводнениям во время сезона ураганов. К сожалению, было несколько случаев, когда я не мог вернуться домой, чтобы покормить свою старуху. Мне нужно было найти решение, чтобы, если я не смогу вернуться домой, моя собака не проголодалась. Вместо того, чтобы покупать один за 30-40 долларов, почему бы не построить его за более чем 100 долларов? Просто шучу.

Кормушка!

В моей кормушке для собак используется микроконтроллер Arduino. Мне было нужно несколько ключевых функций, которых не было в большинстве других версий кормушек для собак. А именно, решение для восстановления питания при отключении электроэнергии и решение проблемы застревания пищи в дозирующем механизме. В моем районе также случаются случайные отключения электроэнергии. Отключения обычно не длятся долго, поэтому я не видел необходимости в резервировании питания. Я также хотел что-то, что можно было бы легко разбирать и чистить. Будьте осторожны при мытье деталей, напечатанных на 3D-принтере, вы можете использовать теплую воду, а НЕ горячую, если только вы не планируете печатать из ABS или PET-G.

Полный список функций выглядит следующим образом:

  • Два кормления в день.
  • Точное время с часами реального времени.
  • Ручное изменение времени часов реального времени.
  • Возможность ручной подачи
  • Светодиодная индикация неисправности датчика Холла и часов реального времени.
  • Обзор времени кормления, текущего времени и окончания кормления на главном экране.
  • Меню с удобной навигацией
  • Возобновление подачи при отключении электроэнергии (подача возобновится при возобновлении подачи электроэнергии)
  • Время кормления и кормления надежно хранятся в EEPROM.
  • Сервопривод "покачивается" в случае застревания пищи во время раздачи.

Демо

Демо-видео скоро будет!

Что еще вам нужно?

  • 4 винта M3-0,5 x 16 мм (колесо энкодера)
  • 4 винта M3-0,5 x 10 мм (кронштейн сервопривода)
  • 12 шестигранных гаек M3
  • 4 контргайки M3
  • Универсальные шурупы по дереву 16x1-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 выступа, удерживающих компоненты на месте.

Несмотря на то, что я сначала испортил слот ЖК-экрана, я вернулся и исправил модель в Fusion 360. Мне было просто лень вернуться и перепечатать ее. Я использовал несколько дополнительных винтов M3 0,5 x 6 мм, которые у меня были, чтобы закрепить его. В каждом углу слота для ЖК-дисплея есть четыре прокладки с отверстиями, чтобы вы могли закрепить экран при необходимости. К сожалению, я ни разу не сфотографировал крышку корпуса, пока все не вставил.

Крепление колеса энкодера

Колесо энкодера служит двум целям:

  • Предоставляет Arduino обратную связь по положению от сервопривода.
  • Присоединяет резиновую лопасть к сервоприводу.

Одна порция подачи равна 1/6 (60 градусов) поворота колеса. Используйте весы для измерения количества корма, получаемого вашим питомцем за одно кормление, а затем регулируйте количество порций, пока не получите диапазон, который соответствует этому количеству. Я считаю, что одно кормление для меня составляло около 173 граммов пищи. Размер порции 17 дал мне диапазон 170–177 граммов на одно кормление. Все зависит от размера вашего куска. Убедитесь, что рог находится между 8 шестигранными гайками M3.

Кронштейн сервопривода и крепление датчика Холла

Этот специальный кронштейн сервопривода также удерживает датчик Холла и устанавливает сервопривод на кусок дерева размером 1-1 / 2 x 1-1 / 2 дюйма (3,8 x 3,8 см). Длина бревна будет зависеть от того, где находится сервопривод (подробнее об этом позже). С креплением есть много места для ошибки, поэтому не беспокойтесь о том, чтобы получить идеальную длину.

Воронка, лоток для пищевых продуктов и направляющая желоба

Это составляет систему доставки еды. Пища опускается из раздаточного устройства через воронку, попадает в лоток и попадает в миску. К сожалению, я не сфотографировал направляющую желоба перед ее установкой.

Фрейм

* Заявление об ограничении ответственности * Я не фотографировал каждый этап процесса сборки. На некоторых из этих изображений шаги пропущены, но я все равно покажу вам пошаговые инструкции по сборке рамы.

Сделайте предварительное просверливание отверстий для любого используемого вами винта. Вы расколите древесину!

Это дозатор для хлопьев, который я купил на Amazon. В некоторых есть несколько дозаторов, если у вас несколько домашних животных и вам нужно несколько кормушек, мне нужен был только один. Бренд - 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. Как только сервопривод закреплен, мы можем закрепить кронштейн на деревянной стойке сервопривода. Используйте два универсальных винта, чтобы слегка закрепить кронштейн на деревянной подставке. Не затягивайте слишком сильно, иначе вы повредите кронштейн. Убедитесь, что вы выполняете шаги в указанном порядке. Воронка будет немного приподнимать сервопривод, и она довольно плотно прилегает к концу пластикового контейнера, поэтому его невозможно надеть, если пластиковый контейнер уже находится в верхней кольцевой стойке.

Последними шагами будет прикрепление выдвижного кронштейна желоба и самой направляющей. Вы хотите разместить кронштейн немного позади отверстия в базовой панели. Вы хотите, чтобы он был как можно дальше вперед, чтобы ползун выходил за рамку устройства подачи. Используйте два универсальных винта, чтобы прикрепить кронштейн к нижней части базовой панели рамы. Лучше всего делать это с ползунком в кронштейне, так как кронштейн немного прогибается, и вам понадобится довольно надежная посадка между ползунком и кронштейном.

Электроника

К сожалению, я так и не сфотографировал процесс пайки. Однако в этом нет ничего особенного. Просто припаяйте каждый компонент к его соответствующим контактам, и все будет в порядке. Если вы хотите использовать вместо этого заголовки контактов, вы также можете сделать это таким же образом. Под и над слотом Arduino имеется достаточный зазор для контактов и разъемов. Я определенно рекомендую спаять все вместе, прежде чем устанавливать все компоненты в соответствующие слоты.

Я запитал свой Arduino через нерегулируемый внешний вывод питания (вывод 30). Для этого требуется входное напряжение от 7 до 20 вольт, потому что это напряжение подается через встроенный регулятор Arduino. Если вы хотите запитать его через USB, вам просто нужно убедиться, что вы подаете на него 5 Вольт, а НЕ 7-20 В.

Обязательно припаяйте резистор 10 кОм между выводами Vcc и Signal датчика Холла. В противном случае вы не получите показания. Также не забудьте подключить все компоненты к общей земле. Я допустил ошибку, пропустив одну из площадок, и моя система какое-то время работала, но датчик Холла в конечном итоге начал выходить из строя. Для моей собаки это был действительно хороший день.

Я закончил тем, что изготовил нестандартные разъемы для подключения датчика Холла и сервопривода. Я припаял провода к штыревым разъемам. Они свешиваются из нижней части корпуса. Для датчика Холла я сделал специальный переходник с внутренней резьбой, отрезав, сняв изоляцию и припаяв несколько проводов разъема Dupont, которые у меня были.

Что касается Vcc и шины заземления, я отрезал часть шины питания от какой-то лишней макетной платы Perma-Pro, которую я валял. Все, что есть у вас под рукой, будет работать. Просто дважды проверьте свои соединения, прежде чем вставлять направляющую в соответствующий слот. Это самая сложная вещь, чтобы выбраться из нее, когда она внутри. Я тоже усвоил это на собственном горьком опыте.

Вот и все! Надеюсь, вы, ребята, получите такое же удовольствие, создавая это, как и я. Поначалу это кажется сложным, но чем больше вы вникаете, тем легче становится. Дайте мне знать, если у вас возникнут какие-либо вопросы. Удачной работы!

Код

  • Код фидера
Код фидера C / C ++
 // Окончательная версия кормушки для домашних животных / * Особенности:- Меню с простой навигацией - Обзор времени кормления, текущего времени, окончания кормления и порции кормления на главном экране - Контролируемые порции с использованием датчика Холла для обратной связи - Точность Отслеживание времени с помощью микросхемы DS1307 - Можно вручную изменить установленное время в микросхеме DS1307 - Два кормления в день - Возможность ручного кормления - Перезапуск подачи в случае отключения электроэнергии - Светодиодная индикация датчика Холла и часов реального времени - Время подачи и завершения безопасно сохраняются в EEPROM - Сервопривод "покачивается" в случае застревания пищи * / # include  #include  #include  #include  #include  # include  #include  // Создает сервообъект для управления вашим servoServo myservo; // Назначение всех моих входных и выходных контактов #define ENTER_BUTTON_PIN 11 # define UP_BUTTON_PIN 10 #define DOWN_BUTTON_PIN 9 #define BACK_BUTTON_PIN 8 # определить POWER_LED_PIN 5 # определить MANUAL_BUTTON_PIN A1 # определить hallPin 2 # определить HALL_LE D_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_BUTTONPIN); 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_delay =1000; unsigned long delay; int ledState =LOW; логическое manualFeed =false; логическое hall_sensor_fail =false; unsigned long blink_previousMillis =0; unsigned long blink_currentMillis =0; unsigned long blink_interval =500; unsigned long delay_currentMillis =0; unsigned long delay_previous; int count; логическое Feed1_complete =false; логическое Feed2_complete =false; логическое Feed1_trigger =false; логическое Feed2_trigger =false; логическое servoOn =tru e; // Прерывание датчика Холлаvolatile boolean 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_FEEDIN_FEED_MIN_FEDIT_MIN_MIN_MIN_FEDIT_MIN_MIN_FEDIT_MIN_FEDIT_MIN_ , 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); 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, ВЫХОД); pinMode (hallPin, INPUT); / * Присоединение прерывания к выводу, который подключается к датчику Холла. Датчик Холла, который я использовал, установлен на ВЫСОКИЙ и становится НИЗКИМ, когда сталкивается с магнитом. Вот почему он установлен на ПАДЕНИЕ * / attachInterrupt (digitalPinToInterrupt (hallPin), hallActiveISR, FALLING); // состояние светодиодов по умолчанию digitalWrite (POWER_LED_PIN, HIGH); digitalWrite (HALL_LED_PIN, LOW); / * Эти функции получают сохраненное время подачи, завершенное кормление и размер порции из EEPROM при запуске. Я сделал это, потому что здесь, где я живу, случаются случайные отключения электроэнергии. * / get_feed_time1 (); get_feed_time2 (); get_completed_feedings (); get_portion ();} // ======================Цикл ===========================void loop () {change_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!»); userInput =btnENTER; menu_transitions (userInput); } if (upBtn.wasPressed ()) {Serial.println («Вы нажали!»); userInput =btnUP; menu_transitions (userInput); } if (downBtn.wasPressed ()) {Serial.println («Вы нажали!»); userInput =btnDOWN; menu_transitions (userInput); } if (backBtn.wasPressed ()) {Serial.println («Вы нажали обратно!»); userInput =btnBACK; menu_transitions (userInput); } if (manualFeedBtn.wasPressed ()) {Serial.println («Вы кормите вручную!»); manualFeed =true; }} // =====================================================/ * Эта функция определяет, что отображается, в зависимости от того, в каком меню или "состоянии" вы находитесь. В каждом меню есть функция, которая отображает соответствующее меню * / void change_states () {switch (state) { case MAIN:display_current_time (); display_feeding_times (); display_portion (); ломать; case MENU_EDIT_FEED1:display_set_feed_time1_menu (); ломать; case MENU_EDIT_FEED2:display_set_feed_time2_menu (); ломать; case MENU_EDIT_TIME:display_set_time_menu (); ломать; case MENU_EDIT_PORTION:display_set_portion_menu (); ломать; case EDIT_FEED1_HOUR:set_feed_time1 (); ломать; case EDIT_FEED1_MIN:set_feed_time1 (); ломать; case EDIT_FEED2_HOUR:set_feed_time2 (); ломать; case EDIT_FEED2_MIN:set_feed_time2 (); ломать; case EDIT_HOUR:set_the_time (); ломать; case EDIT_MIN:set_the_time (); ломать; case EDIT_PORTION:set_the_portion (); ломать; }} // =====================================================/ * Это переходная часть конечного автомата. Это то, что позволяет вам переходить из одного меню в другое и изменять значения * / void menu_transitions (int input) {switch (state) {case MAIN:if (input ==btnENTER) {lcd.clear (); состояние =MENU_EDIT_FEED1; } если (input ==btnBACK) {hall_sensor_fail =false; } ломать; // ------------------------------------------------ ---- case MENU_EDIT_FEED1:if (input ==btnBACK) {lcd.clear (); состояние =ГЛАВНЫЙ; } иначе, если (input ==btnENTER) {lcd.clear (); состояние =EDIT_FEED1_HOUR; } иначе, если (input ==btnDOWN) {lcd.clear (); состояние =MENU_EDIT_FEED2; } ломать; // ------------------------------------------------ ---- case EDIT_FEED1_HOUR:// Это нужно для предотвращения отключения сервопривода при установке времени servoOn =false; если (input ==btnUP) {feed_time1_hour ++; если (feed_time1_hour> 23) {feed_time1_hour =0; }} иначе, если (input ==btnDOWN) {feed_time1_hour--; если (feed_time1_hour <0) {feed_time1_hour =23; }} иначе, если (input ==btnBACK) {lcd.clear (); servoOn =true; состояние =MENU_EDIT_FEED1; } иначе, если (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); lcd.clear (); 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); lcd.clear (); 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); lcd.clear (); 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); lcd.clear (); 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(); 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.

Производственный процесс

  1. Обработка 3D-печатных деталей ... мелками?
  2. Датчик отслеживания линии с RPi
  3. API датчика окружающей среды с RPi
  4. Датчик Portenta и термопары (с MAX6675)
  5. Лучший контроль качества с 3D-печатными деталями
  6. Закрепите себя функциональными деталями, напечатанными на 3D-принтере!
  7. Новый взгляд на картинг с 3D-печатными деталями
  8. Веб-семинар:Печать надежных 3D-печатных деталей с помощью Eiger
  9. Экстремальные дроны с 3D-печатью
  10. Гонка до финиша с 3D-печатью запчастей для автомобилей Formula SAE