Compare commits
No commits in common. "f23b6c39380e64b6be3ac485aa322c9099d1bdb8" and "505db01de1536c5ad4b6c908083c52625274a1b2" have entirely different histories.
f23b6c3938
...
505db01de1
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
* check the ai suggestion
|
|
||||||
* time after completion is not displayed
|
|
||||||
* eeprom saving every 5 minutes and resuming
|
|
||||||
* profile selection
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
#include <max6675.h>
|
#include <max6675.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <PID_v1.h>
|
#include <PID_v1.h>
|
||||||
#include <GyverOLED.h>
|
#include <GyverOLED.h> // Include the GyverOLED library
|
||||||
#include "GyverEncoder.h" // Include the GyverEncoder library
|
|
||||||
|
|
||||||
// MAX6675 configuration
|
// MAX6675 configuration
|
||||||
int max_SO = 12;
|
int max_SO = 12;
|
||||||
@ -13,12 +12,6 @@ MAX6675 thermocouple(max_SCK, max_CS, max_SO);
|
|||||||
// OLED configuration
|
// OLED configuration
|
||||||
GyverOLED<SSD1306_128x64> oled;
|
GyverOLED<SSD1306_128x64> oled;
|
||||||
|
|
||||||
// Encoder configuration
|
|
||||||
#define CLK 5
|
|
||||||
#define DT 6
|
|
||||||
#define SW 7
|
|
||||||
Encoder enc1(CLK, DT, SW, TYPE2);
|
|
||||||
|
|
||||||
// SSR pin configuration
|
// SSR pin configuration
|
||||||
const int ssrPin = 7;
|
const int ssrPin = 7;
|
||||||
|
|
||||||
@ -30,28 +23,21 @@ struct Phase {
|
|||||||
|
|
||||||
struct Profile {
|
struct Profile {
|
||||||
const char* name;
|
const char* name;
|
||||||
int transitionMinutesPerDegree;
|
|
||||||
Phase phases[6]; // Maximum of 6 phases per profile
|
Phase phases[6]; // Maximum of 6 phases per profile
|
||||||
int numPhases;
|
int numPhases;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Profiles definition
|
// Profiles definition
|
||||||
Profile profiles[] = {
|
Profile profiles[] = {
|
||||||
{"Test", 0, {{49, 1}, {51, 1}}, 2},
|
{"Test", {{49, 1}, {51, 1}, {55, 1}, {45, 1}}, 4},
|
||||||
{"Пшеница", 1, {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5},
|
{"Пшеница", {{47, 40}, {55, 40}, {65, 20}, {72, 20}, {85, 20}}, 5},
|
||||||
{"Veggies Sous Vide", 0, {{85, 120}}, 1},
|
{"Veggies Sous Vide", {{85, 120}}, 1},
|
||||||
{"Фитаза/Протеаза", 2, {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6},
|
{"Фитаза/Протеаза", {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global variables for profile selection and execution
|
// Select active profile (constant at this point)
|
||||||
int activeProfileIndex = 100; // 100 indicates no profile selected yet
|
const int activeProfileIndex = 0; // Index of the active profile, starting from 0
|
||||||
int selectedProfileIndex = 0; // Index of the currently selected profile in the selection mode
|
Profile activeProfile = profiles[activeProfileIndex];
|
||||||
Profile activeProfile; // The active profile once selected
|
|
||||||
|
|
||||||
// Global variables for current phase, setpoint, and transition state
|
|
||||||
int currentPhase;
|
|
||||||
bool isInTransition = false;
|
|
||||||
bool inSelectionMode = true;
|
|
||||||
|
|
||||||
// PID Control variables
|
// PID Control variables
|
||||||
double Setpoint, Input, Output;
|
double Setpoint, Input, Output;
|
||||||
@ -64,8 +50,6 @@ unsigned long totalStartTime;
|
|||||||
unsigned long ssrLastSwitchTime;
|
unsigned long ssrLastSwitchTime;
|
||||||
unsigned long totalElapsedTime;
|
unsigned long totalElapsedTime;
|
||||||
unsigned long totalProcessTime;
|
unsigned long totalProcessTime;
|
||||||
unsigned long finishTime = 0;
|
|
||||||
bool isComplete = false;
|
|
||||||
const int ssrSwitchInterval = 1000; // SSR switching interval in milliseconds
|
const int ssrSwitchInterval = 1000; // SSR switching interval in milliseconds
|
||||||
|
|
||||||
// Buffer for formatted time strings
|
// Buffer for formatted time strings
|
||||||
@ -76,163 +60,67 @@ void setup() {
|
|||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
oled.init(); // Initialize the OLED
|
oled.init(); // Initialize the OLED
|
||||||
oled.clear(); // Clear the display
|
oled.clear(); // Clear the display
|
||||||
displaySelection();
|
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() {
|
void loop() {
|
||||||
enc1.tick(); // Mandatory encoder update function
|
static int currentPhase = 0;
|
||||||
|
Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
if (inSelectionMode) {
|
myPID.Compute();
|
||||||
handleProfileSelection(); // Handle profile selection mode
|
|
||||||
} else {
|
|
||||||
unsigned long currentTime = millis();
|
|
||||||
totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds
|
|
||||||
|
|
||||||
getPhaseAndTemperature(totalElapsedTime);
|
// Switch SSR based on PID output and interval control
|
||||||
|
if (currentTime - ssrLastSwitchTime > ssrSwitchInterval) {
|
||||||
Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control
|
digitalWrite(ssrPin, Output > 0.5 ? HIGH : LOW);
|
||||||
myPID.Compute();
|
ssrLastSwitchTime = currentTime;
|
||||||
|
|
||||||
// Switch SSR based on PID output and interval control
|
|
||||||
if (currentTime - ssrLastSwitchTime > ssrSwitchInterval) {
|
|
||||||
digitalWrite(ssrPin, Output > 0.5 ? HIGH : LOW);
|
|
||||||
ssrLastSwitchTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isComplete && currentPhase >= activeProfile.numPhases) {
|
|
||||||
finishTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display all phases and highlight the current one
|
|
||||||
printPhases(currentPhase, currentTime - phaseStartTime, currentTime);
|
|
||||||
|
|
||||||
oled.update();
|
|
||||||
|
|
||||||
delay(1000); // Update every second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleProfileSelection() {
|
|
||||||
|
|
||||||
bool click = enc1.isClick();
|
|
||||||
bool turn = enc1.isTurn();
|
|
||||||
if (!click && !turn) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle encoder input for selecting the profile
|
// Display time calculations
|
||||||
if (enc1.isRight()) {
|
totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds
|
||||||
selectedProfileIndex = (selectedProfileIndex + 1) % (sizeof(profiles) / sizeof(profiles[0]));
|
|
||||||
} else if (enc1.isLeft()) {
|
|
||||||
selectedProfileIndex = (selectedProfileIndex - 1 + (sizeof(profiles) / sizeof(profiles[0]))) % (sizeof(profiles) / sizeof(profiles[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
displaySelection();
|
|
||||||
|
|
||||||
// Start the selected profile on button press
|
|
||||||
if (click) {
|
|
||||||
activeProfileIndex = selectedProfileIndex;
|
|
||||||
activeProfile = profiles[activeProfileIndex];
|
|
||||||
calculateTotalTime();
|
|
||||||
inSelectionMode = false; // Switch to execution mode
|
|
||||||
phaseStartTime = totalStartTime = millis(); // Start the timer
|
|
||||||
myPID.SetMode(AUTOMATIC);
|
|
||||||
myPID.SetOutputLimits(0, 1); // SSR is either ON or OFF
|
|
||||||
digitalWrite(ssrPin, HIGH); // Start with heater on
|
|
||||||
ssrLastSwitchTime = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void displaySelection() {
|
|
||||||
oled.clear();
|
|
||||||
// Display all profiles with the selected one highlighted
|
|
||||||
for (int i = 0; i < (int) (sizeof(profiles) / sizeof(profiles[0])); i++) {
|
|
||||||
if (i == selectedProfileIndex) {
|
|
||||||
oled.invertText(true); // Highlight the selected profile
|
|
||||||
}
|
|
||||||
|
|
||||||
oled.setCursor(0, i); // Set cursor to the correct row
|
|
||||||
oled.print(profiles[i].name);
|
|
||||||
|
|
||||||
oled.invertText(false); // Reset text inversion for other profiles
|
|
||||||
}
|
|
||||||
oled.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void calculateTotalTime() {
|
|
||||||
// Calculate total process time, including transitions
|
|
||||||
totalProcessTime = 0;
|
totalProcessTime = 0;
|
||||||
for (int i = 0; i < activeProfile.numPhases; i++) {
|
for (int i = 0; i < activeProfile.numPhases; i++) {
|
||||||
totalProcessTime += activeProfile.phases[i].duration * 60;
|
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 - 1; // Keep currentPhase as the previous phase
|
|
||||||
int timeInTransition = elapsedSeconds - accumulatedTime;
|
|
||||||
Setpoint = previousTemp + (double)timeInTransition / (60 * activeProfile.transitionMinutesPerDegree) * (targetTemp > previousTemp ? 1 : -1);
|
|
||||||
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
|
// Check if the phase duration is complete
|
||||||
currentPhase = activeProfile.numPhases; // Indicate completion
|
if ((unsigned long)((currentTime - phaseStartTime) / 1000) >= (unsigned long)activeProfile.phases[currentPhase].duration * 60) {
|
||||||
Setpoint = 45; // Default to 45°C after completion
|
currentPhase++;
|
||||||
isInTransition = false;
|
if (currentPhase >= activeProfile.numPhases) {
|
||||||
isComplete = true; // Mark the process as complete
|
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, unsigned long currentTime) {
|
void printPhases(int currentPhase, unsigned long phaseElapsedTime) {
|
||||||
oled.clear();
|
oled.clear();
|
||||||
|
|
||||||
if (isComplete) {
|
oled.setCursor(0, 0);
|
||||||
// Show completion time and current temperature instead of the title
|
oled.print(activeProfile.name);
|
||||||
oled.setCursor(0, 0);
|
|
||||||
oled.invertText(true);
|
|
||||||
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");
|
|
||||||
} else {
|
|
||||||
oled.setCursor(0, 0);
|
|
||||||
oled.print(activeProfile.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display the totals and current state on the OLED
|
// Display the totals and current state on the OLED
|
||||||
oled.setCursor(18, 1);
|
oled.setCursor(18, 1);
|
||||||
formatTime(totalElapsedTime, timeBuffer);
|
formatTime(totalElapsedTime, timeBuffer);
|
||||||
@ -242,7 +130,7 @@ void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long
|
|||||||
oled.print(timeBuffer);
|
oled.print(timeBuffer);
|
||||||
|
|
||||||
for (int i = 0; i < activeProfile.numPhases; i++) {
|
for (int i = 0; i < activeProfile.numPhases; i++) {
|
||||||
if (i == currentPhase && !isComplete) {
|
if (i == currentPhase) {
|
||||||
oled.invertText(true); // Invert text for the current phase
|
oled.invertText(true); // Invert text for the current phase
|
||||||
|
|
||||||
oled.setCursor(0, i + 2); // Set cursor to the row corresponding to the phase
|
oled.setCursor(0, i + 2); // Set cursor to the row corresponding to the phase
|
||||||
@ -252,11 +140,7 @@ void printPhases(int currentPhase, unsigned long phaseElapsedTime, unsigned long
|
|||||||
oled.print(". ");
|
oled.print(". ");
|
||||||
oled.print((int)Input);
|
oled.print((int)Input);
|
||||||
oled.print("c ");
|
oled.print("c ");
|
||||||
if (fabs(Setpoint - round(Setpoint)) < 0.05) {
|
oled.print((int)Setpoint);
|
||||||
oled.print((int)Setpoint); // Print without decimals
|
|
||||||
} else {
|
|
||||||
oled.print(Setpoint, 1); // Print with 1 decimal place
|
|
||||||
}
|
|
||||||
oled.print("c ");
|
oled.print("c ");
|
||||||
oled.print(timeBuffer);
|
oled.print(timeBuffer);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user