Предлагаем изготовить своими руками автоматическую кормушку для кошек, работающую под управлением Arduino, с использованием шнека, напечатанного на 3D-принтере. Кормушка запрограммирована на подачу корма два раза в сутки, количество разовой выдачи пищи задается пользователем. Устройство имеет встроенные часы и резервное питание от батарей.
Если ваши любимые кошки по утрам сводят вас с ума, прося еды, значит настало время роботизировать процесс их кормления.
Кормушки для кота с дозатором, которые есть в продаже, имеют ряд недостатков, таких как: ужасная система программирования, энергозависимая память (стирается, при отсутствии питания) и др.
Хорошая кормушка для котов с таймером стоит более 300 долларов США, поэтому есть смысл сделать такую автокормушку самостоятельно.
Содержание статьи
Шаг 1: Необходимые материалы
Электроника:
- Один обычный серводвигатель.
- Один непрерывный (или взломанный) серводвигатель.
- Один шнек, напечатанный на 3D-принтере.
- ПВХ-тройник.
- Один контроллер Arduino (UNO или любого другого типа).
- Один энкодер KY-040.
- Один символьный (буквенно-цифровой) жидкокристаллический дисплей на базе контроллера HD44780.
- Одна небольшая кнопка (без фиксации).
- Один высокий контейнер для еды.
- Одна монтажная коробка из МДФ.
- Один блок питания на 12 В, 500 мА (лучше 1 А).
Шаг 2: Изготавливаем шнек (механизм подачи пищи)
Один из сложных моментов в проекте автоматической кормилки для кошек – создание шнека. Придется изготовить его при помощи 3D-печати. Скачайте проект с ресурса Thigiverse и распечатайте его. Если у вас нет 3D-принтера, можете вначале построить и его.
Проект шнека состоит из нескольких файлов: для изготовления шнека и для изготовления крепления серводвигателя на трубу. Шнек можно распечатать сразу одной деталью, можно раздельными деталями с последующей склейкой. Шнек имеет размер 10 см в высоту.
Серводвигатель, использующийся для привода шнека, должен иметь непрерывный ход. Такой можете сделать из обычного серводвигателя: для этого его нужно разобрать, удалить пластиковый выступ на одном из зубцов и убрать потенциометр. Поищите в интернете способы переделки обычных серводвигателей на двигатели с непрерывным вращением.
Вставьте и прикрутите сервопривод к основанию шнека. Убедитесь, что он выровнен и не имеет биений. Протестируйте работу шнека в пластиковом тройнике диаметром 15 мм (используется в сантехнике). Если шнек работает без проблем, зафиксируйте его в тройнике темро-клеем.
Шаг 3: Делаем корпус
Корпус сделайте из обыкновенной МДФ плиты толщиной 9 мм. Сначала соберите коробку, затем вырежьте лобзиком квадратные отверстия для панели управления и серводвигателя, круглые, для установки шнекового механизма, сделайте с помощью сверла-коронки и дрели.
Напечатайте на 3D-принтере панель (или найдите подходящую) для установки на нее ЖК-дисплея, кнопки ручной подачи и энкодера. Прикрутите все это на коробку корпуса. Все должно выглядеть красиво и аккуратно!
Мешалку для пищи также напечатайте на 3D-принтере и прикрепите к планке (которая насаживается на вал серводвигателя и идет с ним в комплекте).
Контейнер для еды крепится на корпус и легко может быть заменен.
Пройдет некоторое время, и, возможно, ваши кошки научаться самостоятельно нажимать кнопку ручной подачи корма!
Файлы
Шаг 4: Электронные компоненты Arduino
Наше устройство будет управляться с помощью системы Arduino. Сначала соберите на макетной плате прототип электронной начинки, так вам будет легче проводить отладку устройства. Ниже есть ссылка на файл Fritzing. Загрузите его и подробно изучите макет. Программа Fritzing является бесплатной, с открытым кодом.
Ознакомьтесь с принципами работы и управления ЖК-дисплеем, энкодера, модуля часов реального времени, сервоприводов, затем объедините все это и переходите к программированию.
В проекте используются два серводвигателя, первый — для подачи корма с помощью шнекового механизма, второй – для перемешивания корма перед каждым кормлением, чтобы предотвратить его слипание (в устройство загружается сухой корм).
После того, как электроника будет отлажена и протестирована, можно собирать схему в более компактном исполнении. Контроллер Arduino, для экономии места, можете заменить чипом ATMEGA328P. Также вам потребуется источник питания 5 В.
Ссылка на эскиз макетной платы, созданный с помощью программы PCB Wizard, есть в конце этого шага.
Спаяйте все компоненты и подключите ЖК-дисплей, энкодер-регулятор и кнопку ручной подачи с помощью ленточных кабелей.
Можете изменить схему на свое усмотрение: исключить кнопку и энкодер, или наоборот, добавить какие-то компоненты; можете изготовить плату травлением, или вырезать дорожки с помощью ножа – выбор за вами. Главное потом написать правильный код.
Файлы
- Breadboard Sketch.fzz
- Cat Feeder circuit diag.cwz
- Circuit Wizard — Cat Feeder component side.pdf
- Circuit Wizard — Cat Feeder solder side.pdf
Шаг 5: Программирование
Код программы не такой сложный, как может показаться на первый взгляд.
Программа работает следующим образом:
- Считывается время из модуля часов реального времени.
- На ЖК-экране отображается текущее время, а также время кормления и доза корма (по умолчанию).
- Отслеживается работа энкодера, если его вращать, то начинают циклически переключаться параметры: часы, размер порции, время подачи-1, время подачи-2.
- Проверяется, подошло ли время кормления (1 или 2), если подошло, то выдается очередная порция пищи.
- Отслеживается нажатие кнопки ручной подачи корма: если кнопка нажата, то подается корм. После отпускания кнопки, продолжается обычная работа программы.
В программе используются прерывания – реакция на действия вмешательства из вне, в нашем случае, это будет происходить при поступлении сигнала от энкодера. Это означает, что если вы начнете вращать ручку энкодера, то какое бы действие в этот момент программа не выполняла, она переходит под управление энкодера.
Порядок установки часов, времени подачи и количества корма следующий: нажимаем на энкодер, курсор начинает мигать, выбираем на экране значение часов, поворачиваем энкодер для изменения значения, снова нажимаем на энкодер, значение записывается в память модуля часов реального времени, курсор переходит на значения минут, вращением энкодера меняем минуты и так далее. Ни чего сложного в этом нет.
Процедура кормления основана на функции if (если), то есть, когда текущее время совпадает с запрограммированным временем кормления, выполняется процедура подачи пищи.
Код, в представленном здесь скетче, не идеален, например, поворот энкодера не всегда увеличивает число на экране. Но, возможно, вам удастся выявить и устранить эту проблему.
На первом видео присутствует момент ручной подачи корма. Автоматически машина подает корм два раза в сутки в заданное время. Если задать одинаковое время для кормления-1 и кормления-2, то корм будет подаваться один раз в сутки. Скачайте скетч с кодом ниже.
/* Automatic Auger Audiono pet feeder Copyright Roger Donoghue 28/03/2015 all rights reserved. For personal use only. Not for commercial use or resale. Allows you to set 2 feeding times and the quantity as a multiple of the default feed quantity. Uses a DS1307 real time clock to keep the time, with a rechargable battery built in. (You can use the arduino RTC example code in the IDE to set the clock , or use the rotary encoder as intended) */ // include the library code: #include #include // needed for the RTC libraty #include #include // Real Time Clock Library #include // initialize the library with the numbers of the interface pins dor the LCD LiquidCrystal lcd(12, 11, 5, 8, 7, 6); #define PIN_SERVO 9 Servo feedServo; Servo stirServo; int pos = 0; volatile boolean TurnDetected; volatile boolean up; const int PinCLK=2; // Used for generating interrupts using CLK signal const int PinDT=3; // Used for reading DT signal const int PinSW=4; // Used for the push button switch of the Rotary Encoder const int buttonPin = A3; // the number of the pushbutton pin for manual feed 13 int buttonState = 0; // variable for reading the manual feed pushbutton status int feed1hour = 07; // variables for feeding times and quantity int feed1minute = 00; int feed2hour = 17; int feed2minute = 30; int feedQty = 4; int feedRate = 800; //a pwm rate the triggers forward on the servo 75 int feedReversal = 80; //a pwm rate that triggers reverse on the servo // play with these numbers for your servo. Mine is a Futaba digital servo // that I removed the pot from and the plastic lug, to make it continuous. void isr () { // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK if (digitalRead(PinCLK)) // this keeps an eye out for the rotary encoder being turned regardless of where the program is up = digitalRead(PinDT); // currently exectuting - in other words, during the main loop this ISR will always be active else up = !digitalRead(PinDT); TurnDetected = true; } void setup () { // set up the LCD's number of columns and rows: lcd.begin(16, 2); // setup the Rotary encoder pinMode(PinCLK,INPUT); pinMode(PinDT,INPUT); pinMode(PinSW,INPUT); pinMode(buttonPin, INPUT); attachInterrupt (0,isr,FALLING); // interrupt 0 is always connected to pin 2 on Arduino UNO lcd.setCursor(17,0); lcd.print("Roger Donoghue's"); // A bit of fun :-) lcd.setCursor(17,1); lcd.print(" Cat-O-Matic"); for (int positionCounter = 0; positionCounter < 17; positionCounter++) { // scroll one position left: lcd.scrollDisplayLeft(); // wait a bit: delay(150); } delay(3000); for (int positionCounter = 0; positionCounter < 17; positionCounter++) { // scroll one position left: lcd.scrollDisplayRight(); // wait a bit: delay(150); } // end of fun lcd.setCursor(17,0); lcd.print(" "); lcd.setCursor(17,1); lcd.print(" "); } void loop () { //Main program loop - most things in here! static long virtualPosition=0; // without STATIC it does not count correctly!!! tmElements_t tm; // This sectionm reads the time from the RTC, sets it in tmElements tm (nice to work with), then displays it. RTC.read(tm); lcd.setCursor(0, 0); printDigits(tm.Hour); //call to print digit function that adds leading zeros that may be missing lcd.print(":"); printDigits(tm.Minute); lcd.print(":"); printDigits(tm.Second); lcd.print(" "); lcd.print("Qty "); lcd.print(feedQty); lcd.print(" "); lcd.setCursor(0,1); lcd.print("1)"); printDigits(feed1hour); lcd.print(":"); printDigits(feed1minute); lcd.print(" 2)"); printDigits(feed2hour); lcd.print(":"); printDigits(feed2minute); // MAIN BREAKOUT "IF" SECION BELOW THAT MONITORS THE PUSH BUTTON AND ENTERS PROGRAMMING IF IT'S PUSHED if (!(digitalRead(PinSW))) { // check if pushbutton is pressed // if YES then enter the programming subroutine lcd.blink(); // Turn on the blinking cursor: lcd.setCursor(5,0); lcd.print(" SET"); virtualPosition = tm.Hour; //needed or the hour will be zero each time you change the clock. do { lcd.setCursor(0,0); // put cursor at Time Hour delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the hour of time - tm.Hour = virtualPosition; RTC.write(tm); lcd.setCursor(0, 0); printDigits(tm.Hour); // then re-print the hour on the LCD } while ((digitalRead(PinSW))); // do this "do" loop while the PinSW button is NOT pressed lcd.noBlink(); delay(1000); // SET THE MINS lcd.blink(); // Turn on the blinking cursor: virtualPosition = tm.Minute; //needed or the minute will be zero each time you change the clock. do { lcd.setCursor(3,0); // put cursor at Time Mins delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the min of time - tm.Minute = virtualPosition; RTC.write(tm); lcd.setCursor(3, 0); printDigits(tm.Minute); // then re-print the min on the LCD } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); // SET THE QTY - Feed quantity lcd.blink(); // Turn on the blinking cursor: virtualPosition = feedQty; //needed or the qty will be zero. do { lcd.setCursor(14,0); // put cursor at QTY delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the feed qty feedQty = virtualPosition; lcd.setCursor(14, 0); lcd.print(feedQty); } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); // SET THE Feed1 Hour lcd.blink(); // Turn on the blinking cursor: virtualPosition = feed1hour; //needed or will be zero to start with. do { lcd.setCursor(2,1); // put cursor at feed1hour delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the feed1 hour feed1hour = virtualPosition; lcd.setCursor(2,1); printDigits(feed1hour); } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); // SET THE Feed1 Mins lcd.blink(); // Turn on the blinking cursor: virtualPosition = feed1minute; //needed or will be zero to start with. do { lcd.setCursor(5,1); // put cursor at feed1minute delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the feed1 minute feed1minute = virtualPosition; lcd.setCursor(5,1); printDigits(feed1minute); } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); // SET THE Feed2 Hour lcd.blink(); // Turn on the blinking cursor: virtualPosition = feed2hour; //needed or will be zero to start with. do { lcd.setCursor(10,1); // put cursor at feed1hour delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the feed1 hour feed2hour = virtualPosition; lcd.setCursor(10,1); printDigits(feed2hour); } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); // SET THE Feed2 Mins lcd.blink(); // Turn on the blinking cursor: virtualPosition = feed2minute; //needed or will be zero to start with. do { lcd.setCursor(13,1); // put cursor at feed1minute delay(500); // Delay needed or same button press will exit do-while as while is checking for another button push! if (TurnDetected) { // do this only if rotation was detected if (up) virtualPosition--; else virtualPosition++; TurnDetected = false; // do NOT repeat IF loop until new rotation detected } // Here I change the feed1 minute feed2minute = virtualPosition; lcd.setCursor(13,1); printDigits(feed2minute); } while ((digitalRead(PinSW))); lcd.noBlink(); delay(1000); } // end of main IF rotary encoder push button checker // CHECK FOR MANUAL FEED BUTTON buttonState = digitalRead(buttonPin); if (buttonState == HIGH) { feed(); } // CHECK FEEDING TIME AND FEED IF MATCHED if (tm.Hour == feed1hour && tm.Minute == feed1minute && tm.Second == 0) { // if I dont' check seconds are zero feed(); // then it'll feed continuously for 1 minute! } if (tm.Hour == feed2hour && tm.Minute == feed2minute && tm.Second == 0) { feed(); } } // End of main Loop void printDigits(int digits){ // utility function for digital clock display: prints leading 0 if(digits < 10) lcd.print('0'); lcd.print(digits); } void feed() { lcd.setCursor(17,0); lcd.print(" Meowwwww!"); for (int positionCounter = 0; positionCounter < 16; positionCounter++) { // scroll one position left: lcd.scrollDisplayLeft(); // wait a bit: delay(150); } // Stir servo section If you don't need a stir servo simply comment out all fo this until the Auger rotate section stirServo.attach(10); // I don't know if I need one either but I'm adding it now as it's easiest before I build it! for(pos = 0; pos =0; pos-=1) { stirServo.write(pos); delay(10); } delay(200); for(pos = 0; pos =0; pos-=1) { stirServo.write(pos); delay(5); } stirServo.detach(); // rotate the Auger feedServo.attach(PIN_SERVO); for (int cnt = 0; cnt < feedQty; cnt++) { feedServo.write(feedRate); //the feedrate is really the feed direction and rate. delay(600); //this delay sets how long the servo stays running from the previous command feedServo.write(feedReversal); //...until this command sets the servo a new task! delay(200); feedServo.write(feedRate); delay(600); feedServo.write(feedReversal); // if you want to increase the overall feedrate increase the forward delays (1000 at the moment) delay(200); // or better still just copy and past the forward & backwards code underneath to repeat } // that way the little reverse wiggle is always there to prevent jams feedServo.detach(); for (int positionCounter = 0; positionCounter < 16; positionCounter++) { // scroll one position left: lcd.scrollDisplayRight(); // wait a bit: delay(150); } }
Файлы
Шаг 6: Проводим апгрейд: замена серводвигателя подачи на шаговый двигатель NEMA 17
Проведем апгрейд нашей машины: заменим серводвигатель подачи на шаговый двигатель Nema 17, приводимый в движение при помощи драйвера EasyDriver.
Осуществить замену несложно. У шагового двигателя типа Nema 17 имеется 6 выводов. Определите, какие провода являются выводами катушки «A», а какие катушки «B» и игнорируйте остальные. Если у вашего двигателя четыре вывода, то вам будет проще.
Выполните подключение драйвера EasyDriver следующим образом:
- Плюсовой вывод подключается к +12 В источника питания.
- Провод «Земля» подключается к минусу.
- Пара проводов катушки «A» идет на контакты «A» драйвера.
- Аналогично, провода катушки «B» идут на контакты «B» драйвера.
- Контакт «Step» на плате драйвера подключите к цифровому выводу 13 на контроллере Arduino.
- Контакт «Dir» – к аналоговому выводу A0 на Arduino (этот вывод будет использоваться в качестве цифрового).
- Контакт «Enable» на плате EasyDriver подключите к выводу A1. С помощью этого вывода будет производиться включение и выключение питания драйвера для экономии электроэнергии.
Можете просто припаять провода непосредственно на платы драйвера и контроллера Arduino. Переподключать модуль часов реального времени не потребуется. Просто отключите серводвигатель, и подключите вместо него драйвер шагового двигателя EasyDriver.
После замены серводвигателя на шаговый, код программы нужно будет откорректировать. Для управления шаговым двигателем используется библиотека «Accellstepper». Адаптированный для шагового двигателя скетч, доступен для скачивания в конце этого шага. При подаче низкого уровня на вывод «Enable» драйвера, отключаются все выходы данного драйвера. Это позволяет экономить энергию, а также избежать нагрева платы драйвера. Поэтому имеет смысл использовать этот вывод.
Файлы