// Gerekli Kütüphaneler
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <max6675.h>
#include <PID_v1.h>
#include <EEPROM.h>
// --- Pin Tanımlamaları ---
// MAX6675 Pinleri (Donanımsal SPI için SCK, CS, SO sabit pinlerdir, yazılımsal SPI kullanılabilir)
const int thermoDO = 8; // SO
const int thermoCS = 9; // CS
const int thermoCLK = 10; // SCK
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
// Buton Pinleri
const int startButtonPin = 2;
const int settingsButtonPin = 3;
const int upButtonPin = 4;
const int downButtonPin = 5;
// Çıkış Pinleri
const int ssrPin = 6;
const int buzzerPin = 7;
// --- LCD Ayarları ---
LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C adresi ve LCD boyutları (0x27 veya 0x3F olabilir)
// --- PID Değişkenleri ---
double pidSetpoint, pidInput, pidOutput;
// Bu Kp, Ki, Kd değerleri sisteminize göre ayarlanmalıdır!
double Kp = 2.0, Ki = 0.5, Kd = 1.0;
PID myPID(&pidInput, &pidOutput, &pidSetpoint, Kp, Ki, Kd, DIRECT);
unsigned long pidWindowSize = 5000; // PID çıkışının uygulanacağı zaman penceresi (ms)
unsigned long pidWindowStartTime;
// --- Proses Adımları için Veri Yapısı ---
struct ProfileStep {
double targetTemperature;
unsigned int durationSeconds; // Saniye cinsinden süre
};
const int NUM_STEPS = 10;
ProfileStep steps[NUM_STEPS];
ProfileStep tempSteps[NUM_STEPS]; // Ayarlar menüsünde geçici olarak kullanılacak
// --- Durum Değişkenleri ---
enum SystemState {
STATE_IDLE,
STATE_RUNNING,
STATE_SETTINGS_STEP_SELECT,
STATE_SETTINGS_TEMP,
STATE_SETTINGS_DURATION,
STATE_PROCESS_COMPLETE,
STATE_THERMOCOUPLE_ERROR,
STATE_TOO_HOT
};
SystemState currentState = STATE_IDLE;
SystemState previousStateDisplay = STATE_IDLE; // Ekran yenileme optimizasyonu için
int currentStepIndex = 0;
unsigned long stepStartTime = 0;
unsigned long processElapsedTime = 0; // Prosesin toplam geçen süresi
unsigned long stepRemainingTime = 0; // Adımın kalan süresi
// --- Ayarlar Menüsü Değişkenleri ---
int selectedStepForEditing = 0;
// --- Buton Durumları ve Debounce ---
unsigned long lastDebounceTime[4] = {0}; // 0:start, 1:ayar, 2:yukarı, 3:aşağı
bool lastButtonState[4] = {HIGH, HIGH, HIGH, HIGH};
bool currentButtonState[4] = {HIGH, HIGH, HIGH, HIGH};
const unsigned long debounceDelay = 50;
// --- Ekran Güncelleme Optimizasyonu için Önceki Değerler ---
char lcdLine0[21] = "";
char lcdLine1[21] = "";
char lcdLine2[21] = "";
char lcdLine3[21] = "";
double prevDisplayedTemp = -100;
double prevDisplayedTargetTemp = -100;
unsigned long prevDisplayedElapsedTime = 0xFFFF;
unsigned long prevDisplayedRemainingTime = 0xFFFF;
int prevDisplayedStep = -1;
// --- SSR Kontrolü için Zamanlayıcılar ---
unsigned long ssrControlPeriodStart = 0;
bool ssrControlState = LOW; // Rölenin mevcut mantıksal durumu (AÇIK/KAPALI)
unsigned long ssrOnTime = 0;
unsigned long ssrOffTime = 0;
// --- Buzzer Kontrolü ---
unsigned long buzzerStartTime = 0;
bool buzzerActive = false;
unsigned long buzzerPatternOnTime = 1000; // ms
unsigned long buzzerPatternOffTime = 1000; // ms
int buzzerBeepCount = 0;
const int buzzerEndBeeps = 15;
// --- EEPROM Adresleri ---
const int EEPROM_MAGIC_NUMBER_ADDR = 0;
const int EEPROM_STEPS_ADDR = sizeof(int); // Magic number'dan sonra
const int EEPROM_PID_ADDR = EEPROM_STEPS_ADDR + sizeof(steps);
const int EEPROM_MAGIC_NUMBER = 0xABCD; // EEPROM'da veri olup olmadığını kontrol etmek için
void setup() {
Serial.begin(9600);
pinMode(startButtonPin, INPUT_PULLUP);
pinMode(settingsButtonPin, INPUT_PULLUP);
pinMode(upButtonPin, INPUT_PULLUP);
pinMode(downButtonPin, INPUT_PULLUP);
pinMode(ssrPin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
digitalWrite(ssrPin, LOW);
digitalWrite(buzzerPin, LOW);
lcd.init();
lcd.backlight();
lcdShow("Sistem Basliyor", "Lutfen Bekleyin...", "", "");
delay(500); // Termokuplun stabil olması için kısa bir bekleme
// Termokupl ilk okuma ve hata kontrolü
if (isnan(thermocouple.readCelsius())) {
currentState = STATE_THERMOCOUPLE_ERROR;
} else {
loadSettingsFromEEPROM(); // Ayarları EEPROM'dan yükle
}
myPID.SetMode(AUTOMATIC); // PID'yi otomatik modda başlat
pidWindowStartTime = millis();
// Başlangıçta ekranı temizle ve mevcut durumu göster
clearLcdBuffers();
double initialSetupTemp = thermocouple.readCelsius(); // İlk sıcaklığı oku
// Eğer initialSetupTemp NaN ise, currentState zaten STATE_THERMOCOUPLE_ERROR olmalı
// ve updateDisplay bunu doğru şekilde ele alacaktır.
updateDisplay(initialSetupTemp, millis()); // Fonksiyonu doğru argümanlarla çağır
}
void loop() {
unsigned long currentTime = millis();
double currentTemperature = thermocouple.readCelsius();
// --- 1. Termokupl Arıza Kontrolü ---
if (isnan(currentTemperature)) {
if (currentState != STATE_THERMOCOUPLE_ERROR) {
currentState = STATE_THERMOCOUPLE_ERROR;
digitalWrite(ssrPin, LOW); // Güvenlik için SSR'yi kapat
digitalWrite(buzzerPin, LOW); // Buzzer'ı kapat
clearLcdBuffers();
}
} else if (currentState == STATE_THERMOCOUPLE_ERROR) {
// Eğer hata durumundaydık ve termokupl düzeldi ise IDLE'a dön
currentState = STATE_IDLE;
loadSettingsFromEEPROM(); // Ayarları tekrar yükle, belki EEPROM'da sorun vardı
clearLcdBuffers();
}
// --- 2. Butonları Oku ---
readButtons();
// --- 3. Durum Makinesi (State Machine) ---
switch (currentState) {
case STATE_IDLE:
handleIdleState(currentTemperature);
break;
case STATE_RUNNING:
handleRunningState(currentTemperature, currentTime);
break;
case STATE_SETTINGS_STEP_SELECT:
case STATE_SETTINGS_TEMP:
case STATE_SETTINGS_DURATION:
handleSettingsState(currentTemperature);
break;
case STATE_PROCESS_COMPLETE:
handleProcessCompleteState(currentTime);
break;
case STATE_THERMOCOUPLE_ERROR:
// Ekran güncellemesi aşağıda yapılacak
break;
case STATE_TOO_HOT:
// Ekran güncellemesi aşağıda yapılacak, kısa süre sonra IDLE'a dönecek
break;
}
// --- 4. Ekranı Güncelle (Sadece Değişiklik Varsa) ---
updateDisplay(currentTemperature, currentTime);
// --- 5. Buzzer Kontrolü (Genel) ---
manageBuzzer(currentTime);
delay(10); // Döngüye kısa bir gecikme, çok hızlı çalışmasını engellemek için
}
// =========================================================================
// Buton Okuma ve Debounce Fonksiyonu
// =========================================================================
void readButtons() {
int pins[] = {startButtonPin, settingsButtonPin, upButtonPin, downButtonPin};
for (int i = 0; i < 4; i++) {
bool reading = digitalRead(pins[i]);
if (reading != lastButtonState[i]) {
lastDebounceTime[i] = millis();
}
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
if (reading != currentButtonState[i]) {
currentButtonState[i] = reading;
if (currentButtonState[i] == LOW) { // Butona basıldı (PULLUP olduğu için LOW aktif)
handleButtonPress(i);
}
}
}
lastButtonState[i] = reading;
}
}
// =========================================================================
// Buton Basılma Olaylarını Yönetme
// =========================================================================
void handleButtonPress(int buttonIndex) {
if (currentState == STATE_THERMOCOUPLE_ERROR) return; // Termokupl hatası varsa butonlar çalışmasın
// 0:start, 1:ayar, 2:yukarı, 3:aşağı
switch (buttonIndex) {
case 0: // START Butonu
if (currentState == STATE_IDLE) {
double temp = thermocouple.readCelsius();
if (temp >= 50.0) {
currentState = STATE_TOO_HOT;
buzzerStartTime = millis(); // Ekranın ne kadar süre kalacağını belirlemek için
} else {
currentState = STATE_RUNNING;
currentStepIndex = 0;
stepStartTime = millis();
processElapsedTime = 0;
pidWindowStartTime = millis(); // PID zaman penceresini sıfırla
myPID.SetMode(AUTOMATIC); // PID'yi aktif et
// SSR için başlangıç durumunu ayarla (hedef > 30 ise ilk anda açık olacak)
ssrControlPeriodStart = millis();
ssrControlState = LOW; // Başlangıçta kapalı varsay, controlSSR düzeltecek
}
clearLcdBuffers();
} else if (currentState == STATE_RUNNING) {
// İsteğe bağlı: Çalışırken START'a basılırsa durdurma? (Şimdilik yok)
} else if (currentState == STATE_PROCESS_COMPLETE) {
currentState = STATE_IDLE;
buzzerActive = false;
digitalWrite(buzzerPin, LOW);
clearLcdBuffers();
}
break;
case 1: // AYAR Butonu
if (currentState == STATE_IDLE) {
currentState = STATE_SETTINGS_STEP_SELECT;
selectedStepForEditing = 0;
// Ayarlara girerken mevcut ayarları geçici bir diziye kopyala
memcpy(tempSteps, steps, sizeof(steps));
clearLcdBuffers();
} else if (currentState == STATE_SETTINGS_STEP_SELECT) {
currentState = STATE_SETTINGS_TEMP;
clearLcdBuffers();
} else if (currentState == STATE_SETTINGS_TEMP) {
currentState = STATE_SETTINGS_DURATION;
clearLcdBuffers();
} else if (currentState == STATE_SETTINGS_DURATION) {
selectedStepForEditing++;
if (selectedStepForEditing >= NUM_STEPS) {
// Tüm adımlar ayarlandı, kaydet ve çık
memcpy(steps, tempSteps, sizeof(steps)); // Geçici ayarları asıl ayarlara kopyala
saveSettingsToEEPROM();
currentState = STATE_IDLE;
} else {
// Bir sonraki adımın sıcaklık ayarına geç
currentState = STATE_SETTINGS_TEMP;
}
clearLcdBuffers();
}
break;
case 2: // YUKARI Butonu
if (currentState == STATE_SETTINGS_STEP_SELECT) {
selectedStepForEditing = (selectedStepForEditing + 1) % NUM_STEPS;
clearLcdBuffers(); // Sadece seçili adım değiştiği için tüm satırları güncellemek gerekebilir
} else if (currentState == STATE_SETTINGS_TEMP) {
tempSteps[selectedStepForEditing].targetTemperature += 1.0;
if (tempSteps[selectedStepForEditing].targetTemperature > 300.0) // Maksimum sıcaklık sınırı
tempSteps[selectedStepForEditing].targetTemperature = 300.0;
} else if (currentState == STATE_SETTINGS_DURATION) {
tempSteps[selectedStepForEditing].durationSeconds += 10; // Süreyi 10 saniye adımlarla artır
if (tempSteps[selectedStepForEditing].durationSeconds > 3600) // Maksimum süre (1 saat)
tempSteps[selectedStepForEditing].durationSeconds = 3600;
}
break;
case 3: // AŞAĞI Butonu
if (currentState == STATE_SETTINGS_STEP_SELECT) {
selectedStepForEditing = (selectedStepForEditing - 1 + NUM_STEPS) % NUM_STEPS;
clearLcdBuffers();
} else if (currentState == STATE_SETTINGS_TEMP) {
tempSteps[selectedStepForEditing].targetTemperature -= 1.0;
if (tempSteps[selectedStepForEditing].targetTemperature < 20.0) // Minimum sıcaklık sınırı (oda sıcaklığı)
tempSteps[selectedStepForEditing].targetTemperature = 20.0;
} else if (currentState == STATE_SETTINGS_DURATION) {
if (tempSteps[selectedStepForEditing].durationSeconds >= 10)
tempSteps[selectedStepForEditing].durationSeconds -= 10;
else
tempSteps[selectedStepForEditing].durationSeconds = 0;
}
break;
}
}
// =========================================================================
// Durum Yönetim Fonksiyonları
// =========================================================================
void handleIdleState(double currentTemperature) {
digitalWrite(ssrPin, LOW); // Boşta iken SSR kapalı
buzzerActive = false; // Boşta iken buzzer kapalı
// Ekran güncellemesi updateDisplay() içinde yapılacak
}
void handleRunningState(double currentTemperature, unsigned long currentTime) {
pidInput = currentTemperature;
pidSetpoint = steps[currentStepIndex].targetTemperature;
myPID.Compute(); // PID hesaplamasını yap (pidOutput güncellenir)
controlSSR(currentTemperature, pidSetpoint, currentTime); // SSR'yi özel mantıkla kontrol et
// Süre sayımı
// Hedef sıcaklığa ulaşıldığında veya geçildiğinde süre saymaya başlar
// Ve sıcaklık düşse bile saymaya devam eder.
static bool countdownStartedForStep = false;
if (currentTemperature >= steps[currentStepIndex].targetTemperature - 2.0) { // Hedefe +/- 2 derece tolerans
if (!countdownStartedForStep) {
countdownStartedForStep = true;
stepStartTime = currentTime; // Geri sayım başlangıç zamanını ayarla
}
}
if (countdownStartedForStep) {
processElapsedTime = (currentTime - stepStartTime) / 1000; // Geçen süre (saniye)
if (steps[currentStepIndex].durationSeconds > processElapsedTime) {
stepRemainingTime = steps[currentStepIndex].durationSeconds - processElapsedTime;
} else {
stepRemainingTime = 0;
}
} else { // Henüz hedefe ulaşılamadı, kalan süre adımın toplam süresidir
processElapsedTime = 0;
stepRemainingTime = steps[currentStepIndex].durationSeconds;
}
// Adım tamamlama ve bir sonraki adıma geçiş
if (countdownStartedForStep && processElapsedTime >= steps[currentStepIndex].durationSeconds) {
currentStepIndex++;
countdownStartedForStep = false; // Yeni adım için geri sayımı sıfırla
processElapsedTime = 0; // Yeni adım için geçen süreyi sıfırla
ssrControlPeriodStart = currentTime; // Yeni adım için SSR zamanlayıcısını sıfırla
ssrControlState = LOW;
if (currentStepIndex >= NUM_STEPS) {
currentState = STATE_PROCESS_COMPLETE;
digitalWrite(ssrPin, LOW); // Proses bitti, SSR'yi kapat
buzzerActive = true;
buzzerBeepCount = 0; // Bip sayacını sıfırla
buzzerPatternOnTime = 1000; // 1sn açık
buzzerPatternOffTime = 1000; // 1sn kapalı
buzzerStartTime = currentTime;
clearLcdBuffers();
} else {
stepStartTime = currentTime; // Bir sonraki adımın başlangıç zamanı
if (currentStepIndex == 7) { // 8. Adıma geçildi (0'dan başladığı için index 7)
buzzerActive = true;
buzzerPatternOnTime = 100; // Kısa bip
buzzerPatternOffTime = 900; // 1 saniyede bir
buzzerStartTime = currentTime;
} else if (currentStepIndex == 8) { // 9. Adıma geçildi
buzzerActive = false; // Buzzer'ı sustur
digitalWrite(buzzerPin, LOW);
}
clearLcdBuffers(); // Adım değişti, ekranı temizle (bufferları)
}
}
}
void handleSettingsState(double currentTemperature) {
// Ekran güncellemesi updateDisplay() içinde yapılacak
// Değer değişiklikleri handleButtonPress() içinde yapılıyor
}
void handleProcessCompleteState(unsigned long currentTime) {
// Buzzer manageBuzzer() tarafından yönetilecek.
// Burada sadece belirli bir süre sonra IDLE'a dönmesi sağlanabilir
// veya kullanıcı START'a basana kadar bu ekranda kalır.
// Şimdilik START'a basılınca IDLE'a dönüyor (handleButtonPress içinde)
}
// =========================================================================
// SSR Kontrol Fonksiyonu (Kullanıcının İstediği Mantıkla)
// =========================================================================
void controlSSR(double currentTemp, double targetTemp, unsigned long currentTime) {
double difference = targetTemp - currentTemp;
// SSR kontrol periyotları için zamanlayıcılar
// ssrControlState: rölenin bir sonraki olması gereken durumu (AÇIK/KAPALI)
// ssrOnTime, ssrOffTime: mevcut periyottaki açık/kapalı kalma süreleri
if (difference > 30.0) {
digitalWrite(ssrPin, HIGH);
// Sürekli açık olduğu için periyodik kontrole gerek yok, bir sonraki döngüde tekrar buraya girecek
// ssrControlPeriodStart ve ssrControlState'i sıfırlamaya gerek yok.
return; // Hemen çık, aşağıdaki periyodik kontrolü atla
} else if (difference > 15.0) { // 30 >= fark > 15
ssrOnTime = 2000; // 2sn açık
ssrOffTime = 1000; // 1sn kapalı
} else if (difference > 10.0) { // 15 >= fark > 10
ssrOnTime = 1000; // 1sn açık
ssrOffTime = 1000; // 1sn kapalı
} else if (difference > 0.0) { // 10 >= fark > 0 (Hedefe çok yakın)
ssrOnTime = 500; // 500ms açık
ssrOffTime = 1000; // 1sn kapalı
} else { // fark <= 0 (Hedefe ulaşıldı veya geçildi)
digitalWrite(ssrPin, LOW);
// Hedefte veya altında olduğumuz için periyodik kontrole gerek yok.
return; // Hemen çık
}
// Periyodik SSR Kontrolü
unsigned long elapsedTimeInPeriod = currentTime - ssrControlPeriodStart;
if (ssrControlState == LOW) { // Şu an KAPALI ise AÇIK konuma geçme zamanı mı?
if (elapsedTimeInPeriod >= ssrOffTime) {
digitalWrite(ssrPin, HIGH);
ssrControlState = HIGH;
ssrControlPeriodStart = currentTime; // Yeni periyot başlangıcı
}
} else { // Şu an AÇIK ise KAPALI konuma geçme zamanı mı?
if (elapsedTimeInPeriod >= ssrOnTime) {
digitalWrite(ssrPin, LOW);
ssrControlState = LOW;
ssrControlPeriodStart = currentTime; // Yeni periyot başlangıcı
}
}
// Eğer fark aniden >30 olursa, bir sonraki `controlSSR` çağrısında en baştaki if bloğu onu sürekli HIGH yapacak.
}
// =========================================================================
// Buzzer Yönetim Fonksiyonu
// =========================================================================
void manageBuzzer(unsigned long currentTime) {
static bool buzzerPhysicalState = LOW; // Buzzer'ın gerçek fiziksel durumu
static unsigned long lastBuzzerToggleTime = 0;
if (buzzerActive) {
if (currentState == STATE_PROCESS_COMPLETE) {
if (buzzerBeepCount >= buzzerEndBeeps * 2) { // Her bip AÇIK+KAPALI olduğu için *2
buzzerActive = false;
digitalWrite(buzzerPin, LOW);
buzzerPhysicalState = LOW;
return;
}
}
unsigned long currentPatternTime = buzzerPhysicalState ? buzzerPatternOnTime : buzzerPatternOffTime;
if (currentTime - lastBuzzerToggleTime >= currentPatternTime) {
buzzerPhysicalState = !buzzerPhysicalState;
digitalWrite(buzzerPin, buzzerPhysicalState);
lastBuzzerToggleTime = currentTime;
if (currentState == STATE_PROCESS_COMPLETE && buzzerPhysicalState == LOW) { // Sadece KAPALI duruma geçtiğinde say
buzzerBeepCount++;
}
}
} else {
if (buzzerPhysicalState == HIGH) { // Eğer aktif değilse ve açıksa kapat
digitalWrite(buzzerPin, LOW);
buzzerPhysicalState = LOW;
}
}
}
// =========================================================================
// Ekran Güncelleme Fonksiyonu (Sadece Değişen Yerleri Güncelle)
// =========================================================================
void updateDisplay(double currentTemperature, unsigned long currentTime) {
char buffer[21]; // LCD satırları için geçici buffer
bool forceUpdate = false;
if (currentState != previousStateDisplay) {
forceUpdate = true;
previousStateDisplay = currentState;
clearLcdBuffers(); // Durum değiştiyse, bir sonraki güncellemede tüm satırları yeniden yaz
}
lcd.setCursor(0, 0);
switch (currentState) {
case STATE_IDLE:
snprintf(buffer, sizeof(buffer), "Hazir");
printLcdLine(0, buffer, forceUpdate);
snprintf(buffer, sizeof(buffer), "Sicaklik: %.1fC", currentTemperature);
printLcdLine(1, buffer, forceUpdate || (abs(currentTemperature - prevDisplayedTemp) > 0.1));
prevDisplayedTemp = currentTemperature;
printLcdLine(2, "...OZTURK BiLiSiM...", forceUpdate); // Boş satır
printLcdLine(3, ".BGA REWORK STATiON.", forceUpdate); // Boş satır
break;
case STATE_RUNNING:
snprintf(buffer, sizeof(buffer), "Adim: %d/%d Calisiyor", currentStepIndex + 1, NUM_STEPS);
printLcdLine(0, buffer, forceUpdate || (currentStepIndex != prevDisplayedStep));
prevDisplayedStep = currentStepIndex;
snprintf(buffer, sizeof(buffer), "Mevcut S: %.1fC", currentTemperature);
printLcdLine(1, buffer, forceUpdate || (abs(currentTemperature - prevDisplayedTemp) > 0.1));
prevDisplayedTemp = currentTemperature;
snprintf(buffer, sizeof(buffer), "Hedef S: %.1fC", steps[currentStepIndex].targetTemperature);
printLcdLine(2, buffer, forceUpdate || (abs(steps[currentStepIndex].targetTemperature - prevDisplayedTargetTemp) > 0.1) || (currentStepIndex != prevDisplayedStep));
prevDisplayedTargetTemp = steps[currentStepIndex].targetTemperature;
snprintf(buffer, sizeof(buffer), "Sure: %luSn / %uSn", processElapsedTime, steps[currentStepIndex].durationSeconds);
// Kalan süre veya toplam süre gösterilebilir. Şimdilik böyle.
// snprintf(buffer, sizeof(buffer), "Kalan: %luSn", stepRemainingTime);
printLcdLine(3, buffer, forceUpdate || (processElapsedTime != prevDisplayedElapsedTime) || (currentStepIndex != prevDisplayedStep));
prevDisplayedElapsedTime = processElapsedTime;
break;
case STATE_SETTINGS_STEP_SELECT:
snprintf(buffer, sizeof(buffer), "AYAR: Adim %d Sec", selectedStepForEditing + 1);
printLcdLine(0, buffer, forceUpdate);
snprintf(buffer, sizeof(buffer), "S:%.1fC Sure:%us", tempSteps[selectedStepForEditing].targetTemperature, tempSteps[selectedStepForEditing].durationSeconds);
printLcdLine(1, buffer, forceUpdate); // Değerler değişebileceği için her zaman güncelle
printLcdLine(2, "Ayar:Gecis Yuk/As", forceUpdate);
printLcdLine(3, "Start:Iptal/Kaydet", forceUpdate); // TODO: Start ile iptal eklenebilir
break;
case STATE_SETTINGS_TEMP:
snprintf(buffer, sizeof(buffer), "AYAR Adim %d: Sicaklik", selectedStepForEditing + 1);
printLcdLine(0, buffer, forceUpdate);
snprintf(buffer, sizeof(buffer), "=> %.1f C", tempSteps[selectedStepForEditing].targetTemperature);
printLcdLine(1, buffer, true); // Değer anlık değiştiği için hep güncelle
printLcdLine(2, "Ayar:Sureye Gec", forceUpdate);
printLcdLine(3, "Yuk/As: Degistir", forceUpdate);
break;
case STATE_SETTINGS_DURATION:
snprintf(buffer, sizeof(buffer), "AYAR Adim %d: Sure", selectedStepForEditing + 1);
printLcdLine(0, buffer, forceUpdate);
snprintf(buffer, sizeof(buffer), "=> %u Saniye", tempSteps[selectedStepForEditing].durationSeconds);
printLcdLine(1, buffer, true); // Değer anlık değiştiği için hep güncelle
if (selectedStepForEditing < NUM_STEPS - 1)
snprintf(buffer, sizeof(buffer), "Ayar:Sonraki Adim");
else
snprintf(buffer, sizeof(buffer), "Ayar:Kaydet&Cik");
printLcdLine(2, buffer, forceUpdate);
printLcdLine(3, "Yuk/As: Degistir", forceUpdate);
break;
case STATE_PROCESS_COMPLETE:
printLcdLine(0, "PROSES TAMAMLANDI!", forceUpdate);
printLcdLine(1, "Sogutma Modu...", forceUpdate); // Ya da istenen bir mesaj
printLcdLine(2, "", forceUpdate);
printLcdLine(3, "Start: Ana Menu", forceUpdate);
break;
case STATE_THERMOCOUPLE_ERROR:
lcdShow("HATA!", "Thermocouple Arizasi", "Kontrol Edin!", "");
// Bu durumda sadece bu mesaj gösterilir, diğer her şey durur.
// Hata mesajı sürekli yanıp sönmesin diye bir kerelik yazdırılır.
// `forceUpdate` burada önemli. `previousStateDisplay` değiştiği için ilk seferde yazacak.
break;
case STATE_TOO_HOT:
lcdShow("UYARI!", "Baslangic Cok Sicak", "Lutfen Bekleyin...", "");
if (currentTime - buzzerStartTime > 2000) { // 2 saniye sonra IDLE'a dön
currentState = STATE_IDLE;
clearLcdBuffers(); // IDLE ekranını düzgün çizmek için
}
break;
}
}
// LCD'ye bir satır yazan yardımcı fonksiyon (önceki değerle karşılaştırarak)
void printLcdLine(int row, const char* newText, bool force) {
char* prevText;
switch (row) {
case 0: prevText = lcdLine0; break;
case 1: prevText = lcdLine1; break;
case 2: prevText = lcdLine2; break;
case 3: prevText = lcdLine3; break;
default: return;
}
if (force || strncmp(prevText, newText, 20) != 0) {
strncpy(prevText, newText, 20);
prevText[20] = '\0'; // Null terminate
lcd.setCursor(0, row);
lcd.print(" "); // Önceki satırı temizle
lcd.setCursor(0, row);
lcd.print(prevText);
}
}
// Tüm LCD satır buffer'larını temizle
void clearLcdBuffers() {
lcdLine0[0] = '\0';
lcdLine1[0] = '\0';
lcdLine2[0] = '\0';
lcdLine3[0] = '\0';
// prevDisplayed değerlerini de sıfırla ki bir sonraki updateDisplay'de her şey yeniden yazılsın
prevDisplayedTemp = -1000; // Geçersiz bir değer
prevDisplayedTargetTemp = -1000;
prevDisplayedElapsedTime = 0xFFFFFFFF;
prevDisplayedRemainingTime = 0xFFFFFFFF;
prevDisplayedStep = -10;
}
// LCD'ye hızlıca 4 satır mesaj yazdıran fonksiyon
void lcdShow(const char* l0, const char* l1, const char* l2, const char* l3) {
printLcdLine(0, l0, true);
printLcdLine(1, l1, true);
printLcdLine(2, l2, true);
printLcdLine(3, l3, true);
}
// =========================================================================
// EEPROM Fonksiyonları
// =========================================================================
void loadSettingsFromEEPROM() {
int magic;
EEPROM.get(EEPROM_MAGIC_NUMBER_ADDR, magic);
if (magic == EEPROM_MAGIC_NUMBER) {
Serial.println("EEPROM'dan ayarlar yukleniyor.");
EEPROM.get(EEPROM_STEPS_ADDR, steps);
// PID ayarları da kaydedilip yüklenebilir, şimdilik sabit.
// EEPROM.get(EEPROM_PID_ADDR, Kp); // Örnek
} else {
Serial.println("EEPROM'da gecerli ayar bulunamadi. Varsayilanlar yukleniyor.");
// Varsayılan ayarları yükle
for (int i = 0; i < NUM_STEPS; ++i) {
steps[i].targetTemperature = 100.0 + i * 10; // Örnek varsayılan sıcaklıklar
steps[i].durationSeconds = 60 + i * 5; // Örnek varsayılan süreler
}
// Varsayılan PID ayarları zaten global olarak tanımlı
saveSettingsToEEPROM(); // Varsayılanları ilk çalıştırmada kaydet
}
memcpy(tempSteps, steps, sizeof(steps)); // Geçici ayarları da senkronize et
}
void saveSettingsToEEPROM() {
Serial.println("Ayarlar EEPROM'a kaydediliyor.");
EEPROM.put(EEPROM_MAGIC_NUMBER_ADDR, EEPROM_MAGIC_NUMBER);
EEPROM.put(EEPROM_STEPS_ADDR, steps);
// EEPROM.put(EEPROM_PID_ADDR, Kp); // Örnek
Serial.println("Kayit tamamlandi.");
}