#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Initialize LCD: Address 0x27, 16 columns, 2 rows
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pin assignments
const int startStopPin = A1; // START/STOP
const int cirkulPin = 2; // Output 2
const int dozirPin = 3; // Output 3
const int alarmPin = 4; // Output 4
const int phMeterPin = A0; // PH Meter Input
// Constants
const int maxMinutes = 99; // Max counter in minutes
const int p1Countdown = 45; // Countdown for P1
const float phDropThreshold = 0.5; // PH drop requirement in 15 minutes
const float phMin = 1.0; // PH threshold for stopping P2
const unsigned long checkInterval = 15 * 60 * 1000; // 15 minutes in milliseconds
const unsigned long blinkInterval = 2000; // 2 seconds for blinking alarm
// Variables
bool programRunning = false; // Program running state
bool programPaused = false; // Pause state
bool phError = false; // PH error state
unsigned long lastCheckTime = 0; // Last time PH drop was checked
unsigned long lastBlinkTime = 0; // Last time alarm blinked
unsigned long programStartTime = 0; // Program start time
unsigned long countdownStartTime = 0; // Countdown start time
int currentPhase = 0; // Program phase (0 = P1+P2, 1 = P1, 2 = Finished)
float lastPhValue = 7.0; // Last recorded PH value
int elapsedP1Minutes = 0; // Elapsed minutes counter for P1
int elapsedP2Minutes = 0; // Elapsed minutes counter for P2
void setup() {
lcd.begin(16, 2);
lcd.backlight();
pinMode(startStopPin, INPUT_PULLUP);
pinMode(cirkulPin, OUTPUT);
pinMode(dozirPin, OUTPUT);
pinMode(alarmPin, OUTPUT);
// Initial states
digitalWrite(cirkulPin, LOW);
digitalWrite(dozirPin, LOW);
digitalWrite(alarmPin, LOW);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("P1: 00 P2: 00");
lcd.setCursor(0, 1);
lcd.print("PH: --.-");
}
void loop() {
// Check for START/STOP button press
if (digitalRead(startStopPin) == LOW) {
delay(300); // Debounce
programRunning = !programRunning;
if (programRunning) {
programPaused = false;
if (currentPhase == 0) {
programStartTime = millis();
countdownStartTime = millis();
elapsedP1Minutes = 0;
elapsedP2Minutes = 0;
}
}
}
// Handle program phases
if (programRunning && !programPaused) {
handleProgram();
} else if (phError) {
handlePhError();
}
}
void handleProgram() {
float phValue = readPH();
// Phase 0: P1+P2 running
if (currentPhase == 0) {
unsigned long currentTime = millis();
elapsedP1Minutes = (currentTime - programStartTime) / 60000; // P1 time
elapsedP2Minutes = (currentTime - programStartTime) / 60000; // P2 time
if (elapsedP1Minutes >= maxMinutes) elapsedP1Minutes = maxMinutes;
if (elapsedP2Minutes >= maxMinutes) elapsedP2Minutes = maxMinutes;
lcd.setCursor(0, 0);
lcd.print("P1: ");
lcd.print(elapsedP1Minutes);
lcd.print(" P2: ");
lcd.print(elapsedP2Minutes);
lcd.setCursor(0, 1);
lcd.print("PH: ");
lcd.print(phValue, 1); // Display PH value with one decimal place
digitalWrite(cirkulPin, HIGH); // Output 2 ON
digitalWrite(dozirPin, HIGH); // Output 3 ON
// Check PH value
if (phValue < phMin) {
currentPhase = 1;
countdownStartTime = millis();
}
// Check PH drop
if (millis() - lastCheckTime >= checkInterval) {
if (lastPhValue - phValue < phDropThreshold) {
triggerPhError();
}
lastCheckTime = millis();
lastPhValue = phValue;
}
}
// Phase 1: P1 running (45 min countdown)
else if (currentPhase == 1) {
unsigned long currentTime = millis();
int countdown = p1Countdown - ((currentTime - countdownStartTime) / 60000);
if (countdown <= 0) {
countdown = 0;
currentPhase = 2; // Move to finished phase
}
lcd.setCursor(0, 0);
lcd.print("P1: ");
lcd.print(countdown);
lcd.print(" P2: --");
lcd.setCursor(0, 1);
lcd.print("PH: ");
lcd.print(phValue, 1); // Display PH value with one decimal place
digitalWrite(cirkulPin, HIGH); // Output 2 ON
digitalWrite(dozirPin, LOW); // Output 3 OFF
}
// Phase 2: Finished
else if (currentPhase == 2) {
lcd.setCursor(0, 0);
lcd.print("P1: 00 P2: 00");
lcd.setCursor(0, 1);
lcd.print("PH: ");
lcd.print(phValue, 1); // Display PH value with one decimal place
digitalWrite(cirkulPin, LOW); // Output 2 OFF
digitalWrite(dozirPin, LOW); // Output 3 OFF
programRunning = false; // Stop program
}
}
void handlePhError() {
unsigned long currentTime = millis();
lcd.setCursor(0, 0);
lcd.print("PH ERROR! ");
// Blink alarm
if (currentTime - lastBlinkTime >= blinkInterval) {
bool alarmState = digitalRead(alarmPin);
digitalWrite(alarmPin, !alarmState); // Toggle alarm state
lastBlinkTime = currentTime;
}
lcd.setCursor(0, 1);
lcd.print("PH: ");
lcd.print(readPH(), 1); // Display PH value with one decimal place
if (digitalRead(startStopPin) == LOW) {
delay(300); // Debounce
phError = false;
programRunning = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("P1: 00 P2: 00");
}
}
void triggerPhError() {
digitalWrite(cirkulPin, LOW); // Stop outputs
digitalWrite(dozirPin, LOW);
phError = true;
programRunning = false;
}
float readPH() {
int sensorValue = analogRead(phMeterPin);
float voltage = sensorValue * (5.0 / 1023.0); // Convert to voltage
return map(voltage, 2.75, 0.78, 0, 14); // Map to PH range
}