From 1270e487fa69910fcfa89c5887ac60305b313af6 Mon Sep 17 00:00:00 2001 From: Aleksander Belov Date: Fri, 30 Aug 2024 02:45:48 +0700 Subject: [PATCH] transitions between phases! --- hot_fermentation.ino | 110 +++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 30 deletions(-) diff --git a/hot_fermentation.ino b/hot_fermentation.ino index 3893cbf..cb16c7a 100644 --- a/hot_fermentation.ino +++ b/hot_fermentation.ino @@ -23,22 +23,27 @@ struct Phase { struct Profile { const char* name; + int transitionMinutesPerDegree; Phase phases[6]; // Maximum of 6 phases per profile int numPhases; }; // Profiles definition Profile profiles[] = { - {"Test", {{49, 1}, {51, 1}}, 2}, - {"Пшеница", {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5}, - {"Veggies Sous Vide", {{85, 120}}, 1}, - {"Фитаза/Протеаза", {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6}, + {"Test", 0, {{49, 1}, {51, 1}}, 2}, + {"Пшеница", 1, {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5}, + {"Veggies Sous Vide", 0, {{85, 120}}, 1}, + {"Фитаза/Протеаза", 2, {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6}, }; // Select active profile (constant at this point) const int activeProfileIndex = 0; // Index of the active profile, starting from 0 Profile activeProfile = profiles[activeProfileIndex]; +// Global variables for current phase, setpoint, and transition state +int currentPhase; +bool isInTransition = false; + // PID Control variables double Setpoint, Input, Output; double Kp = 2, Ki = 0.5, Kd = 0.25; @@ -67,9 +72,10 @@ void setup() { oled.update(); oled.setScale(1); // Set text scale back to 1 for more detailed information + calculateTotalTime(); + // 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 @@ -77,37 +83,22 @@ void setup() { } void loop() { - static int currentPhase = 0; - Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control unsigned long currentTime = millis(); + totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds + getPhaseAndTemperature(totalElapsedTime); + + Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control 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 (!isComplete && (unsigned long)((currentTime - phaseStartTime) / 1000) >= (unsigned long)activeProfile.phases[currentPhase].duration * 60) { - currentPhase++; - if (currentPhase >= activeProfile.numPhases) { - isComplete = true; - finishTime = currentTime; - Setpoint = 45; // Set target temperature to 45°C - digitalWrite(ssrPin, LOW); // Turn off the heater - } else { - phaseStartTime = currentTime; // Reset the start time for the new phase - Setpoint = activeProfile.phases[currentPhase].temperature; - } + if (isComplete && currentPhase >= activeProfile.numPhases) { + finishTime = currentTime; } // Display all phases and highlight the current one @@ -118,6 +109,60 @@ void loop() { delay(1000); // Update every second } +void calculateTotalTime() { + // Calculate total process time, including transitions + totalProcessTime = 0; + for (int i = 0; i < activeProfile.numPhases; i++) { + totalProcessTime += activeProfile.phases[i].duration * 60; + if (i < activeProfile.numPhases - 1) { + int tempDiff = abs(activeProfile.phases[i + 1].temperature - activeProfile.phases[i].temperature); + totalProcessTime += tempDiff * 60 * activeProfile.transitionMinutesPerDegree; + } + } +} + +void getPhaseAndTemperature(unsigned long elapsedSeconds) { + unsigned long accumulatedTime = 0; + + for (int i = 0; i < activeProfile.numPhases; i++) { + int phaseDuration = activeProfile.phases[i].duration * 60; + + // Check for transition + if (i > 0) { + int previousTemp = activeProfile.phases[i - 1].temperature; + int targetTemp = activeProfile.phases[i].temperature; + int tempDiff = abs(targetTemp - previousTemp); + int transitionDuration = tempDiff * 60 * activeProfile.transitionMinutesPerDegree; + + if (elapsedSeconds < accumulatedTime + transitionDuration) { + isInTransition = true; + currentPhase = i; + int timeInTransition = elapsedSeconds - accumulatedTime; + Setpoint = previousTemp + ((targetTemp > previousTemp ? (double)timeInTransition / 60 : -(double)timeInTransition / 60) / (double)activeProfile.transitionMinutesPerDegree); + return; + } + + accumulatedTime += transitionDuration; + } + + // Check if we're within the current phase + if (elapsedSeconds < accumulatedTime + phaseDuration) { + isInTransition = false; + currentPhase = i; + Setpoint = activeProfile.phases[i].temperature; + return; + } + + accumulatedTime += phaseDuration; + } + + // If the elapsed time exceeds the total duration, indicate completion + currentPhase = activeProfile.numPhases; // Indicate completion + Setpoint = 45; // Default to 45°C after completion + isInTransition = false; + isComplete = true; // Mark the process as complete +} + void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long currentTime) { oled.clear(); @@ -125,13 +170,14 @@ void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long // Show completion time and current temperature instead of the title oled.setCursor(0, 0); oled.invertText(true); - oled.print("Done! "); + oled.print("Done!"); + oled.invertText(false); + oled.print(" "); formatTime((currentTime - finishTime) / 1000, timeBuffer); // Time since completion oled.print(timeBuffer); oled.print(" "); oled.print((int)Input); oled.print("c"); - oled.invertText(false); } else { oled.setCursor(0, 0); oled.print(activeProfile.name); @@ -156,7 +202,11 @@ void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long oled.print(". "); oled.print((int)Input); oled.print("c "); - oled.print((int)Setpoint); + if (fabs(Setpoint - round(Setpoint)) < 0.05) { + oled.print((int)Setpoint); // Print without decimals + } else { + oled.print(Setpoint, 1); // Print with 1 decimal place + } oled.print("c "); oled.print(timeBuffer); } else {