Compare commits
4 Commits
d4a564cea3
...
adffdc48c5
| Author | SHA1 | Date | |
|---|---|---|---|
| adffdc48c5 | |||
| 88e4cd34a1 | |||
| ce123734b5 | |||
| e5e171df0c |
36
eeprom.ino
Normal file
36
eeprom.ino
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
LP_TIMER((long)10*60*1000, writeEEPROM);
|
||||||
|
|
||||||
|
void writeEEPROM() {
|
||||||
|
#if useEEPROM
|
||||||
|
lastEEPROMWriteTime = millis();
|
||||||
|
EEPROM.put(0, activeProfileIndex); // Store the active profile index
|
||||||
|
EEPROM.put(4, totalElapsedTime); // Store the total elapsed time
|
||||||
|
|
||||||
|
Serial.print("EEPROM written: ");
|
||||||
|
Serial.print(activeProfileIndex);
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(totalElapsedTime);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void readEEPROM() {
|
||||||
|
#if useEEPROM
|
||||||
|
long time = 0;
|
||||||
|
EEPROM.get(0, activeProfileIndex); // Store the active profile index
|
||||||
|
EEPROM.get(4, time); // Store the total elapsed time
|
||||||
|
if (activeProfileIndex != 100) {
|
||||||
|
totalStartTime = millis() - time*1000;
|
||||||
|
calculateTotalTime();
|
||||||
|
getPhaseAndTemperature();
|
||||||
|
inSelectionMode = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
oled.clear();
|
||||||
|
displaySelection();
|
||||||
|
}
|
||||||
|
Serial.print("EEPROM read: ");
|
||||||
|
Serial.print(activeProfileIndex);
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(time);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@ -1,9 +1,12 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <Looper.h>
|
||||||
|
|
||||||
#define useEEPROM 1
|
#define useEEPROM 1
|
||||||
// #define TempSensorMax
|
// #define TempSensorMax
|
||||||
#define TempSensorDallas
|
#define TempSensorDallas
|
||||||
// #define PlotValues
|
// #define PlotValues
|
||||||
|
|
||||||
#include <Wire.h>
|
// #include <Wire.h>
|
||||||
#include <GyverOLED.h>
|
#include <GyverOLED.h>
|
||||||
#include "GyverEncoder.h"
|
#include "GyverEncoder.h"
|
||||||
|
|
||||||
@ -63,12 +66,14 @@ struct Profile {
|
|||||||
Profile profiles[] = {
|
Profile profiles[] = {
|
||||||
{"Chickpeas", 1, {{46, 120}, {55, 60}, {65, 150}, {70, 60}, {90, 105}}, 5},
|
{"Chickpeas", 1, {{46, 120}, {55, 60}, {65, 150}, {70, 60}, {90, 105}}, 5},
|
||||||
{"Lentils", 3, {{46, 120}, {53, 120}, {65, 180}, {72, 90}, {90, 15}}, 5},
|
{"Lentils", 3, {{46, 120}, {53, 120}, {65, 180}, {72, 90}, {90, 15}}, 5},
|
||||||
{"Gradual", 10, {{46, 120}, {80, 60}}, 2},
|
{"Gradual", 10, {{46, 120}, {90, 30}}, 2},
|
||||||
{"Long gradual", 10, {{46, 120}, {80, 60}, {90, 15}}, 3},
|
{"Long gradual", 15, {{46, 120}, {80, 60}, {90, 15}}, 3},
|
||||||
{"Wheat", 1, {{46, 60}, {53, 60}, {65, 20}, {72, 20}, {85, 20}}, 5},
|
{"Wheat", 1, {{46, 120}, {53, 40}, {65, 40}, {72, 40}, {85, 40}}, 5},
|
||||||
{"SV1", 1, {{55, 30}, {85, 120}}, 2},
|
{"Amazake", 1, {{51, 480}}, 1},
|
||||||
{"SV2", 0, {{46, 45}}, 1},
|
{"SV", 0, {{46, 45}}, 1},
|
||||||
{"Yoghurt maker", 0, {{40, 300}, {40, 300}, {30, 300}}, 3},
|
{"Tempeh magic", 1, {{46, 300}, {55, 180}, {72, 20}, {90, 15}}, 4},
|
||||||
|
// {"Yoghurt maker", 0, {{40, 300}, {40, 300}, {30, 300}}, 3},
|
||||||
|
// {"TEST", 0, {{46, 1}}, 1},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global variables for profile selection and execution
|
// Global variables for profile selection and execution
|
||||||
@ -109,15 +114,6 @@ const int ssrSwitchInterval = 1000; // SSR switching interval in milliseconds
|
|||||||
// Buffer for formatted time strings
|
// Buffer for formatted time strings
|
||||||
char timeBuffer[10];
|
char timeBuffer[10];
|
||||||
|
|
||||||
uint32_t timerExecution = 0;
|
|
||||||
#define T_PERIOD_EXEC 1000 // period of Execution processing
|
|
||||||
uint32_t timerSSR = 0;
|
|
||||||
#define T_PERIOD_SSR 500 // period of heater handling
|
|
||||||
uint32_t timerTemp = 0;
|
|
||||||
#define T_PERIOD_TEMP 1000 // period of dallas sensor requesting
|
|
||||||
uint32_t timerChecks= 0;
|
|
||||||
#define T_PERIOD_Checks 1000 // period of additional checks
|
|
||||||
|
|
||||||
bool boolLastCompletedState = false;
|
bool boolLastCompletedState = false;
|
||||||
|
|
||||||
float failedReadingLastValue = 0;
|
float failedReadingLastValue = 0;
|
||||||
@ -147,469 +143,6 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
enc1.tick();
|
Looper.loop();
|
||||||
isLeft = enc1.isLeft();
|
|
||||||
isRight = enc1.isRight();
|
|
||||||
isClick = enc1.isClick();
|
|
||||||
if (isLeft || isRight || isClick) {
|
|
||||||
handleEncoder();
|
|
||||||
}
|
|
||||||
handleTemperatureSensor();
|
|
||||||
handleExecution();
|
|
||||||
handleHeaterAdv();
|
|
||||||
handleAdditionalChecks();
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleAdditionalChecks() {
|
|
||||||
long time = millis();
|
|
||||||
|
|
||||||
if (time - timerChecks < T_PERIOD_Checks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerChecks = time;
|
|
||||||
|
|
||||||
if (isComplete && !boolLastCompletedState) {
|
|
||||||
boolLastCompletedState = true;
|
|
||||||
for(int i=0; i<10; i++) {
|
|
||||||
digitalWrite(activeBuzzerPin, HIGH);
|
|
||||||
delay(100);
|
|
||||||
digitalWrite(activeBuzzerPin, LOW);
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleTemperatureSensor() {
|
|
||||||
long time = millis();
|
|
||||||
static int currentPart = 0;
|
|
||||||
|
|
||||||
if (time - timerTemp < T_PERIOD_TEMP) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerTemp = time;
|
|
||||||
|
|
||||||
#ifdef TempSensorDallas
|
|
||||||
Input = (double) sensors.getTempCByIndex(0);
|
|
||||||
|
|
||||||
sensors.requestTemperatures(); // Send the command to get temperatures
|
|
||||||
#endif
|
|
||||||
#ifdef TempSensorMax // to go into the read temp function, rename from dallas temp
|
|
||||||
Input = thermocouple.readCelsius();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (isnan(Input) || Input < 20 || Input > 95) {
|
|
||||||
failedReadingLastValue = (float) Input;
|
|
||||||
failedReadingCount++;
|
|
||||||
temperatureSensorError = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
temperatureSensorError = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleHeaterSimple() {
|
|
||||||
long time = millis();
|
|
||||||
static int currentPart = 0;
|
|
||||||
|
|
||||||
if (time - timerSSR < T_PERIOD_SSR) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inSelectionMode) {
|
|
||||||
digitalWrite(ssrPin, LOW);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerSSR = time;
|
|
||||||
|
|
||||||
int current = Output * PARTS;
|
|
||||||
|
|
||||||
oled.setCursor(104, 7);
|
|
||||||
char symbol;
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
symbol = current > i ? '=' : '-';
|
|
||||||
if (currentPart == i) {
|
|
||||||
oled.invertText(true);
|
|
||||||
}
|
|
||||||
oled.print(symbol);
|
|
||||||
oled.invertText(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
digitalWrite(ssrPin, currentPart < current ? HIGH : LOW);
|
|
||||||
currentPart++;
|
|
||||||
if (currentPart >= PARTS) {
|
|
||||||
currentPart = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleHeaterAdv() {
|
|
||||||
long time = millis();
|
|
||||||
static int currentPart = 0;
|
|
||||||
static bool states[PARTS];
|
|
||||||
|
|
||||||
if (time - timerSSR < T_PERIOD_SSR) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inSelectionMode) {
|
|
||||||
digitalWrite(ssrPin, LOW);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerSSR = time;
|
|
||||||
|
|
||||||
int current = Output/100 * PARTS;
|
|
||||||
|
|
||||||
int last = 0;
|
|
||||||
for (int i = 1; i < PARTS; i++) {
|
|
||||||
last += states[i] ? 1 : 0;
|
|
||||||
if (i) {
|
|
||||||
states[i-1] = states[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool next = current >= last ? true : false;
|
|
||||||
if (Output < 1) next = false;
|
|
||||||
states[PARTS-1] = next;
|
|
||||||
|
|
||||||
oled.setCursor(128-6*PARTS, 7);
|
|
||||||
sprintf(timeBuffer, "%3d", (int)(Output));
|
|
||||||
char symbol;
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
int index = i - (PARTS - 3);
|
|
||||||
symbol = index < 0 ? ' ' : timeBuffer[index];
|
|
||||||
oled.invertText(states[i]);
|
|
||||||
oled.print(symbol);
|
|
||||||
oled.invertText(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temperatureSensorError) {
|
|
||||||
next = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
digitalWrite(ssrPin, next);
|
|
||||||
currentPart++;
|
|
||||||
if (currentPart >= PARTS) {
|
|
||||||
currentPart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PlotValues
|
|
||||||
Serial.print(Setpoint);
|
|
||||||
Serial.print(",");
|
|
||||||
Serial.print(Input);
|
|
||||||
Serial.print(",");
|
|
||||||
Serial.print(Output);
|
|
||||||
Serial.println(",0,100");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleEncoder() {
|
|
||||||
if (inSelectionMode) {
|
|
||||||
handleProfileSelection();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
handleExecutionSelection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleExecutionSelection() {
|
|
||||||
if (isClick) {
|
|
||||||
activeProfileIndex = 100;
|
|
||||||
activeProfile = profiles[activeProfileIndex];
|
|
||||||
inSelectionMode = true;
|
|
||||||
writeEEPROM();
|
|
||||||
oled.clear();
|
|
||||||
displaySelection();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleExecution() {
|
|
||||||
currentTime = millis();
|
|
||||||
|
|
||||||
if (inSelectionMode || (currentTime - timerExecution < T_PERIOD_EXEC)) { // таймер на millis()
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerExecution = currentTime;
|
|
||||||
|
|
||||||
totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds
|
|
||||||
|
|
||||||
if ((currentTime - lastEEPROMWriteTime) >= (long)10*60*1000) {
|
|
||||||
writeEEPROM();
|
|
||||||
}
|
|
||||||
|
|
||||||
getPhaseAndTemperature();
|
|
||||||
|
|
||||||
regulator.input = (float)Input;
|
|
||||||
regulator.setpoint = Setpoint;
|
|
||||||
regulator.getResultNow();
|
|
||||||
Output = regulator.output;
|
|
||||||
|
|
||||||
if (isComplete && currentPhase >= activeProfile.numPhases && !finishTime) {
|
|
||||||
finishTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display all phases and highlight the current one
|
|
||||||
printPhases();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void startExecution() {
|
|
||||||
activeProfile = profiles[activeProfileIndex];
|
|
||||||
inSelectionMode = false; // Switch to execution mode
|
|
||||||
phaseStartTime = totalStartTime = millis(); // Start the timer
|
|
||||||
totalElapsedTime = 0;
|
|
||||||
regulator.setpoint = 0;
|
|
||||||
// digitalWrite(ssrPin, HIGH); // Start with heater on
|
|
||||||
ssrLastSwitchTime = millis();
|
|
||||||
calculateTotalTime();
|
|
||||||
getPhaseAndTemperature();
|
|
||||||
writeEEPROM();
|
|
||||||
oled.clear();
|
|
||||||
boolLastCompletedState = isComplete = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleProfileSelection() {
|
|
||||||
if (!isClick && !isLeft && !isRight) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle encoder input for selecting the profile
|
|
||||||
if (isRight) {
|
|
||||||
selectedProfileIndex = (selectedProfileIndex + 1) % (sizeof(profiles) / sizeof(profiles[0]));
|
|
||||||
} else if (isLeft) {
|
|
||||||
selectedProfileIndex = (selectedProfileIndex - 1 + (sizeof(profiles) / sizeof(profiles[0]))) % (sizeof(profiles) / sizeof(profiles[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
displaySelection();
|
|
||||||
|
|
||||||
// Start the selected profile on button press
|
|
||||||
if (isClick) {
|
|
||||||
activeProfileIndex = selectedProfileIndex;
|
|
||||||
startExecution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
activeProfile = profiles[activeProfileIndex];
|
|
||||||
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() {
|
|
||||||
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 (totalElapsedTime < accumulatedTime + transitionDuration) {
|
|
||||||
isInTransition = true;
|
|
||||||
currentPhase = i - 1; // Keep currentPhase as the previous phase
|
|
||||||
int timeInTransition = totalElapsedTime - accumulatedTime;
|
|
||||||
Setpoint = previousTemp + (double)timeInTransition / (60 * activeProfile.transitionMinutesPerDegree) * (targetTemp > previousTemp ? 1 : -1);
|
|
||||||
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
accumulatedTime += transitionDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're within the current phase
|
|
||||||
if (totalElapsedTime < accumulatedTime + phaseDuration) {
|
|
||||||
isInTransition = false;
|
|
||||||
currentPhase = i;
|
|
||||||
Setpoint = activeProfile.phases[i].temperature;
|
|
||||||
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time
|
|
||||||
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
|
|
||||||
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time to the end
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void printPhases() {
|
|
||||||
// oled.clear();
|
|
||||||
|
|
||||||
if (isComplete) {
|
|
||||||
// Show completion time and current temperature instead of the title
|
|
||||||
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(Input,0);
|
|
||||||
oled.print("c");
|
|
||||||
oled.print("->");
|
|
||||||
oled.print((int)Setpoint);
|
|
||||||
oled.print("c ");
|
|
||||||
} else {
|
|
||||||
oled.setCursor(0, 0);
|
|
||||||
oled.print(activeProfile.name);
|
|
||||||
oled.print(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display the totals and current state on the OLED
|
|
||||||
oled.setCursor(0, 1);
|
|
||||||
oled.print(" ");
|
|
||||||
oled.setCursor(18, 1);
|
|
||||||
formatTime(totalElapsedTime, timeBuffer);
|
|
||||||
oled.print(timeBuffer);
|
|
||||||
oled.print(" / ");
|
|
||||||
formatTime(totalProcessTime, timeBuffer);
|
|
||||||
oled.print(timeBuffer);
|
|
||||||
oled.print(" ");
|
|
||||||
|
|
||||||
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
|
|
||||||
long timeRemaining = (activeProfile.phases[i].duration * 60) - ((currentTime - phaseStartTime) / 1000);
|
|
||||||
formatTime(timeRemaining, timeBuffer);
|
|
||||||
oled.print(i + 1);
|
|
||||||
oled.print(". ");
|
|
||||||
oled.print(Input, 1);
|
|
||||||
oled.print("c ");
|
|
||||||
if (fabs(Setpoint - round(Setpoint)) < 0.05) {
|
|
||||||
oled.print(Setpoint, 0); // Print without decimals
|
|
||||||
} else {
|
|
||||||
oled.print(Setpoint, 1); // Print with 1 decimal place
|
|
||||||
}
|
|
||||||
oled.print("c ");
|
|
||||||
if (!isInTransition) {
|
|
||||||
oled.print(timeBuffer);
|
|
||||||
}
|
|
||||||
oled.print(" ");
|
|
||||||
} 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.print(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temperatureSensorError) {
|
|
||||||
oled.invertText(true);
|
|
||||||
oled.setCursor(0, 0);
|
|
||||||
oled.setScale(2);
|
|
||||||
oled.print("T = ");
|
|
||||||
oled.print(Input);
|
|
||||||
oled.print(" ");
|
|
||||||
oled.setScale(1);
|
|
||||||
digitalWrite(activeBuzzerPin, HIGH);
|
|
||||||
delay(5);
|
|
||||||
digitalWrite(activeBuzzerPin, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedReadingCount) {
|
|
||||||
oled.setCursor(0, 7);
|
|
||||||
oled.print(failedReadingCount);
|
|
||||||
oled.print(" (");
|
|
||||||
oled.print(failedReadingLastValue);
|
|
||||||
oled.print(") ");
|
|
||||||
}
|
|
||||||
|
|
||||||
oled.invertText(false); // Ensure text inversion is off after the loop
|
|
||||||
|
|
||||||
// oled.setCursor(110,7);
|
|
||||||
// sprintf(timeBuffer, "%3d", (int)(Output*100));
|
|
||||||
// oled.print(timeBuffer);
|
|
||||||
// oled.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void formatTime(long seconds, char* buffer) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeEEPROM() {
|
|
||||||
#if useEEPROM
|
|
||||||
lastEEPROMWriteTime = millis();
|
|
||||||
EEPROM.put(0, activeProfileIndex); // Store the active profile index
|
|
||||||
EEPROM.put(4, totalElapsedTime); // Store the total elapsed time
|
|
||||||
|
|
||||||
Serial.print("EEPROM written: ");
|
|
||||||
Serial.print(activeProfileIndex);
|
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(totalElapsedTime);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void readEEPROM() {
|
|
||||||
#if useEEPROM
|
|
||||||
long time = 0;
|
|
||||||
EEPROM.get(0, activeProfileIndex); // Store the active profile index
|
|
||||||
EEPROM.get(4, time); // Store the total elapsed time
|
|
||||||
if (activeProfileIndex != 100) {
|
|
||||||
totalStartTime = millis() - time*1000;
|
|
||||||
calculateTotalTime();
|
|
||||||
getPhaseAndTemperature();
|
|
||||||
inSelectionMode = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
oled.clear();
|
|
||||||
displaySelection();
|
|
||||||
}
|
|
||||||
Serial.print("EEPROM read: ");
|
|
||||||
Serial.print(activeProfileIndex);
|
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(time);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|||||||
132
output.ino
Normal file
132
output.ino
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
void printPhases() {
|
||||||
|
// oled.clear();
|
||||||
|
|
||||||
|
if (isComplete) {
|
||||||
|
// Show completion time and current temperature instead of the title
|
||||||
|
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(Input,0);
|
||||||
|
oled.print("c");
|
||||||
|
oled.print("->");
|
||||||
|
oled.print((int)Setpoint);
|
||||||
|
oled.print("c ");
|
||||||
|
} else {
|
||||||
|
oled.setCursor(0, 0);
|
||||||
|
oled.print(activeProfile.name);
|
||||||
|
oled.print(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the totals and current state on the OLED
|
||||||
|
oled.setCursor(0, 1);
|
||||||
|
oled.print(" ");
|
||||||
|
oled.setCursor(18, 1);
|
||||||
|
formatTime(totalElapsedTime, timeBuffer);
|
||||||
|
oled.print(timeBuffer);
|
||||||
|
oled.print(" / ");
|
||||||
|
formatTime(totalProcessTime, timeBuffer);
|
||||||
|
oled.print(timeBuffer);
|
||||||
|
oled.print(" ");
|
||||||
|
|
||||||
|
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
|
||||||
|
long timeRemaining = (activeProfile.phases[i].duration * 60) - ((currentTime - phaseStartTime) / 1000);
|
||||||
|
formatTime(timeRemaining, timeBuffer);
|
||||||
|
oled.print(i + 1);
|
||||||
|
oled.print(". ");
|
||||||
|
oled.print(Input, 1);
|
||||||
|
oled.print("c ");
|
||||||
|
if (fabs(Setpoint - round(Setpoint)) < 0.05) {
|
||||||
|
oled.print(Setpoint, 0); // Print without decimals
|
||||||
|
} else {
|
||||||
|
oled.print(Setpoint, 1); // Print with 1 decimal place
|
||||||
|
}
|
||||||
|
oled.print("c ");
|
||||||
|
if (!isInTransition) {
|
||||||
|
oled.print(timeBuffer);
|
||||||
|
}
|
||||||
|
oled.print(" ");
|
||||||
|
} 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.print(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperatureSensorError) {
|
||||||
|
oled.invertText(true);
|
||||||
|
oled.setCursor(0, 0);
|
||||||
|
oled.setScale(2);
|
||||||
|
oled.print("T = ");
|
||||||
|
oled.print(Input);
|
||||||
|
oled.print(" ");
|
||||||
|
oled.setScale(1);
|
||||||
|
digitalWrite(activeBuzzerPin, HIGH);
|
||||||
|
delay(5);
|
||||||
|
digitalWrite(activeBuzzerPin, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedReadingCount) {
|
||||||
|
oled.setCursor(0, 7);
|
||||||
|
oled.print(failedReadingCount);
|
||||||
|
oled.print(" (");
|
||||||
|
oled.print(failedReadingLastValue);
|
||||||
|
oled.print(") ");
|
||||||
|
}
|
||||||
|
|
||||||
|
oled.invertText(false); // Ensure text inversion is off after the loop
|
||||||
|
|
||||||
|
// oled.setCursor(110,7);
|
||||||
|
// sprintf(timeBuffer, "%3d", (int)(Output*100));
|
||||||
|
// oled.print(timeBuffer);
|
||||||
|
// oled.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 formatTime(long seconds, char* buffer) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
t_additional_checks.ino
Normal file
28
t_additional_checks.ino
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
LP_THREAD({
|
||||||
|
if (isComplete && !boolLastCompletedState) {
|
||||||
|
boolLastCompletedState = true;
|
||||||
|
for(int i=0; i<10; i++) {
|
||||||
|
digitalWrite(activeBuzzerPin, HIGH);
|
||||||
|
delay(100);
|
||||||
|
digitalWrite(activeBuzzerPin, LOW);
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LP_DELAY(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// LP_TIMER(1000, AdditionalChecks);
|
||||||
|
|
||||||
|
// void AdditionalChecks() {
|
||||||
|
// if (isComplete && !boolLastCompletedState) {
|
||||||
|
// boolLastCompletedState = true;
|
||||||
|
// for(int i=0; i<5; i++) {
|
||||||
|
// digitalWrite(activeBuzzerPin, HIGH);
|
||||||
|
// delay(100);
|
||||||
|
// digitalWrite(activeBuzzerPin, LOW);
|
||||||
|
// delay(500);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
51
t_encoder.ino
Normal file
51
t_encoder.ino
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
LP_THREAD({
|
||||||
|
enc1.tick();
|
||||||
|
isLeft = enc1.isLeft();
|
||||||
|
isRight = enc1.isRight();
|
||||||
|
isClick = enc1.isClick();
|
||||||
|
if (!isLeft && !isRight && !isClick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inSelectionMode) {
|
||||||
|
handleProfileSelection();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleExecutionSelection();
|
||||||
|
}
|
||||||
|
LP_DELAY(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
void handleExecutionSelection() {
|
||||||
|
if (isClick) {
|
||||||
|
activeProfileIndex = 100;
|
||||||
|
activeProfile = profiles[activeProfileIndex];
|
||||||
|
inSelectionMode = true;
|
||||||
|
writeEEPROM();
|
||||||
|
oled.clear();
|
||||||
|
displaySelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleProfileSelection() {
|
||||||
|
if (!isClick && !isLeft && !isRight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle encoder input for selecting the profile
|
||||||
|
if (isRight) {
|
||||||
|
selectedProfileIndex = (selectedProfileIndex + 1) % (sizeof(profiles) / sizeof(profiles[0]));
|
||||||
|
} else if (isLeft) {
|
||||||
|
selectedProfileIndex = (selectedProfileIndex - 1 + (sizeof(profiles) / sizeof(profiles[0]))) % (sizeof(profiles) / sizeof(profiles[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySelection();
|
||||||
|
|
||||||
|
// Start the selected profile on button press
|
||||||
|
if (isClick) {
|
||||||
|
activeProfileIndex = selectedProfileIndex;
|
||||||
|
startExecution();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
t_heater.ino
Normal file
54
t_heater.ino
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
LP_TIMER(500, HandleHeater);
|
||||||
|
void HandleHeater() {
|
||||||
|
static int currentPart = 0;
|
||||||
|
static bool states[PARTS];
|
||||||
|
|
||||||
|
if (inSelectionMode) {
|
||||||
|
digitalWrite(ssrPin, LOW);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current = Output/100 * PARTS;
|
||||||
|
|
||||||
|
int last = 0;
|
||||||
|
for (int i = 1; i < PARTS; i++) {
|
||||||
|
last += states[i] ? 1 : 0;
|
||||||
|
if (i) {
|
||||||
|
states[i-1] = states[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool next = current >= last ? true : false;
|
||||||
|
if (Output < 1) next = false;
|
||||||
|
states[PARTS-1] = next;
|
||||||
|
|
||||||
|
oled.setCursor(128-6*PARTS, 7);
|
||||||
|
sprintf(timeBuffer, "%3d", (int)(Output));
|
||||||
|
char symbol;
|
||||||
|
for (int i = 0; i < PARTS; i++) {
|
||||||
|
int index = i - (PARTS - 3);
|
||||||
|
symbol = index < 0 ? ' ' : timeBuffer[index];
|
||||||
|
oled.invertText(states[i]);
|
||||||
|
oled.print(symbol);
|
||||||
|
oled.invertText(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperatureSensorError) {
|
||||||
|
next = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(ssrPin, next);
|
||||||
|
currentPart++;
|
||||||
|
if (currentPart >= PARTS) {
|
||||||
|
currentPart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PlotValues
|
||||||
|
Serial.print(Setpoint);
|
||||||
|
Serial.print(",");
|
||||||
|
Serial.print(Input);
|
||||||
|
Serial.print(",");
|
||||||
|
Serial.print(Output);
|
||||||
|
Serial.println(",0,100");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
95
t_main.ino
Normal file
95
t_main.ino
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
LP_TIMER(1000, HandleExecution);
|
||||||
|
void HandleExecution() {
|
||||||
|
currentTime = millis();
|
||||||
|
if (inSelectionMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds
|
||||||
|
|
||||||
|
getPhaseAndTemperature();
|
||||||
|
|
||||||
|
regulator.input = (float)Input;
|
||||||
|
regulator.setpoint = Setpoint;
|
||||||
|
regulator.getResultNow();
|
||||||
|
Output = regulator.output;
|
||||||
|
|
||||||
|
if (isComplete && currentPhase >= activeProfile.numPhases && !finishTime) {
|
||||||
|
finishTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display all phases and highlight the current one
|
||||||
|
printPhases();
|
||||||
|
}
|
||||||
|
|
||||||
|
void getPhaseAndTemperature() {
|
||||||
|
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 (totalElapsedTime < accumulatedTime + transitionDuration) {
|
||||||
|
isInTransition = true;
|
||||||
|
currentPhase = i - 1; // Keep currentPhase as the previous phase
|
||||||
|
int timeInTransition = totalElapsedTime - accumulatedTime;
|
||||||
|
Setpoint = previousTemp + (double)timeInTransition / (60 * activeProfile.transitionMinutesPerDegree) * (targetTemp > previousTemp ? 1 : -1);
|
||||||
|
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulatedTime += transitionDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're within the current phase
|
||||||
|
if (totalElapsedTime < accumulatedTime + phaseDuration) {
|
||||||
|
isInTransition = false;
|
||||||
|
currentPhase = i;
|
||||||
|
Setpoint = activeProfile.phases[i].temperature;
|
||||||
|
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time
|
||||||
|
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
|
||||||
|
phaseStartTime = totalStartTime + accumulatedTime * 1000; // Set phase start time to the end
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateTotalTime() {
|
||||||
|
// Calculate total process time, including transitions
|
||||||
|
activeProfile = profiles[activeProfileIndex];
|
||||||
|
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 startExecution() {
|
||||||
|
activeProfile = profiles[activeProfileIndex];
|
||||||
|
inSelectionMode = false; // Switch to execution mode
|
||||||
|
phaseStartTime = totalStartTime = millis(); // Start the timer
|
||||||
|
totalElapsedTime = 0;
|
||||||
|
regulator.setpoint = 0;
|
||||||
|
// digitalWrite(ssrPin, HIGH); // Start with heater on
|
||||||
|
ssrLastSwitchTime = millis();
|
||||||
|
calculateTotalTime();
|
||||||
|
getPhaseAndTemperature();
|
||||||
|
writeEEPROM();
|
||||||
|
oled.clear();
|
||||||
|
boolLastCompletedState = isComplete = false;
|
||||||
|
}
|
||||||
20
t_temperature_sensor.ino
Normal file
20
t_temperature_sensor.ino
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
LP_TIMER(1000, HandleTemperatureSensor);
|
||||||
|
void HandleTemperatureSensor() {
|
||||||
|
#ifdef TempSensorDallas
|
||||||
|
Input = (double) sensors.getTempCByIndex(0);
|
||||||
|
|
||||||
|
sensors.requestTemperatures(); // Send the command to get temperatures
|
||||||
|
#endif
|
||||||
|
#ifdef TempSensorMax // to go into the read temp function, rename from dallas temp
|
||||||
|
Input = thermocouple.readCelsius();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (isnan(Input) || Input < 20 || Input > 95) {
|
||||||
|
failedReadingLastValue = (float) Input;
|
||||||
|
failedReadingCount++;
|
||||||
|
temperatureSensorError = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temperatureSensorError = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user