#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <PID_v1.h> // Include the PID library
// Pin definitions for Arduino Mega
#define BUTTON_MENU 22
#define BUTTON_UP 23
#define BUTTON_DOWN 24
#define BUTTON_OK 25
#define TEMP_SENSOR A1 // Temperature sensor input pin
#define LED_PIN 29 // LED output pin
// LCD setup
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C address 0x27, 16 columns, 2 rows
// Menu variables
enum MenuState { MAIN_MENU, SET_TEMP, SET_MAX_TEMP, SET_PID, SET_MODE };
MenuState menuState = MAIN_MENU;
bool inMenu = false;
unsigned long menuButtonPressTime = 0;
bool menuButtonHeld = false;
int selectedMenuItem = 0;
double setpoint = 25.0;
double maxTemperature = 300.0;
double kp = 2.0, ki = 0.5, kd = 0.1; // PID parameters
enum Mode { AUTONOMOUS, SLAVE };
Mode currentMode = AUTONOMOUS;
double tempIncrement = 1.0; // Temperature increment value
// Timing variables
unsigned long lastButtonPressTime = 0;
unsigned long debounceDelay = 300; // 300 ms debounce time
unsigned long lastDisplayUpdateTime = 0;
unsigned long displayUpdateInterval = 1500; // Update display every 1.5 seconds
unsigned long menuTimeout = 3000; // Menu timeout in 3 seconds
unsigned long lastInteractionTime = 0;
// PID control variables
double input = 0, output = 0; // Input from the sensor and output to the LED
PID myPID(&input, &output, &setpoint, kp, ki, kd, DIRECT); // PID object
// PID parameter selection
enum PIDState { SELECTING, ADJUSTING };
PIDState pidState = SELECTING;
int selectedPID = 0; // 0: Kp, 1: Ki, 2: Kd
void setup() {
// Initialize LCD
lcd.init();
lcd.backlight();
// Initialize buttons
pinMode(BUTTON_MENU, INPUT);
pinMode(BUTTON_UP, INPUT);
pinMode(BUTTON_DOWN, INPUT);
pinMode(BUTTON_OK, INPUT);
// Initialize LED and PID controller
pinMode(LED_PIN, OUTPUT);
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(0, 255); // Set limits for PWM output (0 to 255)
// Display welcome message
lcd.clear();
lcd.print("Temp Controller");
delay(1000);
lcd.clear();
lastDisplayUpdateTime = millis(); // Update display timer
}
void loop() {
readButtons();
// Read the temperature sensor value
input = analogRead(TEMP_SENSOR) * (maxTemperature / 1023.0); // Convert analog input to temperature value
// Compute PID output and control LED brightness
myPID.Compute();
analogWrite(LED_PIN, output); // Adjust LED brightness based on PID output
if (inMenu) {
unsigned long currentMillis = millis();
if (currentMillis - lastInteractionTime >= menuTimeout) {
inMenu = false; // Exit menu after 3 seconds of inactivity
menuState = MAIN_MENU;
}
displayMenu(); // Continuously update the menu display
} else {
unsigned long currentMillis = millis();
if (currentMillis - lastDisplayUpdateTime >= displayUpdateInterval) {
lastDisplayUpdateTime = currentMillis;
// Main screen display
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(input, 1);
lcd.print("C "); // Clear remaining characters
lcd.setCursor(0, 1);
lcd.print("Mode: ");
lcd.print(currentMode == AUTONOMOUS ? "AUTO " : "SLAVE"); // Add space to clear the line
}
}
}
void readButtons() {
unsigned long currentMillis = millis();
// Handle MENU button
if (digitalRead(BUTTON_MENU) == HIGH) {
if (!menuButtonHeld) {
menuButtonPressTime = currentMillis;
menuButtonHeld = true;
} else if (currentMillis - menuButtonPressTime > 3000) {
inMenu = true;
menuButtonHeld = false;
menuState = MAIN_MENU;
lastInteractionTime = currentMillis; // Reset the inactivity timer
lcd.clear(); // Clear LCD when menu is accessed
}
} else {
menuButtonHeld = false;
}
// Handle button presses within the menu
if (inMenu && (currentMillis - lastButtonPressTime >= debounceDelay)) {
if (digitalRead(BUTTON_UP) == HIGH) {
lastButtonPressTime = currentMillis;
lastInteractionTime = currentMillis; // Reset the inactivity timer
if (menuState == MAIN_MENU) {
selectedMenuItem = (selectedMenuItem - 1 + 4) % 4; // 4 menu items
} else {
handleMenuSelection('U'); // 'U' stands for UP button
}
lcd.clear(); // Clear LCD on any button press in the menu
displayMenu(); // Update the display after button press
}
if (digitalRead(BUTTON_DOWN) == HIGH) {
lastButtonPressTime = currentMillis;
lastInteractionTime = currentMillis; // Reset the inactivity timer
if (menuState == MAIN_MENU) {
selectedMenuItem = (selectedMenuItem + 1) % 4;
} else {
handleMenuSelection('D'); // 'D' stands for DOWN button
}
lcd.clear(); // Clear LCD on any button press in the menu
displayMenu(); // Update the display after button press
}
if (digitalRead(BUTTON_OK) == HIGH) {
lastButtonPressTime = currentMillis;
lastInteractionTime = currentMillis; // Reset the inactivity timer
handleMenuSelection('O'); // 'O' stands for OK button
lcd.clear(); // Clear LCD on OK button press
displayMenu(); // Update the display after button press
}
}
}
void displayMenu() {
// Display content only in menu state
if (inMenu) {
lcd.setCursor(0, 0);
switch (menuState) {
case MAIN_MENU:
lcd.print("> ");
if (selectedMenuItem == 0) lcd.print("Set Temp ");
else if (selectedMenuItem == 1) lcd.print("Set Max Temp ");
else if (selectedMenuItem == 2) lcd.print("Set PID ");
else if (selectedMenuItem == 3) lcd.print("Set Mode ");
break;
case SET_TEMP:
lcd.print("Set Temp: ");
lcd.setCursor(0, 1);
lcd.print(setpoint, 1);
lcd.print(" C ");
break;
case SET_MAX_TEMP:
lcd.print("Set Max Temp: ");
lcd.setCursor(0, 1);
lcd.print(maxTemperature, 1);
lcd.print(" C ");
break;
case SET_PID:
if (pidState == SELECTING) {
lcd.print("Select PID: ");
lcd.setCursor(0, 1);
if (selectedPID == 0) lcd.print("> Kp");
else if (selectedPID == 1) lcd.print("> Ki");
else if (selectedPID == 2) lcd.print("> Kd");
} else if (pidState == ADJUSTING) {
lcd.print("Adjust PID: ");
lcd.setCursor(0, 1);
if (selectedPID == 0) lcd.print("Kp: ");
else if (selectedPID == 1) lcd.print("Ki: ");
else if (selectedPID == 2) lcd.print("Kd: ");
lcd.print(selectedPID == 0 ? kp : selectedPID == 1 ? ki : kd, 1);
}
break;
case SET_MODE:
lcd.print("Set Mode: ");
lcd.setCursor(0, 1);
lcd.print(currentMode == AUTONOMOUS ? "AUTO " : "SLAVE");
break;
}
}
}
void handleMenuSelection(char button) {
switch (menuState) {
case MAIN_MENU:
if (selectedMenuItem == 0) {
menuState = SET_TEMP;
} else if (selectedMenuItem == 1) {
menuState = SET_MAX_TEMP;
} else if (selectedMenuItem == 2) {
menuState = SET_PID;
pidState = SELECTING; // Start by selecting PID
} else if (selectedMenuItem == 3) {
menuState = SET_MODE;
}
break;
case SET_TEMP:
if (button == 'U') setpoint += tempIncrement;
else if (button == 'D') setpoint -= tempIncrement;
else if (button == 'O') menuState = MAIN_MENU; // OK to exit
break;
case SET_MAX_TEMP:
if (button == 'U') maxTemperature += tempIncrement;
else if (button == 'D') maxTemperature -= tempIncrement;
else if (button == 'O') menuState = MAIN_MENU; // OK to exit
break;
case SET_PID:
if (pidState == SELECTING) {
if (button == 'U') selectedPID = (selectedPID - 1 + 3) % 3; // Cycle through PID parameters
else if (button == 'D') selectedPID = (selectedPID + 1) % 3;
else if (button == 'O') pidState = ADJUSTING; // OK to adjust selected PID
} else if (pidState == ADJUSTING) {
if (button == 'U') {
if (selectedPID == 0) kp += 0.1;
else if (selectedPID == 1) ki += 0.1;
else if (selectedPID == 2) kd += 0.1;
} else if (button == 'D') {
if (selectedPID == 0) kp -= 0.1;
else if (selectedPID == 1) ki -= 0.1;
else if (selectedPID == 2) kd -= 0.1;
} else if (button == 'O') {
pidState = SELECTING; // OK to return to selecting
myPID.SetTunings(kp, ki, kd); // Update PID parameters
}
}
break;
case SET_MODE:
if (button == 'U' || button == 'D') {
currentMode = (currentMode == AUTONOMOUS) ? SLAVE : AUTONOMOUS;
} else if (button == 'O') {
menuState = MAIN_MENU; // OK to exit
}
break;
}
}