Умный кормушка для домашних животных на Arduino Nano — Соберите сами
Компоненты и материалы
| Arduino Nano R3 | 1 |
| Adafruit Real Time Clock (DS3231) | 1 |
| Сервопривод непрерывного вращения | 1 |
| Цереальный дозатор (сухой корм) | 1 |
| Датчик Холла униполярный | 1 |
| Стержневые магниты 3мм диаметр, 5мм длина | 6 |
| Adafruit RGB LCD 16x2 с подсветкой | 1 |
| I2C модуль для LCD (backpack) | 1 |
| Кнопка Apem momentary IP67 | 1 |
| Маленькие тактовые кнопки | 1 |
| Светодиоды (общие) | 6 |
| Корпус Hammond Multi ABS с фланцем | 1 |
Приложения и онлайн-сервисы
| LCD character generator |
О проекте
Обновление 2018-09-06
Мои две кормушки работают безупречно уже 7 месяцев. Мы заботимся о питомцах днём, но теперь они получают корм строго по расписанию. Очень доволен системой!
Зачем ещё одна кормушка?
Моя версия кормушки основана на микроконтроллере Arduino. Материалы относительно дешёвы, а сборка — проста!
Конечно, существует много вариантов, но я не нашёл готового проекта с нужными мне функциями, поэтому начал с нуля.
Функции:
- Точные порции каждый раз! (благодаря датчику Холла)
- Два времени кормления в сутки
- Чрезвычайно точные часы реального времени (только с оригинальным чипом DS3231)
- Отмена предстоящего кормления отдельно для обоих таймеров с индикацией на дисплее и LED. Отмена автоматически сбрасывается после наступления заданного времени.
- Ручное кормление (одна порция за нажатие кнопки)
- Настраиваемые порции для каждого из двух кормлений (1–9 порций)
- Обзор всех параметров на главном экране
- Простая навигация по меню
- LED-индикация успешного кормления
- Таймер подсветки LCD (выкл. через 30 сек, вкл. при нажатии любой кнопки)
- Время и настройки надежно хранятся в EEPROM
Видео-демонстрации
- Обзор: кликните сюда
- Подробное объяснение: кликните сюда
- Медленная съёмка работы: кликните сюда
Фотографии


Необходимые детали
- 1x Цереальный дозатор (дешево на eBay)
- Дерево (обрезки) для крепления дозатора
- 1x Arduino Uno (или Nano для компактности)
- 1x I2C DS3231 (экстремально точный) или DS1307 RTC
- 1x LCD дисплей 16 символов / 2 строки
- 1x I2C backpack для LCD
- 1x Сервопривод непрерывного вращения (купите качественный!)
- 1x Датчик Холла Honeywell SS443R униполярный
- 1x Блок питания 12V/1A (сетевой адаптер)
- 7x Тактовые кнопки momentary
- 6x LED (2 красных, 3 зелёных, 1 жёлтый)
- 2x резистор 1кОм (для красных LED)
- 4x резистор 560Ом (для зелёных и жёлтого LED)
- 1x Корпус для электроники
- Розетка и разъёмы для внешнего сервопривода и датчика Холла
- Много-много ТЕРМОКЛЕЯ.
Лучше использовать более высокое напряжение для сервопривода, если возможно. Больше мощь! Мой HSR-2645CRH выдерживает 7.4V, для этого нужно:
- Монтажная плата
- LM317T регулятор напряжения
- 3 резистора для установки выходного напряжения (1кОм, 150кОм, 4.7кОм)
- 2 полярных конденсатора (220мкФ, 10мкФ)
- Радиатор не нужен, сервопривод работает 1-2 секунды в день ;)

Онлайн есть калькуляторы LM317.
Контейнер для корма
Самые дешёвые и простые — céréal dispensers (дозаторы для сухих завтраков) в магазинах и на eBay. Купил двойной дозатор на eBay за 15 евро с доставкой (UK → Нидерланды).

Сложность — сделать крепление. В интернете не нашёл помощи, полез в сарай и нашёл красивые куски кедрового дерева.
Деревянная основа 3 см толщины — никакой качки! Отверстие под основание дозатора (у меня 86 мм) должно быть точным — лучше просверлить меньше и доработать, чем слишком большое.
Смотрите фото, чтобы понять принцип сборки.

















Сервопривод и крепление к палочке дозатора
Покупайте качественный сервопривод. Сначала взял MG996R и модифицировал для непрерывного вращения — деньги на ветер. Потом взял качественный HSR-2645CRH.
ОБЯЗАТЕЛЬНО модель непрерывного вращения!! Не пытайтесь модифицировать обычный сервопривод — плохо работает и не останавливается мгновенно.
Видео тестов сервопривода: кликните сюда
Крепление сервопривода к палочке дозатора
Казалось, самая сложная часть, но оказалась простой.
Взял кусок поликарбоната 5 мм, ножовкой вырезал диск ~6 см. В центре отверстие 8 мм (толщина оси дозатора. Пластиковая ось извлекается из палочки).

По краю 6 отверстий 3 мм под магниты. У палочки 6 резиновых лопастей — 6 магнитов, равномерно по 60°. Стержневые магниты вставляются плотно.

Датчик Холла фиксирует каждый из 6 магнитов — точно позиционируем и останавливаем палочку. Переменная задержки остановки сервопривода (servo stop delay) добавляет задержку после кормления, перед остановкой. Так можно точно настроить позицию палочки. Если «стоп» неверен, корм не выпадет полностью. Позиция датчика Холла может отличаться — подстройка задержки решит проблему.

Клеим ось палочки в диск поликарбоната.




Белый рог сервопривода болтится к диску 3 болтами M3. Используйте гайки как распорки и не затягивайте сильно!



Теперь сервопривод часть дозатора — устанавливаем в деревянное крепление и измеряем высоту подставки под сервопривод.



Датчик Холла
Использован униполярный датчик Холла. Выдаёт LOW при приближении магнита и НЕ требует обратной поляризации для сброса в HIGH. Униполярный — проще.
!!! Датчик Холла требует 10кОм подтягивающий резистор.
Припаял SMD 0805 резистор прямо на датчик, но можно и по-своему. Не забывайте этот резистор!
Корпус от старой шариковой ручки использован как держатель датчика.



Корпус электроники
Корпус Hammond ABS с фланцем. 120 x 65 x 40 мм.






Для внешних соединений — точные гнездовые разъёмы, не дешёвые китайские. Использую во всех проектах. Ничего хуже плохих контактов, вызывающих трудноуловимые сбои.












Для удобства прошивки Arduino — внешнее FTDI соединение:


Фронтальная панель
Пробовал много способов сделать дешёвую и красивую переднюю панель...
- Mod Podge для клеивания лазерной печати на корпус и покрытия сверху — ПРОВАЛ
- Прозрачный лак на основе полиуретана/акрилата. Очень прочный. Бумага отлично приклеилась, лакирование выглядело супер, НО не прилипало к тонеру лазерного принтера — ПРОВАЛ
- Лазерная печать передней панели, ламинация только верхней стороны PVC. Отличный вид, защита, красивые цвета. Потом клеил ламинированную бумагу на корпус лаком. Идея: лак пропиткает бумагу, делая её водостойкой. Но толстая бумага не пропускала лак, при тесте на воду бумага отслаивалась — ЧАСТИЧНЫЙ ПРОВАЛ, требует доработки
- Специальная алюминиевая фольга для лазерной печати. 2.25 евро/лист. Результат отличный, но хотел защитить тонер прозрачной фольей. Не получилось из-за дефектов (фото 1):

Потом попытка защитить лаком — фольга стала сверхглянцевой! НО... лак снова не прилипал к тонеру. Забил на лак навсегда...
ИТОГО... Использовал фольгу без защиты... Показывает отпечатки пальцев при частом контакте (см. под кнопкой BACK на фото вверху, корпус в корме для кур) — не идеально, но достаточно для меня.
Фото больше не загрузить, видимо...
Всё, ребята... удачи!
Код
Pet Feed-O-Matic v 1.1 - 20-02-2018Arduino
v 1.1: нажатие Cancel1 или Cancel2 показывает объяснение
// c++ stuff...
//#include <Arduino.h>
//#line 1
//#line 1 "/Users/Erik/Documents/PlatformIO/Projects/180202-151127-uno/src/Pet_Feeder_1_1_ENG.cpp"
/*
_ _ /\ | | (_) / \ _ __ __| |_ _ _ _ __ ___ / /\ \ | '__/ _` | | | | | '_ \ / _ \ / ____ \| | | (_| | |_| | | | | | (_) | /_/ \_\_| \__,_|\__,_|_|_| |_|\___/
_____ _ ______ _
| __ \ | | | ____| | | | |__) |__| |_ | |__ ___ ___ __| | ___ _ __
| ___/ _ \ __| | __/ _ \/ _ \/ _` |/ _ \ '__|
| | | __/ |_ | | | | __/ __/ (_| | __/ |
|_| \___|\__| |_| \___|\___|\__,_|\___|_|
Erik de Ruiter
-----------------------------------------------------------------------------
To do:
- remote feeding?
- feeder stop delay as menu editable item
last change: dinsdag 20 februari 2018 - 17:20:28
FEATURES:
- *Accurate portions* each time! (by using a Hall sensor)
- Two feeding times, once a day
- *Extremely* accurate Real Time Clock (Only with genuine DS3231 chip)
- Cancel upcoming feeding individually for both timers with display and Led indicator. Automatically reset after time has passed.
- Manual feed function (one portion per button press)
- Adjustable portions for each of the two feeding times (1-9 portions)
- Overview of all set parameters in the main screen
- Easy to navigate menu system
- LED indication if feeding was successful
- LCD backlight timer (off after 30 sec, on with any button press)
- Backup in the event of a failing Hall sensor
- Hass sensor LED will blink until midnight if the Hall sensor has failed
- Time and other settings are safely stored in EEPROM
WEBSITES:
LCD HD44780 character generator website to make your own lcd symbols
https://omerk.github.io/lcdchargen/
Arduino sketch Large letter comment generator
https://patorjk.com/software/taag/#p=display&c=c%2B%2B&f=Big&t=Comment
Revisions:
zondag 28 januari 2018 - 20:17:10
*/
////////////////////////////////////////////////////////////////////////////////
// USER CHANGEABLE VARIABLES
////////////////////////////////////////////////////////////////////////////////
// used for the Hall sensor fail backup function.
// the interval time should be somewhat larger than one 60 degree
// turn of the feeder (one portion)
#define HALL_SENSOR_BACKUP_VALUE 300
// delay before stopping the feeder servo after feeding. This way you can
// let the rotating peddle wheel stop at the right position
#define FEEDER_STOP_DELAY 100
// lcd backlight on time after pressing a button
#define LCD_BACKLIGHT_ON_TIME 30000
// exit menu without save after amount of time set here in ms
#define MENU_TIMEOUT_VALUE 7000
////////////////////////////////////////////////////////////////////////////////
// https:// github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
#include <LiquidCrystal_I2C.h>
// https:// github.com/JChristensen/Button
#include <Button.h>
// https:// github.com/JChristensen/DS3232RTC
#include <DS3232RTC.h>
// https:// www.arduino.cc/playground/Code/Time
#include <Time.h>
// https:// arduino.cc/en/Reference/Wire (included with Arduino IDE)
#include <Wire.h>
#include <EEPROM.h>
// CONNECTIONS:
//
// LCD (I2C module):
// SCL - A5
// SDA - A4
// VCC
// GND
// interrupt hal sensor pin
#define HALL_SENSOR_PIN 3
#define BUTTON_BACK_PIN 4
#define BUTTON_UP_PIN 5
#define BUTTON_DOWN_PIN 6
#define BUTTON_SELECT_PIN 7
#define BUTTON_CANCEL1_PIN 8
#define BUTTON_CANCEL2_PIN 9
#define BUTTON_MANUAL_PIN 10
#define LED_CANCEL1_PIN A0
#define LED_CANCEL2_PIN A1
#define LED_SUCCESS1_PIN A2
#define LED_SUCCESS2_PIN A3
// feeder Servo output Pin
#define SERVO_OUTPUT_PIN 12
// Hall sensor input Pin
#define LED_HALL_SENSOR_PIN 13
// address LCD(16x2) 0x27 or 0x3F
LiquidCrystal_I2C lcd(0x3F, 16, 2);
// define button library settings
// A debounce time of 20 milliseconds usually works well
// for tactile button switches.
#define DEBOUNCE_MS 20
// ms required before repeating on long press
#define REPEAT_FIRST 1000
// repeat interval for long press
#define REPEAT_INCR 200
// To keep things simple, we use the Arduino's internal pullup resistor.
#define PULLUP true
#define INVERT true
// Declare the buttons
Button buttonSelect (BUTTON_SELECT_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonUp (BUTTON_UP_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonDown (BUTTON_DOWN_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonBack (BUTTON_BACK_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonCancel1 (BUTTON_CANCEL1_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonCancel2 (BUTTON_CANCEL2_PIN, PULLUP, INVERT, DEBOUNCE_MS);
Button buttonManual (BUTTON_MANUAL_PIN, PULLUP, INVERT, DEBOUNCE_MS);
// The number that is adjusted
int count;
// Previous value of count (initialized to ensure it's different when
// the sketch starts)
int lastCount = -1;
// A variable time that is used to drive the repeats for long presses
unsigned long rpt = REPEAT_FIRST;
// used for the menu time-out
unsigned long timeoutValue = 0;
// manual cancel feeding times variables
boolean manualCancelFeed1 = false;
boolean manualCancelFeed2 = false;
// Manual feed option
boolean manualFeed = false;
// Feed amount (in portions)
int feedAmount1 = 1;
int feedAmount2 = 1;
bool feederSuccess = false;
// feeder portions
int portions = 0;
int turns = 0;
// input actions
enum {btnSELECT, btnUP, btnDOWN, btnBACK, btnCANCEL1, btnCANCEL2, btnMANUAL, trigTIMEOUT};
// States of the Finite State Machine (FSM)
enum STATES {
MAIN, MENU_EDIT_FEEDTIME1, MENU_EDIT_FEEDTIME2, MENU_EDIT_FEEDAMOUNT,
MENU_EDIT_TIME, MENU_EDIT_DATE, MENU_EDIT_SETTINGS,
EDIT_FEED_TIME1_HOUR, EDIT_FEED_TIME1_MINUTE, EDIT_FEED_TIME1_ON_OFF,
EDIT_FEED_TIME2_HOUR, EDIT_FEED_TIME2_MINUTE, EDIT_FEED_TIME2_ON_OFF,
EDIT_FEED_AMOUNT1, EDIT_FEED_AMOUNT2,
EDIT_HOUR, EDIT_MINUTE, EDIT_DAY, EDIT_MONTH, EDIT_YEAR,
EDIT_SERVO_STOP_DELAY, EDIT_SERVO_BACKUP_DELAY,
};
// Holds the current state of the system
STATES state;
int8_t userInput;
int8_t trigger;
int Second;
int Minute;
int Hour;
int Day;
int Month;
int Year;
int8_t DoW;
String day_of_week;
unsigned char address, data;
int testt = 0;
int feed_time1_hour;
int feed_time1_minute;
bool feed_time1_active = false;
bool alarm1Activated = false;
int feed_time2_hour;
int feed_time2_minute;
bool feed_time2_active = false;
bool alarm2Activated = false;
// used for the blink function when editing values
uint32_t blink_interval = 500;
uint32_t blink_previousMillis = 0;
uint32_t blink_currentMillis = 0;
boolean blink_state = false;
// used for the blink function when editing values
uint32_t spinningWheel_interval = 170;
uint32_t spinningWheel_previousMillis = 0;
uint32_t spinningWheel_currentMillis = 0;
int spinningWheelSymbol = 0;
// used for the Hall sensor fail backup function
// the interval time should be somewhat larger than one 60 degree
// turn of the feeder (one portion).
uint32_t hallSensorBackup_interval = HALL_SENSOR_BACKUP_VALUE;
uint32_t hallSensorBackup_currentMillis = 0;
boolean hallSensorFail = false;
// used for the lcd backlight timer
uint32_t lcdBacklight_interval = LCD_BACKLIGHT_ON_TIME;
uint32_t lcdBacklight_currentMillis = 0;
boolean RTC_error = true;
boolean long_press_button = false;
// Define custom symbols for the LCD display
byte bell_symbol_Char[8] = { B00100, B01110, B01110, B01110, B11111, B00100, B00000, B00000 };
byte inverted_one_Char[8] = { 0b11111, 0b11011, 0b10011, 0b11011, 0b11011, 0b11011, 0b10001, 0b11111 };
byte inverted_two_Char[8] = { 0b11111, 0b11011, 0b10101, 0b11101, 0b11011, 0b10111, 0b10001, 0b11111 };
byte arrow_up_Char[8] = { 0b00100, 0b01110, 0b11111, 0b01110, 0b01110, 0b01110, 0b01110, 0b00000 };
byte arrow_down_Char[8] = { 0b00000, 0b01110, 0b01110, 0b01110, 0b01110, 0b11111, 0b01110, 0b00100 };
byte inverted_p_Char[8] = { 0b11111, 0b10001, 0b10101, 0b10001, 0b10111, 0b10111, 0b11111, 0b00000 };
byte backslash_Char[8] = { 0b00000, 0b10000, 0b01000, 0b00100, 0b00010, 0b00001, 0b00000, 0b00000 };
byte thickDash_Char[8] = { 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b00000, 0b00000, 0b00000 };
volatile boolean hallSensorActivated = false;
// Interrupt 1
void HallSensorIsr() {
hallSensorActivated = true;
// turn on Hall sensor LED
digitalWrite(LED_HALL_SENSOR_PIN, HIGH);
}
// These lines are needed to make this sketch a C++ file.
// I use it because I edit the code with the Atom
// text editor and the PlatformIO add-on
void HallSensorIsr();
void setup();
void loop();
void change_states();
void check_inputs();
void transition(int trigger);
void check_alarm();
void check_manual_feed();
void display_menu_option_set_feedtime1();
void display_menu_option_set_feedtime2();
void display_menu_option_set_feed_amount();
void display_menu_option_set_time();
void display_menu_option_set_date();
void midnight_reset();
void display_time();
void displayFeedingAmouts();
void displayFeedingTimes();
void set_feedAmount();
void set_time();
void set_date();
void set_feeding1_time();
void set_feeding2_time();
void get_time();
void get_date();
void write_time();
void write_date();
void write_feeding_time1();
void write_feeding_time2();
void write_feedamount();
void get_feedamount();
void get_feed_time1();
void get_feed_time2();
void check_RTC();
byte decToBcd(byte val);
byte bcdToDec(byte val);
void leading_zero(int digits);
void blinkFunction();
void displaySpinningWheel();
void startFeederServo();
void stopFeederServo();
void activateFeeder(int portions);
void check_LcdBacklight();
void lcd_backlight_ON();
void ledsAndLcdDisplayStartup();
void hallSensorCheck();
#line 355
// ******************************************************************************
// SETUP
void setup() {
// activate the lcd screen
lcd.begin();
// turn lcd backlight on and start backlight off-timer
lcd_backlight_ON();
// start I2C
Wire.begin();
// hall sensor input to detect rotation of the feeder/feed amount output check pin
pinMode(HALL_SENSOR_PIN, INPUT_PULLUP);
pinMode(LED_HALL_SENSOR_PIN, OUTPUT);
pinMode(SERVO_OUTPUT_PIN, OUTPUT);
pinMode(LED_CANCEL1_PIN, OUTPUT);
pinMode(LED_CANCEL2_PIN, OUTPUT);
pinMode(LED_SUCCESS1_PIN, OUTPUT);
pinMode(LED_SUCCESS2_PIN, OUTPUT);
// set default state of LED's to OFF
digitalWrite(LED_CANCEL1_PIN, LOW);
digitalWrite(LED_CANCEL2_PIN, LOW);
digitalWrite(LED_SUCCESS1_PIN, LOW);
digitalWrite(LED_SUCCESS2_PIN, LOW);
digitalWrite(LED_HALL_SENSOR_PIN, LOW);
lcd.createChar(0, thickDash_Char);
lcd.createChar(1, bell_symbol_Char);
lcd.createChar(2, backslash_Char);
lcd.createChar(3, inverted_p_Char);
lcd.createChar(4, inverted_one_Char);
lcd.createChar(5, inverted_two_Char);
lcd.createChar(6, arrow_up_Char);
lcd.createChar(7, arrow_down_Char);
// set feeder Servo to default state OFF
stopFeederServo();
Wire.begin();
// set RTC as the Syncprovider
setSyncProvider(RTC.get);
// time in sec of resync with RTC
setSyncInterval(60);
// Disable the default square wave of the SQW pin.
RTC.squareWave(SQWAVE_NONE);
// Attach an interrupt on the Hall sensor (when input turns LOW)
// every 60 degree turn of the feeder shaft, the hall sensor
// should generate an interrupt
attachInterrupt(INT1, HallSensorIsr, FALLING);
// display test
ledsAndLcdDisplayStartup();
// Initial state of the FSM
state = MAIN;
// read the stored alarm value from the Arduino memory
get_feed_time1();
get_feed_time2();
}// End SETUP
// ******************************************************************************
// LOOP
void loop() {
// change states of FSM
change_states();
// check inputs (buttons)
check_inputs();
// check if alarm was called
check_alarm();
// check if manual feed was requested
check_manual_feed();
// at midnight, reset some variables
midnight_reset();
// check connection RTC
check_RTC();
// Check the Hall sensor function
hallSensorCheck();
// check if lcd backlight must be turned off
check_LcdBacklight();
}// End of LOOP
// ******************************************************************************
// ******************************************************************************
// ******************************************************************************
// ******************************************************************************
//******************************************************************************
// Finite State Machine
void change_states() {
// states
switch (state) {
//---------------------------------------
case MAIN:
display_time();
displayFeedingAmouts();
displayFeedingTimes();
break;
//---------------------------------------
case MENU_EDIT_FEEDTIME1:
display_menu_option_set_feedtime1();
break;
//---------------------------------------
case MENU_EDIT_FEEDTIME2:
display_menu_option_set_feedtime2();
break;
//---------------------------------------
case MENU_EDIT_FEEDAMOUNT:
display_menu_option_set_feed_amount();
break;
//---------------------------------------
case MENU_EDIT_TIME:
display_menu_option_set_time();
break;
//---------------------------------------
case MENU_EDIT_DATE:
display_menu_option_set_date();
break;
//---------------------------------------
case EDIT_FEED_TIME1_HOUR:
set_feeding1_time();
break;
//---------------------------------------
case EDIT_FEED_TIME1_MINUTE:
set_feeding1_time();
break;
//---------------------------------------
case EDIT_FEED_TIME1_ON_OFF:
set_feeding1_time();
break;
//---------------------------------------
case EDIT_FEED_TIME2_HOUR:
set_feeding2_time();
break;
//---------------------------------------
case EDIT_FEED_TIME2_MINUTE:
set_feeding2_time();
break;
//---------------------------------------
case EDIT_FEED_TIME2_ON_OFF:
set_feeding2_time();
break;
//---------------------------------------
case EDIT_FEED_AMOUNT1:
set_feedAmount();
break;
//---------------------------------------
case EDIT_FEED_AMOUNT2:
set_feedAmount();
break;
//---------------------------------------
case EDIT_HOUR:
set_time();
break;
//---------------------------------------
case EDIT_MINUTE:
set_time();
break;
//---------------------------------------
case EDIT_DAY:
set_date();
break;
//---------------------------------------
case EDIT_MONTH:
set_date();
break;
//---------------------------------------
case EDIT_YEAR:
set_date();
break;
//---------------------------------------
}
}
//******************************************************************************
// Check INPUTS
void check_inputs() {
// first check if timeout has occurred
if ( millis() - timeoutValue > MENU_TIMEOUT_VALUE ) {
userInput = trigTIMEOUT;
transition(userInput);
}
// check state of buttons
buttonSelect.read();
buttonUp.read();
buttonDown.read();
buttonBack.read();
buttonManual.read();
buttonCancel1.read();
buttonCancel2.read();
// check manual cancel Feed1 button
switch (buttonCancel1.wasPressed()) {
case 1:
// invert variable value (true to false and vise versa)
manualCancelFeed1 = !manualCancelFeed1;
//turn on lcd backlight manually
lcd_backlight_ON();
// message when Cancel1 button is pressed
if (manualCancelFeed1 == 1) {
lcd.clear();
lcd.setCursor(0, 0);
// 0123456789012345 - LCD screen character counter
lcd.print("Upcoming Feed #1");
lcd.setCursor(0, 1);
lcd.print("cancelled once ");
delay(2000);
lcd.clear();
} else if (manualCancelFeed1 == 0) {
lcd.clear();
lcd.setCursor(0, 0);
// 0123456789012345 - LCD screen character counter
lcd.print("Upcoming Feed #1");
lcd.setCursor(0, 1);
lcd.print("canceling undone");
delay(2000);
lcd.clear();
}
break;
}
// check manual cancel Feed2 button
switch (buttonCancel2.wasPressed()) {
case 1:
// invert variable value (true to false and vise versa)
manualCancelFeed2 = !manualCancelFeed2;
//turn on lcd backlight manually
lcd_backlight_ON();
// message when Cancel1 button is pressed
if (manualCancelFeed2 == 1) {
lcd.clear();
lcd.setCursor(0, 0);
// 0123456789012345 - LCD screen character counter
lcd.print("Upcoming Feed #2");
lcd.setCursor(0, 1);
lcd.print("cancelled once ");
delay(2000);
lcd.clear();
} else if (manualCancelFeed2 == 0) {
lcd.clear();
lcd.setCursor(0, 0);
// 0123456789012345 - LCD screen character counter
lcd.print("Upcoming Feed #2");
lcd.setCursor(0, 1);
lcd.print("canceling undone");
delay(2000);
lcd.clear();
}
break;
}
// check manual Feed button
switch (buttonManual.wasPressed()) {
case 1:
manualFeed = true;
//turn on lcd backlight manually
lcd_backlight_ON();
break;
}
// check MENU/SELECT button
switch (buttonSelect.wasPressed()) {
case 1:
userInput = btnSELECT;
//turn on lcd backlight manually
lcd_backlight_ON();
transition(userInput);
break;
}
// check UP button
switch (buttonUp.wasPressed()) {
case 1:
userInput = btnUP;
transition(userInput);
//turn on lcd backlight manually
lcd_backlight_ON();
break;
}
// check long press UP button
switch (buttonUp.wasReleased()) {
case 1:
long_press_button = false;
rpt = REPEAT_FIRST;
break;
}
switch (buttonUp.pressedFor(rpt)) {
case 1:
// increment the long press interval
rpt += REPEAT_INCR;
long_press_button = true;
userInput = btnUP;
transition(userInput);
break;
}
// check DOWN button
switch (buttonDown.wasPressed()) {
case 1:
userInput = btnDOWN;
transition(userInput);
//turn on lcd backlight manually
lcd_backlight_ON();
break;
}
// check long press DOWN button
switch (buttonDown.wasReleased()) {
case 1:
long_press_button = false;
rpt = REPEAT_FIRST;
break;
}
switch (buttonDown.pressedFor(rpt)) {
case 1:
// increment the long press interval
rpt += REPEAT_INCR;
long_press_button = true;
userInput = btnDOWN;
transition(userInput);
break;
}
// check btnBACK button
switch (buttonBack.wasPressed()) {
case 1:
userInput = btnBACK;
transition(userInput);
//turn on lcd backlight manually
lcd_backlight_ON();
break;
}
}
//******************************************************************************
// Check for state transition trigger
void transition(int trigger) {
switch (state) {
//---------------------------------------
case MAIN:
// set time-out timer
timeoutValue = millis();
if (trigger == btnSELECT) {
lcd.clear();
state = MENU_EDIT_FEEDTIME1;
} else if (trigger == btnBACK) {
//lcd.clear();
//state = ALARM1_AND_2_TIME;
}
break;
//---------------------------------------
case MENU_EDIT_FEEDTIME1:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers
if (trigger == btnUP) {
//no action, this is the first menu
} else if (trigger == btnDOWN) {
lcd.clear();
state = MENU_EDIT_FEEDTIME2;
}
if (trigger == btnSELECT) {
lcd.clear();
state = EDIT_FEED_TIME1_HOUR;
}
if (trigger == btnBACK) {
lcd.clear();
state = MAIN;
}
break;
//---------------------------------------
case MENU_EDIT_FEEDTIME2:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers
if (trigger == btnUP) {
lcd.clear();
state = MENU_EDIT_FEEDTIME1;
} else if (trigger == btnDOWN) {
lcd.clear();
state = MENU_EDIT_FEEDAMOUNT;
}
if (trigger == btnSELECT) {
lcd.clear();
state = EDIT_FEED_TIME2_HOUR;
}
if (trigger == btnBACK) {
lcd.clear();
state = MAIN;
}
break;
//---------------------------------------
case MENU_EDIT_FEEDAMOUNT:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers
if (trigger == btnUP) {
lcd.clear();
state = MENU_EDIT_FEEDTIME2;
} else if (trigger == btnDOWN) {
lcd.clear();
state = MENU_EDIT_TIME;
}
if (trigger == btnSELECT) {
lcd.clear();
state = EDIT_FEED_AMOUNT1;
}
if (trigger == btnBACK) {
lcd.clear();
state = MAIN;
}
break;
//---------------------------------------
case MENU_EDIT_TIME:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers
if (trigger == btnUP) {
lcd.clear();
state = MENU_EDIT_FEEDTIME2;
}
if (trigger == btnDOWN) {
lcd.clear();
state = MENU_EDIT_DATE;
}
if (trigger == btnSELECT) {
lcd.clear();
state = EDIT_HOUR;
}
if (trigger == btnBACK) {
lcd.clear();
state = MAIN;
}
break;
//---------------------------------------
case MENU_EDIT_DATE:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers
if (trigger == btnUP) {
lcd.clear();
state = MENU_EDIT_TIME;
} else if (trigger == btnDOWN) {
//no action, end of menu items!
}
if (trigger == btnSELECT) {
lcd.clear();
state = EDIT_DAY;
}
if (trigger == btnBACK) {
lcd.clear();
state = MAIN;
}
break;
//---------------------------------------
case EDIT_FEED_TIME1_HOUR:
// set time-out timer
timeoutValue = millis();
// check for time-out 'button' trigger
if (trigger == trigTIMEOUT) {
lcd.clear();
state = MAIN;
}
// Now check for button triggers ... This file has been truncated, please download it to see its full contents.
Схемы
Arduino based Pet Feeder

Использован Arduino IDE 1.8.2. ОСТОРОЖНО: НЕ ДОЛЖНО БЫТЬ ДВУХ ВЕРСИЙ ОДНОЙ БИБЛИОТЕКИ В ПАПКЕ LIBRARIES!!
arduino_pet_feeder_final_eng_v1_0-ino_hex_libraries_dsoo0HscCr.zip
Производственный процесс
- Производство кормов для домашних животных: от ингредиентов к регулированию
- DIY-консоль управления Photoshop на Arduino Nano RP2040 Connect
- Сборка Bluetooth-робота-шпиона на Arduino с управлением с телефона
- Набор Arduino Nano: CrazyCircuits, датчики DFRobot и инструменты — создавайте проекты с уверенностью
- Tetris на Arduino Nano: самостоятельная сборка 16×8 LED-матрицы на 74HC595
- Собираем свой Tamagotchi на Arduino: цифровой питомец своими руками
- Управление двумя шаговыми двигателями через джойстик на Arduino Nano — простой урок (Visuino)
- Удаленное кормление питомца: Arduino-кормушка с ИК-пультом
- Ручной дозиметр (Гейгер-счетчик) на Arduino Nano: сборка в 3D-корпусе
- TFT Shield 2.8" 320×240 SPI для Arduino Nano — высокопроизводительный дисплей