#include <Wire.h>
#include <EEPROM.h>
#include "LiquidCrystal_I2C_Menu.h"
LiquidCrystal_I2C_Menu lcd(0x27, 16, 2);
#define pinCLK 3
#define pinDT 2
#define pinSW 12
#define BUZZER 10
#define FEED_SPEED 5000
#define STEPS_FRW 19
#define STEPS_BKW 12
//const byte drvPins[] = {9, 4, 5, 11};
const byte drvPins[] = {9, 5, 4, 11};
// Таблица фаз шагов (полушаг)
const byte steps[] = {0b1010, 0b0110, 0b0101, 0b1001};
int feedDurationSec = 5;
int feedIntervalHours = 6;
int feedHour = 8;
int feedMinute = 0;
int feedIntervalMinutes = 360;
unsigned long lastFeedMillis = 0;
bool menuState = false;
enum {
mkBack, mkRoot,
mkFeedNow, mkSetDuration, mkSetInterval, mkSetFeedTime, mkAbout
};
void feedNow();
void setDuration();
void setInterval();
void setFeedTime();
void showAbout();
void playTone(int freq, int dur);
sMenuItem menu[] = {
{mkBack, mkRoot, "МЕНЮ", NULL}, // МЕНЮ
{mkRoot, mkFeedNow, "КОРМ ЩАC", feedNow}, // КОРМ ЩАС
{mkRoot, mkSetDuration, NULL, setDuration},
{mkRoot, mkSetInterval, NULL, setInterval},
// {mkRoot, mkSetFeedTime, NULL, setFeedTime},
{mkRoot, mkAbout, "ОБО МНЕ", showAbout}, // ОБО МНЕ
{mkRoot, mkBack, "НА3АД", NULL} // НАЗАД
};
uint8_t menuLen = sizeof(menu) / sizeof(sMenuItem);
// Один шаг мотора: dir = 1 (вперёд), dir = -1 (назад)
void runMotor(int8_t dir) {
static byte step = 0;
for (byte i = 0; i < 4; i++) {
digitalWrite(drvPins[i], bitRead(steps[step & 0b11], i));
}
delayMicroseconds(FEED_SPEED);
step += dir;
}
// Один цикл вращения порции (назад + вперёд)
void oneRev() {
for (int i = 0; i < STEPS_BKW; i++) runMotor(-1);
for (int i = 0; i < STEPS_FRW; i++) runMotor(1);
}
// Выключить питание мотора (удержание)
void disableMotor() {
for (byte i = 0; i < 4; i++) digitalWrite(drvPins[i], LOW);
}
int getItemIndexByKey(uint8_t key) {
for (uint8_t i = 0; i < menuLen; i++)
if (menu[i].key == key)
return i;
return -1;
}
void runMotorForDuration(unsigned long durationMs) {
unsigned long startTime = millis();
while (millis() - startTime < durationMs) {
for (int i = 0; i < STEPS_BKW; i++) runMotor(-1);
for (int i = 0; i < STEPS_FRW; i++) runMotor(1);
}
disableMotor();
}
void updateCaption(uint8_t key, const char* format, int value) {
uint8_t index = getItemIndexByKey(key);
char* buf = (char*) malloc(32);
sprintf(buf, format, value);
menu[index].caption = (char*) realloc(menu[index].caption, strlen(buf) + 1);
strcpy(menu[index].caption, buf);
free(buf);
}
void feedNow() {
lcd.ResetAllIndex();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("КОРМEШКА..."); // КОРМЁШКА...
runMotorForDuration(feedDurationSec * 1000);
lcd.clear();
lcd.print("CДЕЛАHHО!"); // СДЕЛАНО!
delay(1000);
}
void setDuration() {
lcd.ResetAllIndex();
feedDurationSec = lcd.inputVal<int>("BPEMЯ KOPM (C)", 1, 30, feedDurationSec); // ВРЕМЯ КОРМ
EEPROM.put(0, feedDurationSec);
updateCaption(mkSetDuration, "ДЛИT: %d C", feedDurationSec); // ДОЛГОТА
}
void setInterval() {
lcd.ResetAllIndex();
feedIntervalMinutes = lcd.inputVal<int>("ИНТЕРВАЛ (МИН)", 1, 1440, feedIntervalMinutes); // ИНТЕРВАЛ (МИН)
EEPROM.put(2, feedIntervalMinutes);
updateCaption(mkSetInterval, "ИНТЕРВАЛ: %d МИН", feedIntervalMinutes); // ИНТЕРВАЛ: МИН
}
void setFeedTime() {
lcd.ResetAllIndex();
feedHour = lcd.inputVal<int>("ЧАС (0-23)", 0, 23, feedHour); // ЧАС
feedMinute = lcd.inputVal<int>("МИНУТА (0-59)", 0, 59, feedMinute); // МИНУТА
EEPROM.put(4, feedHour);
EEPROM.put(6, feedMinute);
lcd.clear();
lcd.print("УСТАНОВ ВРЕМЯ:"); // УСТАНОВ ВРЕМЯ
lcd.setCursor(0, 1);
lcd.printf("%02d:%02d", feedHour, feedMinute);
delay(2000);
}
void showAbout() {
lcd.ResetAllIndex();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("АВТО-КОРМУШКА"); // АВТО-КОРМУШКА
lcd.setCursor(0, 1);
lcd.print("v1.0 - DIY");
delay(3000);
}
void LCDRepaint() {
lcd.ResetAllIndex();
lcd.clear();
lcd.printfAt(0, 0, "ДЛИТ: %d C", feedDurationSec); // ДОЛГОТА
unsigned long elapsed = millis() - lastFeedMillis;
unsigned long remainingMs = (unsigned long)feedIntervalMinutes * 60000UL;
unsigned long timeLeft = remainingMs - elapsed;
int mins = timeLeft / 60000;
int secs = (timeLeft % 60000) / 1000;
lcd.printfAt(0, 1, "CЛЕД КОРМ: %02d:%02d", mins, secs); // СЛЕД КОРМ
}
void setup() {
Serial.begin(9600);
lcd.begin();
lcd.attachEncoder(pinDT, pinCLK, pinSW);
pinMode(BUZZER, OUTPUT);
// Инициализация пинов мотора
for (byte i = 0; i < 4; i++) pinMode(drvPins[i], OUTPUT);
// Настройки
EEPROM.get(0, feedDurationSec);
if (feedDurationSec < 1 || feedDurationSec > 30) feedDurationSec = 5;
EEPROM.get(2, feedIntervalMinutes);
if (feedIntervalMinutes < 1 || feedIntervalMinutes > 1440) feedIntervalMinutes = 360;
updateCaption(mkSetInterval, "ИНТЕР: %d МИН", feedIntervalMinutes);
EEPROM.get(4, feedHour);
if (feedHour > 23) feedHour = 8;
EEPROM.get(6, feedMinute);
if (feedMinute > 59) feedMinute = 0;
updateCaption(mkSetDuration, "ДЛИТ: %d C", feedDurationSec);
updateCaption(mkSetInterval, "ИHTEPBAЛ: %d MИH", feedIntervalMinutes);
// Приветствие
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("КОРМУШКА");
lcd.setCursor(0, 1);
lcd.print("ГОТОВА!");
playTone(1000, 150);
delay(200);
playTone(1500, 150);
delay(1000);
LCDRepaint();
attachInterrupt(0, ttone, CHANGE);
attachInterrupt(1, ttone, CHANGE);
}
void loop() {
// Обработка энкодера
if (lcd.getEncoderState() == eButton) {
playTone(1200, 50);
menuState = true;
lcd.showMenu(menu, menuLen, 1);
menuState = false;
LCDRepaint(); // перерисовать после меню
}
// Обратный отсчёт: обновлять дисплей каждую секунду
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= 1000) {
lastUpdate = millis();
LCDRepaint(); // перерисовать экран
}
// Проверка времени на автокормление
if (millis() - lastFeedMillis >= (unsigned long)feedIntervalMinutes * 60000UL) {
feedNow(); // Подача корма
lastFeedMillis = millis();
}
}
// Функция воспроизведения звука
void playTone(int freq, int dur) {
tone(BUZZER, freq, dur);
delay(dur);
noTone(BUZZER);
}
void ttone() {
if(menuState) {
tone(BUZZER, 800, 20);
}
}