hot-fermentation/hot_fermentation.ino

195 lines
5.9 KiB
Arduino
Raw Normal View History

2024-08-29 12:37:31 +07:00
#include <max6675.h>
#include <Wire.h>
#include <PID_v1.h>
2024-08-29 23:01:32 +07:00
#include <GyverOLED.h> // Include the GyverOLED library
2024-08-29 12:37:31 +07:00
// MAX6675 configuration
int max_SO = 12;
int max_CS = 10;
int max_SCK = 13;
MAX6675 thermocouple(max_SCK, max_CS, max_SO);
2024-08-29 23:01:32 +07:00
// OLED configuration
2024-08-30 00:10:25 +07:00
GyverOLED<SSD1306_128x64> oled;
2024-08-29 12:37:31 +07:00
// SSR pin configuration
const int ssrPin = 7;
2024-08-29 23:58:25 +07:00
// Profile structure definition
struct Phase {
int temperature;
int duration; // in minutes
};
struct Profile {
const char* name;
2024-08-30 00:07:12 +07:00
Phase phases[6]; // Maximum of 6 phases per profile
2024-08-29 23:58:25 +07:00
int numPhases;
};
// Profiles definition
Profile profiles[] = {
{"Test", {{49, 1}, {51, 1}}, 2},
2024-08-29 23:58:25 +07:00
{"Пшеница", {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5},
{"Veggies Sous Vide", {{85, 120}}, 1},
2024-08-30 00:07:12 +07:00
{"Фитаза/Протеаза", {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6},
2024-08-29 23:58:25 +07:00
};
// Select active profile (constant at this point)
2024-08-30 00:07:12 +07:00
const int activeProfileIndex = 0; // Index of the active profile, starting from 0
2024-08-29 23:58:25 +07:00
Profile activeProfile = profiles[activeProfileIndex];
2024-08-29 12:37:31 +07:00
// 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;
2024-08-29 23:58:25 +07:00
unsigned long totalElapsedTime;
unsigned long totalProcessTime;
unsigned long finishTime = 0;
bool isComplete = false;
2024-08-29 12:37:31 +07:00
const int ssrSwitchInterval = 1000; // SSR switching interval in milliseconds
2024-08-29 23:58:25 +07:00
// Buffer for formatted time strings
char timeBuffer[10];
2024-08-29 12:37:31 +07:00
void setup() {
pinMode(ssrPin, OUTPUT);
Serial.begin(9600);
2024-08-29 23:01:32 +07:00
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();
2024-08-29 23:58:25 +07:00
oled.setScale(1); // Set text scale back to 1 for more detailed information
2024-08-29 23:01:32 +07:00
2024-08-29 12:37:31 +07:00
// Begin the first phase
phaseStartTime = totalStartTime = millis();
2024-08-29 23:58:25 +07:00
Setpoint = activeProfile.phases[0].temperature;
2024-08-29 12:37:31 +07:00
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
2024-08-29 23:58:25 +07:00
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;
2024-08-29 12:37:31 +07:00
}
// Check if the phase duration is complete
if (!isComplete && (unsigned long)((currentTime - phaseStartTime) / 1000) >= (unsigned long)activeProfile.phases[currentPhase].duration * 60) {
2024-08-29 12:37:31 +07:00
currentPhase++;
2024-08-29 23:58:25 +07:00
if (currentPhase >= activeProfile.numPhases) {
isComplete = true;
finishTime = currentTime;
Setpoint = 45; // Set target temperature to 45°C
2024-08-29 12:37:31 +07:00
digitalWrite(ssrPin, LOW); // Turn off the heater
} else {
phaseStartTime = currentTime; // Reset the start time for the new phase
Setpoint = activeProfile.phases[currentPhase].temperature;
2024-08-29 12:37:31 +07:00
}
}
2024-08-29 23:58:25 +07:00
// Display all phases and highlight the current one
printPhases(currentPhase, currentTime - phaseStartTime, currentTime);
2024-08-29 23:58:25 +07:00
2024-08-29 23:01:32 +07:00
oled.update();
2024-08-29 12:37:31 +07:00
delay(1000); // Update every second
}
void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long currentTime) {
2024-08-29 23:58:25 +07:00
oled.clear();
if (isComplete) {
// Show completion time and current temperature instead of the title
oled.setCursor(0, 0);
oled.invertText(true);
oled.print("Done! ");
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);
}
2024-08-29 23:58:25 +07:00
// 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 && !isComplete) {
2024-08-29 23:58:25 +07:00
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);
2024-08-30 00:07:12 +07:00
oled.print("c ");
2024-08-29 23:58:25 +07:00
oled.print((int)Setpoint);
2024-08-30 00:07:12 +07:00
oled.print("c ");
2024-08-29 23:58:25 +07:00
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);
2024-08-30 00:07:12 +07:00
oled.print("c ");
2024-08-29 23:58:25 +07:00
oled.print(timeBuffer);
}
}
oled.invertText(false); // Ensure text inversion is off after the loop
}
void formatTime(long seconds, char* buffer) {
2024-08-30 00:07:12 +07:00
long hours = seconds / 3600;
long mins = (seconds % 3600) / 60;
int secs = seconds % 60;
buffer[0] = '\0'; // Ensure the buffer is empty
if (hours > 0) {
sprintf(buffer + strlen(buffer), "%ldh", hours);
}
if (mins > 0) {
sprintf(buffer + strlen(buffer), "%ldm", mins);
}
if (secs > 0) {
sprintf(buffer + strlen(buffer), "%ds", secs);
}
2024-08-29 23:01:32 +07:00
}