#include #include #include #include // Include the GyverOLED library // MAX6675 configuration int max_SO = 12; int max_CS = 10; int max_SCK = 13; MAX6675 thermocouple(max_SCK, max_CS, max_SO); // OLED configuration GyverOLED oled; // SSR pin configuration const int ssrPin = 7; // Profile structure definition struct Phase { int temperature; int duration; // in minutes }; struct Profile { const char* name; Phase phases[7]; // Maximum of 6 phases per profile int numPhases; }; // Profiles definition Profile profiles[] = { {"Test", {{49, 1}, {51, 1}, {55, 1}, {45, 1}}, 4}, {"Пшеница", {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5}, {"Veggies Sous Vide", {{85, 120}}, 1}, {"Profile 4", {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6}, {"Profile 5", {{55, 60}, {65, 120}, {72, 120}, {80, 120}, {90, 30}, {10, 100}}, 6} }; // Select active profile (constant at this point) const int activeProfileIndex = 1; // Index of the active profile, starting from 0 Profile activeProfile = profiles[activeProfileIndex]; // PID Control variables double Setpoint, Input, Output; double Kp = 2, Ki = 0.5, Kd = 0.25; PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // Timing variables unsigned long phaseStartTime; unsigned long totalStartTime; unsigned long ssrLastSwitchTime; unsigned long totalElapsedTime; unsigned long totalProcessTime; const int ssrSwitchInterval = 1000; // SSR switching interval in milliseconds // Buffer for formatted time strings char timeBuffer[10]; void setup() { pinMode(ssrPin, OUTPUT); Serial.begin(9600); oled.init(); // Initialize the OLED oled.clear(); // Clear the display oled.setScale(2); // Set text scale to 2 for better visibility oled.print("Starting..."); oled.update(); oled.setScale(1); // Set text scale back to 1 for more detailed information // Begin the first phase phaseStartTime = totalStartTime = millis(); Setpoint = activeProfile.phases[0].temperature; myPID.SetMode(AUTOMATIC); myPID.SetOutputLimits(0, 1); // SSR is either ON or OFF digitalWrite(ssrPin, HIGH); // Start with heater on ssrLastSwitchTime = millis(); } void loop() { static int currentPhase = 0; Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control unsigned long currentTime = millis(); myPID.Compute(); // Switch SSR based on PID output and interval control if (currentTime - ssrLastSwitchTime > ssrSwitchInterval) { digitalWrite(ssrPin, Output > 0.5 ? HIGH : LOW); ssrLastSwitchTime = currentTime; } // Display time calculations totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds totalProcessTime = 0; for (int i = 0; i < activeProfile.numPhases; i++) { totalProcessTime += activeProfile.phases[i].duration * 60; } // Check if the phase duration is complete if ((currentTime - phaseStartTime) / 1000 >= activeProfile.phases[currentPhase].duration * 60) { currentPhase++; if (currentPhase >= activeProfile.numPhases) { oled.clear(); oled.print("Complete"); digitalWrite(ssrPin, LOW); // Turn off the heater return; // Stop further execution } phaseStartTime = currentTime; // Reset the start time for the new phase Setpoint = activeProfile.phases[currentPhase].temperature; } // Display all phases and highlight the current one printPhases(currentPhase, currentTime - phaseStartTime); oled.update(); delay(1000); // Update every second } void printPhases(int currentPhase, unsigned long phaseElapsedTime) { oled.clear(); oled.setCursor(0, 0); oled.print(activeProfile.name); // Display the totals and current state on the OLED oled.setCursor(18, 1); formatTime(totalElapsedTime, timeBuffer); oled.print(timeBuffer); oled.print(" / "); formatTime(totalProcessTime, timeBuffer); oled.print(timeBuffer); for (int i = 0; i < activeProfile.numPhases; i++) { if (i == currentPhase) { oled.invertText(true); // Invert text for the current phase oled.setCursor(0, i + 2); // Set cursor to the row corresponding to the phase unsigned long timeRemaining = (activeProfile.phases[i].duration * 60) - (phaseElapsedTime / 1000); formatTime(timeRemaining, timeBuffer); oled.print(i + 1); oled.print(". "); oled.print((int)Input); oled.print("C "); oled.print((int)Setpoint); oled.print("C "); oled.print(timeBuffer); } else { oled.invertText(false); // Normal text for other phases oled.setCursor(0, i + 2); // Set cursor to the row corresponding to the phase formatTime(activeProfile.phases[i].duration * 60, timeBuffer); oled.print(i + 1); oled.print(". "); oled.print(activeProfile.phases[i].temperature); oled.print("C "); oled.print(timeBuffer); } } oled.invertText(false); // Ensure text inversion is off after the loop } void formatTime(long seconds, char* buffer) { long hours = seconds / 3600; long mins = (seconds % 3600) / 60; int secs = seconds % 60; if (hours > 0) { sprintf(buffer, "%ld:%02ld:%02d", hours, mins, secs); } else { sprintf(buffer, "%ld:%02d", mins, secs); } }