#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <RTClib.h>
#include <Encoder.h>
// Pin Definitions
#define RED 7 // Holding high temp indecator Red LED
#define YELOW 8 // Holding LOW temp indecator Yelow LED
#define DS18B20_PIN 2 // DS18B20 connected to digital pin 2
#define RELAY_PIN 6 // Relay to control the heater
#define STOP_BUTTON_PIN 9 // Stop button
#define ENCODER_PIN_A 3 // Rotary encoder A
#define ENCODER_PIN_B 4 // Rotary encoder B
#define ENCODER_BUTTON 5 // Rotary encoder button
#define SD_CS 10 // Chip Select for SD Card
// Initialize LCD (I2C address 0x27 for a 20x4 display)
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Initialize SD card
File dataFile;
// Initialize OneWire and DS18B20
OneWire oneWire(DS18B20_PIN);
DallasTemperature sensors(&oneWire);
// Initialize RTC
RTC_DS1307 rtc;
// Initialize rotary encoder
Encoder encoder(ENCODER_PIN_A, ENCODER_PIN_B);
// Variables
float currentTemperature = 0.0;
float highTemp = 0.0; // Default high temperature
float lowTemp = 0.0; // Default low temperature
int holdTime = 0; // Default hold time in minutes
bool heatingStarted = false;
bool holdTimeTracking = false;
bool lowTempTracking = false;
bool heating = false;
bool tempering = false;
bool keep_low_temp = false;
bool loggingData = false; // Flag for logging data
DateTime holdStartTime;
DateTime lowTempStartTime;
DateTime logStartTime; // Start time for logging
unsigned long logStartMillis = 0; // Milliseconds when logging started
// Button State Tracking for Debouncing
bool lastEncoderButtonState = HIGH;
bool lastStopButtonState = HIGH;
// Encoder menu states
int state = 0; // 0 = Set High Temp, 1 = Set Hold Time, 2 = Set Low Temp, 3 = Ready to Start
void setup() {
// Initialize RTC
if (!rtc.begin()) {
lcd.setCursor(0, 0);
lcd.print("RTC Not Found!");
while (1);
}
// Initialize serial monitor
Serial.begin(9600);
// Initialize LCD
lcd.init();
lcd.backlight();
// Initialize DS18B20
sensors.begin();
// Initialize RTC
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Set to compile time
}
// Initialize pins
pinMode(RED, OUTPUT);
pinMode(YELOW, OUTPUT);
pinMode(RELAY_PIN, OUTPUT);
pinMode(STOP_BUTTON_PIN, INPUT_PULLUP);
pinMode(ENCODER_BUTTON, INPUT_PULLUP);
// Ensure heater is off at startup
digitalWrite(RELAY_PIN, LOW);
// Initialize SD card
if (!SD.begin(SD_CS)) {
lcd.setCursor(0, 0);
lcd.print("SD card error!");
while (1); // Halt if SD card initialization fails
}
// Welcome Message
lcd.setCursor(6, 1);
lcd.print("STARTING");
lcd.setCursor(7, 2);
lcd.print("SYSTEM");
delay(2000);
lcd.clear();
}
void loop() {
// Request temperature from DS18B20
sensors.requestTemperatures();
currentTemperature = sensors.getTempCByIndex(0);
// Read rotary encoder position
long encoderPosition = encoder.read() / 4;
// Read buttons
bool encoderButtonPressed = digitalRead(ENCODER_BUTTON) == LOW;
bool stopButtonPressed = digitalRead(STOP_BUTTON_PIN) == LOW;
// Handle encoder button press
if (encoderButtonPressed && lastEncoderButtonState == HIGH) {
delay(50); // Debounce delay
if (digitalRead(ENCODER_BUTTON) == LOW) {
state = (state + 1) % 4; // Cycle through menu states
encoder.write(0); // Reset encoder position
lcd.clear();
}
}
lastEncoderButtonState = encoderButtonPressed;
// Handle stop button press
if (stopButtonPressed && lastStopButtonState == HIGH) {
delay(50); // Debounce delay
if (digitalRead(STOP_BUTTON_PIN) == LOW) {
heatingStarted = false;
holdTimeTracking = false;
lowTempTracking = false;
digitalWrite(RELAY_PIN, LOW); // Turn off heater
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Process Stopped");
lcd.setCursor(6, 1);
lcd.print("Restart");
lcd.setCursor(6, 2);
lcd.print(".......");
// Stop logging and write completion message
loggingData = false;
writeLogData();
delay(200);
lcd.clear();
return; // Exit loop to ensure heating is off
}
}
lastStopButtonState = stopButtonPressed;
// Update LCD
updateLCD(encoderPosition);
// Start heating process if in "Ready to Start" state
if (state == 3 && !heatingStarted) {
heatingStarted = true;
heating = true;
lcd.clear();
lcd.setCursor(7, 1);
lcd.print("Start");
lcd.setCursor(6, 2);
lcd.print("Heating.... ");
delay(3000);
lcd.clear();
digitalWrite(RELAY_PIN, HIGH); // Turn on heater
lcd.setCursor(0, 2);
lcd.print("Go to High ");
}
// Control heating and time tracking
if (heatingStarted) {
controlHeating();
}
// Start logging after 1-minute holding (when it's time to log)
if (loggingData) {
unsigned long currentMillis = millis();
if (currentMillis - logStartMillis >= 240*1000) { // Stop after 4 minute
loggingData = false;
writeLogData();
lcd.setCursor(0, 3);
lcd.print("Data Saved!");
}
}
delay(50); // Short delay for stability
}
void updateLCD(long encoderPosition) {
lcd.setCursor(4, 0);
lcd.print("Temp: ");
lcd.print(currentTemperature);
lcd.print(" C ");
if (state == 0) { // Set High Temperature
highTemp = constrain(0 + encoderPosition * 5, 0, 400); // Adjust high temperature
lcd.setCursor(0, 2);
lcd.print("Set High Temp:");
lcd.setCursor(0, 3);
lcd.print(highTemp);
lcd.print(" C ");
} else if (state == 1) { // Set Hold Time
holdTime = constrain(1 + encoderPosition , 0, 240); // Adjust hold time
lcd.setCursor(0, 2);
lcd.print("Set Hold Time: ");
lcd.setCursor(0, 3);
if(holdTime>60) {
lcd.print(holdTime/60);
lcd.print("h ");
lcd.print(holdTime%60);
lcd.print("min");
} else {
lcd.print(holdTime);
lcd.print(" min ");
}
} else if (state == 2) { // Set Low Temperature
lowTemp = constrain(0 + encoderPosition * 5, 0, highTemp - 1); // Adjust low temperature
lcd.setCursor(0, 2);
lcd.print("Set Low Temp:");
lcd.setCursor(0, 3);
lcd.print(lowTemp);
lcd.print(" C ");
} else if (state == 3) { // Ready to Start
lcd.setCursor(0, 1);
lcd.print("Starting ");
}
}
void controlHeating() {
if (heating && currentTemperature < highTemp) {
// continue heating to high temperature
} else if (heating && currentTemperature >= (highTemp+1)) {
heating = false;
tempering = true;
digitalWrite(RELAY_PIN, LOW); // Turn off heater
lcd.setCursor(0, 2);
lcd.print("Reached High ");
holdStartTime = rtc.now(); // Start hold time tracking
holdTimeTracking = true;
digitalWrite(RED, HIGH);
logStartTime = rtc.now(); // Start logging the data
loggingData = true;
logStartMillis = millis();
} else if (tempering) {
DateTime now = rtc.now();
TimeSpan elapsed = now - holdStartTime;
if (elapsed.minutes() >= holdTime) {
digitalWrite(RELAY_PIN, LOW); // Turn off heater
holdTimeTracking = false;
digitalWrite(RED, LOW);
tempering = false;
keep_low_temp = true;
lcd.setCursor(0, 3);
lcd.print("Time Over H ");
} else {
lcd.setCursor(0, 3);
lcd.print("Hold H temp: ");
lcd.print(elapsed.minutes());
lcd.print("m ");
lcd.print(elapsed.seconds());
lcd.print("s ");
if (currentTemperature >= (highTemp + 1)) {
digitalWrite(RELAY_PIN, LOW); // Turn off heater
} else if (currentTemperature < (highTemp - 1)) {
digitalWrite(RELAY_PIN, HIGH); // Turn on heater
}
}
} else if(keep_low_temp) {
if(currentTemperature>(lowTemp-3) && currentTemperature<(lowTemp+3) && !lowTempTracking) {
lowTempTracking = true;
digitalWrite(YELOW, HIGH);
holdStartTime = rtc.now(); // Start hold time tracking (Low)
} else if(lowTempTracking) {
DateTime now = rtc.now();
TimeSpan elapsed = now - holdStartTime;
lcd.setCursor(0, 3);
lcd.print("Hold L temp: ");
lcd.print(elapsed.minutes());
lcd.print("m ");
lcd.print(elapsed.seconds());
lcd.print("s ");
if(currentTemperature >= (lowTemp+1)) {
digitalWrite(RELAY_PIN, LOW); // Turn off heater
} else if(currentTemperature < (lowTemp-1)) {
digitalWrite(RELAY_PIN, HIGH); // Turn on heater
}
}
}
}
void writeLogData() {
// Open SD card and write data
dataFile = SD.open("temp_log.csv", FILE_WRITE);
if (dataFile) {
dataFile.print(rtc.now().timestamp(DateTime::TIMESTAMP_FULL));
dataFile.print(",");
dataFile.print(highTemp);
dataFile.print(",");
dataFile.print(holdTime);
dataFile.print(",");
dataFile.print(lowTemp);
dataFile.println();
dataFile.close();
}
}