/*
* PROFESYONEL KULUÇKA MAKİNESİ KONTROL SİSTEMİ - Rev 3.2 I2C & Joystick Mod
* LCD I2C Kullanımı, Joystick Navigasyon ve Tarih Ayarı
*/
#include <DHT.h>
#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>
#include <PID_v1.h>
#include <EEPROM.h>
// ==================== BUTON ENUM ====================
enum Button {
BTN_NONE,
BTN_RIGHT,
BTN_UP,
BTN_DOWN,
BTN_LEFT,
BTN_SELECT
};
// ==================== DONANIM TANIMLARI ====================
#define DHTPIN A1
#define DHTTYPE DHT22
#define JOY_X A0
#define JOY_Y A2
#define JOY_SW 2
#define FAN_PIN 3
#define MOTOR_PIN 4
#define RELAY_HEAT 11
#define RELAY_HUMID 12
#define RELAY_COOL 13
#define BUZZER_PIN 5
// ==================== PID YAPILANDIRMA ====================
struct PIDConfig { double Kp, Ki, Kd; };
const PIDConfig TEMP_PID[4] = {
{1.8, 0.4, 0.9},
{2.0, 0.5, 1.1},
{2.2, 0.3, 0.8},
{1.5, 0.6, 1.0}
};
const PIDConfig HUM_PID[4] = {
{1.2, 0.2, 0.5},
{1.5, 0.3, 0.7},
{1.8, 0.4, 0.6},
{2.0, 0.5, 0.9}
};
// ==================== TÜRLER ====================
struct SpeciesConfig {
const char* name;
int incubationDays;
float tNMin, tNMax, tFMin, tFMax;
float hNMin, hNMax, hFMin, hFMax;
int rotIntH, rotDurM, fanSpeed, coolingDay;
};
const SpeciesConfig SPECIES[4] = {
{"Tavuk", 21, 37.5, 38.0, 37.2, 37.5, 50, 60, 70, 75, 3, 5, 200, -1},
{"Hindi", 28, 37.3, 37.6, 37.0, 37.3, 55, 65, 75, 80, 4, 7, 220, -1},
{"Bıldırcın",17, 37.7, 38.0, 37.5, 37.7, 55, 60, 70, 75, 2, 3, 255, -1},
{"Kaz", 30, 37.3, 37.6, 36.9, 37.2, 60, 65, 80, 85, 6,10, 255, 25}
};
// ==================== DURUM ====================
struct State {
uint8_t species;
DateTime startTime;
unsigned long lastRot;
unsigned long rotStart;
bool rotActive;
} state;
// ==================== GLOBAL DEĞİŞKENLER ====================
double setTemp, curTemp, outHeat;
double setHum, curHum, outHum;
bool alarmActive = false;
LiquidCrystal_I2C lcd(0x27, 16, 2);
DHT dht(DHTPIN, DHTTYPE);
RTC_DS3231 rtc;
PID pidT(&curTemp, &outHeat, &setTemp, TEMP_PID[0].Kp, TEMP_PID[0].Ki, TEMP_PID[0].Kd, DIRECT);
PID pidH(&curHum, &outHum, &setHum, HUM_PID[0].Kp, HUM_PID[0].Ki, HUM_PID[0].Kd, DIRECT);
// ==================== MENÜ ====================
enum MenuItem {
HOME,
MENU_SPECIES,
ROT_INT,
ROT_DUR,
FAN,
DATE,
PID_INFO
};
const char* MENU[] = {"AnaEkran","Tur","Arlk","Sure","Fan","Tarih","PID"};
MenuItem curMenu = HOME;
bool editing = false;
enum DateField { DF_DAY, DF_MON, DF_YEAR };
DateField df = DF_DAY;
enum Addr { A_SP = 0, A_Y = 10, A_M = 11, A_D = 12 };
// ==================== PROTOTİPLER ====================
void initHW();
void loadS();
void saveS();
void readSensors();
void calcPID();
void ctrlOut();
void handleRot();
void checkAlarms();
void draw();
void nav(Button b);
void edit(Button b);
void editDate(Button b);
int daysLeft();
Button readBtn();
void setDate();
// ==================== SETUP & LOOP ====================
void setup() {
initHW();
loadS();
pidT.SetMode(AUTOMATIC);
pidH.SetMode(AUTOMATIC);
}
void loop() {
Button b = readBtn();
if (!editing) nav(b);
else edit(b);
readSensors();
calcPID();
ctrlOut();
handleRot();
checkAlarms();
draw();
delay(200);
}
// ==================== DONANIM BAŞLAT ====================
void initHW() {
Wire.begin();
lcd.begin(16, 2);
lcd.backlight();
dht.begin();
if (!rtc.begin()) {
lcd.clear();
lcd.print("RTC HATA");
while (1);
}
pinMode(JOY_SW, INPUT_PULLUP);
pinMode(RELAY_HEAT, OUTPUT);
pinMode(RELAY_HUMID, OUTPUT);
pinMode(RELAY_COOL, OUTPUT);
pinMode(MOTOR_PIN, OUTPUT);
pinMode(FAN_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(MOTOR_PIN, LOW);
analogWrite(FAN_PIN, 0);
digitalWrite(BUZZER_PIN, LOW);
}
// ==================== EEPROM YÜKLE & KAYDET ====================
void loadS() {
uint8_t sp = EEPROM.read(A_SP);
if (sp < 4) state.species = sp;
int y = EEPROM.read(A_Y) + 2000;
int m = EEPROM.read(A_M);
int d = EEPROM.read(A_D);
if (y > 2000 && m >= 1 && m <= 12 && d >= 1 && d <= 31) {
state.startTime = DateTime(y, m, d, 0, 0, 0);
} else {
state.startTime = rtc.now();
saveS();
}
}
void saveS() {
EEPROM.update(A_SP, state.species);
EEPROM.update(A_Y, state.startTime.year() - 2000);
EEPROM.update(A_M, state.startTime.month());
EEPROM.update(A_D, state.startTime.day());
}
int daysLeft() {
long diff = rtc.now().unixtime() - state.startTime.unixtime();
int rem = SPECIES[state.species].incubationDays - diff / 86400;
return rem < 0 ? 0 : rem;
}
// ==================== SENSOR OKUMA ====================
void readSensors() {
static float lastT = 0, lastH = 0;
float t = dht.readTemperature();
float h = dht.readHumidity();
curTemp = isnan(t) ? lastT : (lastT = t);
curHum = isnan(h) ? lastH : (lastH = h);
}
// ==================== PID HESAPLAMA ====================
void calcPID() {
auto &cfg = SPECIES[state.species];
bool finalPhase = (daysLeft() <= 3);
pidT.SetTunings(
TEMP_PID[state.species].Kp,
TEMP_PID[state.species].Ki,
TEMP_PID[state.species].Kd
);
pidH.SetTunings(
HUM_PID[state.species].Kp,
HUM_PID[state.species].Ki,
HUM_PID[state.species].Kd
);
setTemp = finalPhase
? (cfg.tFMin + cfg.tFMax) / 2
: (cfg.tNMin + cfg.tNMax) / 2;
setHum = finalPhase
? (cfg.hFMin + cfg.hFMax) / 2
: (cfg.hNMin + cfg.hNMax) / 2;
pidT.Compute();
pidH.Compute();
}
// ==================== ÇIKIŞ KONTROL ====================
void ctrlOut() {
auto &cfg = SPECIES[state.species];
analogWrite(FAN_PIN, cfg.fanSpeed);
digitalWrite(RELAY_HEAT, outHeat > 0);
digitalWrite(RELAY_HUMID, outHum > 0);
digitalWrite(
RELAY_COOL,
(state.species == 3 && daysLeft() == cfg.coolingDay)
);
}
// ==================== DÖNME İŞLEMİ ====================
void handleRot() {
auto &cfg = SPECIES[state.species];
if (daysLeft() <= 3) return;
unsigned long now = millis();
if (!state.rotActive && now - state.lastRot > cfg.rotIntH * 3600000UL) {
state.rotActive = true;
state.rotStart = now;
state.lastRot = now;
digitalWrite(MOTOR_PIN, HIGH);
}
if (state.rotActive && now - state.rotStart > cfg.rotDurM * 60000UL) {
state.rotActive = false;
digitalWrite(MOTOR_PIN, LOW);
}
}
// ==================== ALARM KONTROL ====================
void checkAlarms() {
auto &cfg = SPECIES[state.species];
bool finalPhase = (daysLeft() <= 3);
float tMin = finalPhase ? cfg.tFMin : cfg.tNMin;
float tMax = finalPhase ? cfg.tFMax : cfg.tNMax;
float hMin = finalPhase ? cfg.hFMin : cfg.hNMin;
float hMax = finalPhase ? cfg.hFMax : cfg.hNMax;
bool tAlarm = (curTemp < tMin - 0.8 || curTemp > tMax + 0.8);
bool hAlarm = (curHum < hMin - 8 || curHum > hMax + 8);
if (daysLeft() == 0) tAlarm = true;
alarmActive = tAlarm || hAlarm;
digitalWrite(BUZZER_PIN, alarmActive);
}
// ==================== LCD GÖSTERİM ====================
void draw() {
lcd.clear();
if (curMenu == HOME) {
lcd.setCursor(0, 0);
lcd.print(curTemp, 1);
lcd.write(223);
lcd.print("C ");
lcd.print((int)curHum);
lcd.print("% G");
lcd.print(daysLeft());
lcd.setCursor(0, 1);
if (alarmActive) lcd.print("!ALARM!");
} else {
lcd.setCursor(0, 0);
lcd.print(MENU[curMenu]);
if (editing) {
lcd.setCursor(15, 0);
lcd.print('*');
}
lcd.setCursor(0, 1);
switch (curMenu) {
case MENU_SPECIES:
lcd.print(SPECIES[state.species].name);
break;
case ROT_INT:
lcd.print(SPECIES[state.species].rotIntH);
lcd.print("h");
break;
case ROT_DUR:
lcd.print(SPECIES[state.species].rotDurM);
lcd.print("m");
break;
case FAN:
lcd.print(map(
SPECIES[state.species].fanSpeed,
0, 255, 0, 100
));
lcd.print("%");
break;
case DATE: {
auto dt = state.startTime;
lcd.print(dt.day());
lcd.print('/');
lcd.print(dt.month());
lcd.print('/');
lcd.print(dt.year());
} break;
case PID_INFO:
lcd.print("KP:"); lcd.print(TEMP_PID[state.species].Kp);
lcd.setCursor(0, 1);
lcd.print("KI:"); lcd.print(TEMP_PID[state.species].Ki);
lcd.print(" KD:"); lcd.print(TEMP_PID[state.species].Kd);
break;
}
}
}
// ==================== MENÜ NAVİGASYON ====================
void nav(Button b) {
if (b == BTN_UP) curMenu = (curMenu == HOME ? PID_INFO : MenuItem(curMenu - 1));
if (b == BTN_DOWN) curMenu = (curMenu == PID_INFO ? HOME : MenuItem(curMenu + 1));
if (b == BTN_SELECT && curMenu != HOME) {
editing = true;
if (curMenu == DATE) df = DF_DAY;
}
}
// ==================== MENÜ DÜZENLEME ====================
void edit(Button b) {
if (curMenu == DATE) editDate(b);
else if (curMenu == MENU_SPECIES && (b == BTN_LEFT || b == BTN_RIGHT)) {
state.species = (state.species + (b==BTN_LEFT?3:1)) % 4;
state.startTime = rtc.now();
saveS();
}
if (b == BTN_SELECT) {
editing = false;
if (curMenu == MENU_SPECIES || curMenu == DATE) saveS();
}
}
// ==================== BUTON OKUMA ====================
Button readBtn() {
int x = analogRead(JOY_X);
int y = analogRead(JOY_Y);
if (digitalRead(JOY_SW) == LOW) return BTN_SELECT;
if (x < 300) return BTN_LEFT;
if (x > 700) return BTN_RIGHT;
if (y < 300) return BTN_UP;
if (y > 700) return BTN_DOWN;
return BTN_NONE;
}
// ==================== TARİH AYARLAMA HELPER ====================
void editDate(Button b) {
int y = state.startTime.year();
int m = state.startTime.month();
int d = state.startTime.day();
if (b == BTN_LEFT) df = DateField((df + 2) % 3);
else if (b == BTN_RIGHT) df = DateField((df + 1) % 3);
else if (b == BTN_UP || b == BTN_DOWN) {
int delta = (b == BTN_UP ? 1 : -1);
if (df == DF_DAY) d = constrain(d + delta, 1, 31);
else if (df == DF_MON) m = constrain(m + delta, 1, 12);
else y = constrain(y + delta, 2000, 2099);
state.startTime = DateTime(y,m,d,0,0,0);
}
}
// ==================== TARİH AYARLAMA FONKSİYONU ====================
void setDate() {
int y = state.startTime.year();
int m = state.startTime.month();
int d = state.startTime.day();
int s = 0; // 0=gün,1=ay,2=yıl
while (true) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Tarih Ayari:");
lcd.setCursor(0,1);
lcd.print(s==0 ? ">" : " "); lcd.print(d<10?"0":""); lcd.print(d); lcd.print("/");
lcd.print(s==1 ? ">" : " "); lcd.print(m<10?"0":""); lcd.print(m); lcd.print("/");
lcd.print(s==2 ? ">" : " "); lcd.print(y);
Button btn = readBtn();
if (btn==BTN_UP||btn==BTN_DOWN||btn==BTN_LEFT||btn==BTN_RIGHT)
editDate(btn);
else if (btn==BTN_SELECT) {
s++;
if (s>2) {
rtc.adjust(DateTime(y,m,d,0,0,0));
state.startTime = DateTime(y,m,d,0,0,0);
saveS();
editing = false;
break;
}
}
delay(200);
}
}