482 lines
12 KiB
C++
482 lines
12 KiB
C++
#include <Keypad.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#include <AccelStepper.h>
|
|
#include <OneWire.h>
|
|
#include <DallasTemperature.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_task_wdt.h"
|
|
#include <Wire.h>
|
|
|
|
// Disable the watchdog timers
|
|
void disableWatchdogTimers() {
|
|
// Disable the Task Watchdog Timer
|
|
esp_task_wdt_deinit();
|
|
|
|
// Disable the Idle Task Watchdog Timer on both cores
|
|
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
|
|
TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);
|
|
esp_task_wdt_delete(idle_0);
|
|
esp_task_wdt_delete(idle_1);
|
|
}
|
|
|
|
const char* version = "V0.1 20240613";
|
|
|
|
// Define keypad matrix
|
|
const byte ROWS = 5;
|
|
const byte COLS = 4;
|
|
char keys[ROWS][COLS] = {
|
|
{ 'F', 'E', '#', '*' },
|
|
{ '1', '2', '3', 'U' },
|
|
{ '4', '5', '6', 'D' },
|
|
{ '7', '8', '9', 'X' },
|
|
{ 'L', '0', 'R', 'E' }
|
|
};
|
|
|
|
struct devSequence {
|
|
char name[7];
|
|
int cycles;
|
|
unsigned long int processTime[20];
|
|
char processName[20][10];
|
|
float processCycle[2][20];
|
|
float processTemp[3][20];
|
|
};
|
|
|
|
struct MotorTaskParams {
|
|
float cwRotations;
|
|
float ccwRotations;
|
|
unsigned long processEndTime;
|
|
};
|
|
|
|
// Create custom characters
|
|
byte thermometer[8] = {
|
|
B00100,
|
|
B01100,
|
|
B00100,
|
|
B01100,
|
|
B00100,
|
|
B01110,
|
|
B01110,
|
|
B01110
|
|
};
|
|
|
|
int run;
|
|
byte rowPins[ROWS] = { 19, 18, 5, 17, 16 };
|
|
byte colPins[COLS] = { 15, 2, 0, 4 };
|
|
|
|
// Define keypad properties
|
|
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
|
|
|
|
// Define LCD properties
|
|
#define LCD_COLUMNS 20
|
|
#define LCD_ROWS 4
|
|
LiquidCrystal_I2C lcd(0x27, LCD_COLUMNS, LCD_ROWS);
|
|
|
|
// Define stepper motor pins
|
|
#define PULSE_PIN 12
|
|
#define DIR_PIN 14
|
|
#define EN_PIN 27
|
|
|
|
// Define stepper motor properties
|
|
#define STEPS_PER_REV 4800 // We are using 1600 microsteps, but a 33:99 gear, so this gives us revolutions of the tank
|
|
#define RPM 60
|
|
#define SPEED (RPM * STEPS_PER_REV / 60.0)
|
|
|
|
// Create an instance of the AccelStepper class
|
|
AccelStepper stepper(AccelStepper::DRIVER, PULSE_PIN, DIR_PIN);
|
|
|
|
// Define the temperature sensor pin
|
|
#define TEMP_SENSOR_PIN 13
|
|
|
|
// Define the OneWire bus for temperature sensor
|
|
OneWire oneWire(TEMP_SENSOR_PIN);
|
|
|
|
// Create a DallasTemperature object to control the temperature sensor
|
|
DallasTemperature sensors(&oneWire);
|
|
|
|
String inputString = ""; // String to hold input
|
|
String devPgm = ""; // String to hold programme type
|
|
|
|
TaskHandle_t motorTaskHandle = NULL;
|
|
|
|
unsigned long processStartTime;
|
|
unsigned long processTimeMillis;
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
// Disable the watchdog timers
|
|
disableWatchdogTimers();
|
|
|
|
// Initialize LCD
|
|
Wire.begin(21, 22);
|
|
lcd.init();
|
|
lcd.backlight();
|
|
|
|
lcd.createChar(0, thermometer);
|
|
|
|
// Set pin modes
|
|
pinMode(EN_PIN, OUTPUT);
|
|
digitalWrite(EN_PIN, HIGH); // Disable motor initially
|
|
|
|
// Set stepper motor properties
|
|
stepper.setMaxSpeed(SPEED);
|
|
stepper.setAcceleration(9600); // Set acceleration
|
|
|
|
// Display welcome message
|
|
lcd.setCursor(6, 1);
|
|
lcd.print("AUTOFILM");
|
|
lcd.setCursor((20 - strlen(version)) / 2, 3);
|
|
lcd.print(version);
|
|
delay(1000);
|
|
lcd.clear();
|
|
}
|
|
|
|
void loop() {
|
|
startingMenu();
|
|
}
|
|
void processHeadings() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Step");
|
|
lcd.setCursor(10, 0);
|
|
lcd.print("Time");
|
|
lcd.setCursor(16, 0);
|
|
lcd.print("Temp");
|
|
}
|
|
|
|
|
|
void startingMenu() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Select Programme:");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("1. C41");
|
|
lcd.setCursor(0, 2);
|
|
lcd.print("2. E6");
|
|
lcd.setCursor(0, 3);
|
|
lcd.print("3. B&W");
|
|
lcd.setCursor(10, 1);
|
|
lcd.print("4. ECN-2");
|
|
lcd.setCursor(10, 2);
|
|
lcd.print("5. Custom");
|
|
lcd.setCursor(10, 3);
|
|
|
|
|
|
while (devPgm == "") {
|
|
getMenuInput();
|
|
}
|
|
lcd.print(devPgm);
|
|
|
|
if (devPgm == "C41") {
|
|
c41Dev();
|
|
} else if (devPgm == "E6") {
|
|
e6Dev();
|
|
} else if (devPgm == "B&W") {
|
|
bwDev();
|
|
} else if (devPgm == "ECN-2") {
|
|
ecn2Dev();
|
|
} else if (devPgm == "Custom") {
|
|
customDev();
|
|
}
|
|
}
|
|
|
|
void getMenuInput() {
|
|
char key = keypad.getKey();
|
|
if (key != NO_KEY) {
|
|
// Check if the key is 1-5
|
|
if (key >= '1' && key <= '5') {
|
|
if (key == '1') {
|
|
devPgm = "C41";
|
|
} else if (key == '2') {
|
|
devPgm = "E6";
|
|
} else if (key == '3') {
|
|
devPgm = "B&W";
|
|
} else if (key == '4') {
|
|
devPgm = "ECN-2";
|
|
} else if (key == '5') {
|
|
devPgm = "Custom";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char getScrollEntEscInput() {
|
|
while (true) { // Loop indefinitely until a valid key is found
|
|
for (int i = 1; i <= 10000; i++) {
|
|
if (i == 10000) { readTemperature(); }
|
|
char key = keypad.getKey(); // Assume keypad.getKey() is properly defined and returns a character
|
|
if (key != NO_KEY) { // Check if a key has been pressed
|
|
// Check if the key is valid
|
|
if (key == 'U' || key == 'D' || key == 'X' || key == 'E') {
|
|
return key; // Return the valid key
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char getEntEscInput() {
|
|
while (true) { // Loop indefinitely until a valid key is found
|
|
|
|
for (int i = 1; i <= 10000; i++) {
|
|
if (i == 10000) { readTemperature(); }
|
|
char key = keypad.getKey(); // Assume keypad.getKey() is properly defined and returns a character
|
|
if (key != NO_KEY) { // Check if a key has been pressed
|
|
// Check if the key is valid
|
|
if (key == 'X' || key == 'E') {
|
|
return key; // Return the valid key
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void readTemperature() {
|
|
sensors.requestTemperatures();
|
|
float temperatureC = sensors.getTempCByIndex(0);
|
|
lcd.setCursor(13, 3);
|
|
lcd.write(byte(0));
|
|
if (temperatureC == DEVICE_DISCONNECTED_C) {
|
|
lcd.print("--");
|
|
} else {
|
|
lcd.print(temperatureC, 1);
|
|
lcd.print("C");
|
|
}
|
|
}
|
|
|
|
void c41Dev() {
|
|
//assign processing parameters
|
|
struct devSequence c41DevSequence = {
|
|
{ "C41" },
|
|
{ 7 },
|
|
{ 180, 195, 45, 180, 60, 60, 30 }, //processTime
|
|
{ "Prewarm", "Developer", "Bleach", "Fix", "Rinse 1", "Rinse 2", "Fin Rinse" }, //processName
|
|
{ { 1, 5.5, 5.5, 5.5, 3.5, 3.5, 1 }, //CW rotations processCycle
|
|
{ 1, 5, 5, 5, 3, 3, 1 } }, //CCW rotations processCycle
|
|
{
|
|
{ 37.8, 37.8, 32, 32, 32, 32, 32 }, //min processTemp
|
|
{ 38, 38, 38, 38, 38, 38, 38 }, //preferred processTemp
|
|
{ 38.2, 38.2, 38.2, 38.2, 38.2, 38.2, 38.2 } //max processTemp
|
|
}
|
|
};
|
|
|
|
struct devSequence e6DevSequence = {
|
|
{ "C41" },
|
|
{ 7 },
|
|
{ 180, 195, 45, 180, 60, 60, 30 }, //processTime
|
|
{ "Prewarm", "First Dev", "Wash", "Reversal", "Colour Dev", "Pre-Bleach", "Bleach", "Fix", "Rinse 1", "Rinse 2", "Rinse 3", "Fin Rinse" }, //processName
|
|
{ { 1, 5.5, 5.5, 5.5, 3.5, 3.5, 1 }, //CW rotations processCycle
|
|
{ 1, 5, 5, 5, 3, 3, 1 } }, //CCW rotations processCycle
|
|
{
|
|
{ 37.7, 37.8, 32, 32, 32, 32, 32 }, //min processTemp
|
|
{ 38, 38, 38, 38, 38, 38, 38 }, //preferred processTemp
|
|
{ 38.3, 38.3, 38.2, 38.2, 38.2, 38.2, 38.2 } //max processTemp
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < c41DevSequence.cycles; i++) {
|
|
if (run == 1) {
|
|
startProcessing(c41DevSequence, i);
|
|
continue;
|
|
}
|
|
|
|
processHeadings();
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(c41DevSequence.processName[i]);
|
|
for (int x = strlen(c41DevSequence.processName[i]); x < 10; x++) {
|
|
lcd.print(" ");
|
|
}
|
|
|
|
|
|
|
|
lcd.setCursor(10, 1);
|
|
lcd.print(secondsToMinutesSeconds(c41DevSequence.processTime[i]));
|
|
lcd.setCursor(16, 1);
|
|
lcd.print(" " + (String)(int)c41DevSequence.processTemp[1][i] + "C");
|
|
// lcd.setCursor(0, 2);
|
|
// lcd.print((String)c41DevSequence.processCycle[0][i] + ":" + (String)c41DevSequence.processCycle[1][i]);
|
|
readTemperature();
|
|
lcd.setCursor(0, 2);
|
|
lcd.print("Scroll / Esc / Ent");
|
|
|
|
char key = getScrollEntEscInput();
|
|
if (key == 'U') {
|
|
if (i > 0) {
|
|
i--;
|
|
}
|
|
i--;
|
|
} else if (key == 'D' && i < c41DevSequence.cycles - 1) {
|
|
continue;
|
|
|
|
} else if (key == 'D' && i == c41DevSequence.cycles - 1) {
|
|
i--;
|
|
continue;
|
|
} else if (key == 'X') {
|
|
devPgm = "";
|
|
run = 0;
|
|
return;
|
|
} else if (key == 'E') {
|
|
int run = 1;
|
|
startProcessing(c41DevSequence, i);
|
|
}
|
|
}
|
|
devPgm = "";
|
|
run = 0;
|
|
}
|
|
|
|
int startProcessing(struct devSequence sequence, int sequenceStep) {
|
|
run = 1;
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(sequence.processName[sequenceStep]);
|
|
for (int x = strlen(sequence.processName[sequenceStep]); x < 10; x++) {
|
|
lcd.print(" ");
|
|
}
|
|
|
|
lcd.setCursor(10, 0);
|
|
lcd.print(secondsToMinutesSeconds(sequence.processTime[sequenceStep]));
|
|
lcd.setCursor(16, 0);
|
|
lcd.print(" " + (String)(int)sequence.processTemp[1][sequenceStep] + "C");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Ent:start Esc:quit");
|
|
readTemperature();
|
|
|
|
char key = getEntEscInput();
|
|
if (key == 'X') {
|
|
devPgm = "";
|
|
run = 0;
|
|
return 0;
|
|
}
|
|
processTimeMillis = (unsigned long)sequence.processTime[sequenceStep] * 1000;
|
|
processStartTime = millis();
|
|
|
|
MotorTaskParams* params = new MotorTaskParams();
|
|
params->cwRotations = sequence.processCycle[0][sequenceStep];
|
|
params->ccwRotations = sequence.processCycle[1][sequenceStep];
|
|
params->processEndTime = processStartTime + processTimeMillis;
|
|
|
|
// Debugging output
|
|
Serial.print("Starting Motor Task with CW Rotations: ");
|
|
Serial.print(params->cwRotations);
|
|
Serial.print(", CCW Rotations: ");
|
|
Serial.print(params->ccwRotations);
|
|
Serial.print(", Process End Time: ");
|
|
Serial.println(params->processEndTime);
|
|
|
|
xTaskCreatePinnedToCore(
|
|
runMotorTask,
|
|
"MotorTask",
|
|
8192, // Increased stack size
|
|
(void*)params,
|
|
1,
|
|
&motorTaskHandle,
|
|
0);
|
|
|
|
|
|
|
|
|
|
while (millis() < processStartTime + processTimeMillis) {
|
|
for (int i = 1; i <= 5; i++) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Remaining: " + (String)secondsToMinutesSeconds((processStartTime + processTimeMillis - millis()) / 1000) + " ");
|
|
|
|
if (i != 5) {
|
|
delay(1000);
|
|
} else {
|
|
readTemperature();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Terminate the motor task
|
|
if (motorTaskHandle != NULL) {
|
|
vTaskDelete(motorTaskHandle);
|
|
motorTaskHandle = NULL;
|
|
}
|
|
delete params;
|
|
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
void runMotorTask(void* parameter) {
|
|
MotorTaskParams* params = (MotorTaskParams*)parameter;
|
|
|
|
// Enable the motor
|
|
digitalWrite(EN_PIN, LOW);
|
|
while (true) {
|
|
// Rotate clockwise
|
|
Serial.println("Rotating CW" + (String)params->cwRotations);
|
|
stepper.setCurrentPosition(0);
|
|
stepper.moveTo(STEPS_PER_REV * params->cwRotations);
|
|
while (stepper.distanceToGo() != 0) {
|
|
stepper.run();
|
|
}
|
|
|
|
// Rotate counter-clockwise
|
|
Serial.println("Rotating CCW" + (String)params->ccwRotations);
|
|
stepper.setCurrentPosition(0);
|
|
stepper.moveTo(-STEPS_PER_REV * params->ccwRotations);
|
|
while (stepper.distanceToGo() != 0) {
|
|
stepper.run();
|
|
}
|
|
}
|
|
// Disable the motor
|
|
digitalWrite(EN_PIN, HIGH);
|
|
vTaskDelete(NULL); // Delete the task when done
|
|
}
|
|
|
|
void ecn2Dev() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(devPgm);
|
|
devPgm = "";
|
|
}
|
|
|
|
void e6Dev() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(devPgm);
|
|
devPgm = "";
|
|
}
|
|
|
|
void bwDev() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(devPgm);
|
|
devPgm = "";
|
|
}
|
|
|
|
void customDev() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(devPgm);
|
|
devPgm = "";
|
|
}
|
|
|
|
char* secondsToMinutesSeconds(int seconds) {
|
|
// Allocate memory for the resulting string
|
|
// MM:SS + null terminator -> total 6 characters
|
|
char* result = (char*)malloc(6 * sizeof(char));
|
|
if (result == NULL) {
|
|
// Handle memory allocation failure
|
|
return NULL;
|
|
}
|
|
|
|
int minutes = seconds / 60;
|
|
int secs = seconds % 60;
|
|
|
|
// Format the string as "MM:SS"
|
|
snprintf(result, 6, "%02d:%02d", minutes, secs);
|
|
|
|
return result;
|
|
}
|