#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>
// --- PIN DEFINITIONS ---
const int STEP_PIN = 14;
const int DIR_PIN = 12;
const int RELAY_PIN = 27;
const int POT_AMOUNT = 32;
const int POT_INTERVAL = 33;
const int BTN_LOCK = 4;
const int TRIG_WATER = 26;
const int ECHO_WATER = 25;
const int TRIG_FOOD = 5;
const int ECHO_FOOD = 18;
// --- CONSTANTS ---
const int WATER_EMPTY_DIST = 20; // cm (Empty)
const int WATER_FULL_DIST = 5; // cm (Full)
const int STEPS_PER_REV = 200;
const int FOOD_LOW_DIST = 18;
// --- GLOBAL STATE ---
bool isLocked = false;
long targetSteps = 0;
unsigned long feedIntervalMs = 0;
unsigned long lastFeedTime = 0;
unsigned long lastWaterCheck = 0;
float currentWaterDist = 0;
float currentFoodDist = 0;
// Objects
AccelStepper stepper(1, STEP_PIN, DIR_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
// LCD Initialization
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("CAT FEEDER 3000");
// Pin Config
pinMode(TRIG_WATER, OUTPUT);
pinMode(ECHO_WATER, INPUT);
pinMode(RELAY_PIN, OUTPUT);
pinMode(BTN_LOCK, INPUT_PULLUP);
pinMode(TRIG_FOOD, OUTPUT);
pinMode(ECHO_FOOD, INPUT);
digitalWrite(RELAY_PIN, LOW);
// Stepper Config
stepper.setMaxSpeed(1000);
stepper.setAcceleration(500);
delay(2000);
lcd.clear();
}
float getDistance(int trig, int echo) {
digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
long duration = pulseIn(echo, HIGH, 30000);
if (duration == 0) return 999;
return duration * 0.034 / 2;
}
void handleWaterSystem() {
currentWaterDist = getDistance(TRIG_WATER, ECHO_WATER);
if (currentWaterDist >= WATER_EMPTY_DIST) {
digitalWrite(RELAY_PIN, HIGH); // Start filling
}
else if (currentWaterDist <= WATER_FULL_DIST) {
digitalWrite(RELAY_PIN, LOW); // Tank is full
}
currentFoodDist = getDistance(TRIG_FOOD, ECHO_FOOD);
}
// 2. Physical UI Logic (Button + Pots)
void handlePhysicalControls() {
static bool lastBtnState = HIGH;
static unsigned long lastDebounceTime = 0;
bool reading = digitalRead(BTN_LOCK);
if (reading != lastBtnState) lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > 50) {
static bool steadyState = HIGH;
if (reading != steadyState) {
steadyState = reading;
if (steadyState == LOW) {
isLocked = !isLocked;
}
}
}
lastBtnState = reading;
if (!isLocked) {
// Pot 1: Amount (0 to 1000 steps)
targetSteps = map(analogRead(POT_AMOUNT), 0, 4095, 0, 1000);
// Pot 2: Interval (1 to 24 hours)
int hours = map(analogRead(POT_INTERVAL), 0, 4095, 1, 24);
int seconds = map(analogRead(POT_INTERVAL), 0, 4095, 1, 60);
feedIntervalMs = (unsigned long)seconds * 1000; //* 3600000;
}
}
void updateDisplay() {
static unsigned long lastLCDUpdate = 0;
if (millis() - lastLCDUpdate < 500) return; // Refresh rate: 2Hz
lastLCDUpdate = millis();
lcd.setCursor(0, 0);
lcd.print("W:");
if (digitalRead(RELAY_PIN)) lcd.print("FILL");
else lcd.print(" OK ");
lcd.setCursor(8, 0);
lcd.print("F:");
if (currentFoodDist > FOOD_LOW_DIST) lcd.print("LOW! ");
else lcd.print("OK ");
lcd.setCursor(0, 1);
lcd.print(isLocked ? "L" : "S");
lcd.print(" A:");
lcd.print(targetSteps);
lcd.setCursor(9, 1);
lcd.print("T:");
lcd.print(feedIntervalMs / 1000);
lcd.print("s ");
}
void loop() {
// Check button and pots
handlePhysicalControls();
// Manage Water every 2 seconds
if (millis() - lastWaterCheck > 2000) {
handleWaterSystem();
lastWaterCheck = millis();
}
// Update LCD
updateDisplay();
// Automatic Feeding Logic
if (feedIntervalMs > 0 && (millis() - lastFeedTime > feedIntervalMs)) {
if (stepper.distanceToGo() == 0) {
stepper.move(targetSteps);
lastFeedTime = millis();
}
}
// Run Stepper Motor
stepper.run();
}LOCK/ UNLOCK
PERIOD
AMOUNT