diff --git a/Config.h b/Config.h index d680bbc..721aaff 100644 --- a/Config.h +++ b/Config.h @@ -23,15 +23,15 @@ /** * General Config Options */ -#define CO2_PURGE_PERIOD 2000 +#define CO2_PRE_PURGE_PERIOD 2000 #define MOVE_BEER_BELT_PERIOD 5000 #define FILLER_TUBE_MOVEMENT_DELAY 2000 -#define CO2_PURGE_RETRACTION_DELAY 1000 -#define CO2_PURGE_RETRACTION_PERIOD 500 +#define CO2_POST_PURGE_DELAY 1000 +#define CO2_POST_PURGE_PERIOD 500 #define FILL_SENSORS_TIMER_FREQUENCY 100000 // 100ms This value needs to be defined in microseconds. -#define FILL_SENSORS_TRIGGER 400 // Int between 0 and 1023 used to trigger the fill sensor: operating voltage(5v or 3.3v) / 1024 +#define FILL_SENSOR_TRIGGER 400 // Ints between 0 and 1023 used to trigger the fill sensor: operating voltage(5v or 3.3v) / 1024 /** * Feature flags */ -//#define CONINUOUS_FILLING // Uncomment this to have the filling process repeat for new batch after the current batch has completed it filling. +//#define CONTINUOUS_FILLING // Uncomment this to have the filling process repeat for new batch after the current batch has completed it filling. diff --git a/InputConfig.h b/InputConfig.h index 7631c4f..47efcd5 100644 --- a/InputConfig.h +++ b/InputConfig.h @@ -23,13 +23,16 @@ /** * Pin definitions */ -#define START_BUTTON 8 -#define BEER_INLET_SOL_1 5 -#define BEER_INLET_SOL_2 6 -#define BEER_INLET_SOL_3 7 +#define START_BUTTON 10 +#define BEER_INLET_SOL_1 7 +#define BEER_INLET_SOL_2 8 +#define BEER_INLET_SOL_3 9 #define BEER_FILL_SENSOR_1 A0 #define BEER_FILL_SENSOR_2 A1 #define BEER_FILL_SENSOR_3 A2 -#define CO2_PURGE_SOL 4 -#define FILL_RAIL_SOL 3 -#define BEER_BELT_SOL 2 +#define CO2_PURGE_SOL 6 +#define FILL_RAIL_SOL 5 +#define BEER_BELT_SOL 4 +#define ROT_ENC_A 2 +#define ROT_ENC_B 3 +#define ROT_ENC_BUTTON 12 diff --git a/OpenBeerFiller.ino b/OpenBeerFiller.ino index 6633fe2..ab0a53f 100644 --- a/OpenBeerFiller.ino +++ b/OpenBeerFiller.ino @@ -29,6 +29,8 @@ // Project specific includes. #include "Config.h"; #include "InputConfig.h"; +#include "ReprapLCD.h" +#include "Settings.h" /** * *************************************************************************** @@ -39,8 +41,10 @@ volatile bool fillSensor1Triggered = false; volatile bool fillSensor2Triggered = false; volatile bool fillSensor3Triggered = false; bool idleMessageDisplayed = false; -enum ProgramState {UNDEF,IDLE,START,FILLING,STOP}; +enum ProgramState {UNDEF, IDLE, START, PRE_PURGE, FILLING, POST_PURGE, STOP}; +char ProgramStateText[][8] = {"UNDEF", "Idle", "Feeding", "Purging", "Filling", "Purging", "Stop"}; ProgramState currentState = UNDEF; +int cansFilled = 0; /** * *************************************************************************** @@ -83,13 +87,13 @@ void setupFillSensorsTimer() { * Check if the fill sensors have been triggered. */ void checkFillSensors() { - if (FILL_SENSORS_TRIGGER < analogRead(BEER_FILL_SENSOR_1)) { + if (EEPROM16_Read(EEPROM_FILL_SENSOR_TRIGGER) < analogRead(BEER_FILL_SENSOR_1)) { triggerFullFillSensor1(); } - if (FILL_SENSORS_TRIGGER < analogRead(BEER_FILL_SENSOR_2)) { + if (EEPROM16_Read(EEPROM_FILL_SENSOR_TRIGGER) < analogRead(BEER_FILL_SENSOR_2)) { triggerFullFillSensor2(); } - if (FILL_SENSORS_TRIGGER < analogRead(BEER_FILL_SENSOR_3)) { + if (EEPROM16_Read(EEPROM_FILL_SENSOR_TRIGGER) < analogRead(BEER_FILL_SENSOR_3)) { triggerFullFillSensor3(); } } @@ -172,27 +176,13 @@ void closeAllBeerFillerTubes() { digitalWrite(BEER_INLET_SOL_3, LOW); } -/** - * Open the CO2 purge solenoid, wait a while and then close it again. - */ -void purgeCO2( bool retract = false ) { - Serial.println("Purging CO2"); - digitalWrite(CO2_PURGE_SOL, HIGH); - if(!retract) { - delay(CO2_PURGE_PERIOD); - } else { - delay(CO2_PURGE_RETRACTION_PERIOD); - } - digitalWrite(CO2_PURGE_SOL, LOW); -} - /** * Raise the fillter tubes out of the bottles. */ void raiseFillerTubes() { Serial.println("Raising filler tubes"); digitalWrite(FILL_RAIL_SOL, HIGH); - delay(CO2_PURGE_RETRACTION_DELAY); // We use CO2_PURGE_RETRACTION_DELAY here as we want to start purging with CO2 as the fill rail raises. + delay(EEPROM16_Read(EEPROM_CO2_POST_PURGE_DELAY)); // We use CO2_POST_PURGE_DELAY here as we want to start purging with CO2 as the fill rail raises. } /** @@ -201,7 +191,7 @@ void raiseFillerTubes() { void lowerFillerTubes() { Serial.println("Lowering filler tubes"); digitalWrite(FILL_RAIL_SOL, LOW); - delay(FILLER_TUBE_MOVEMENT_DELAY); + delay(EEPROM16_Read(EEPROM_FILLER_TUBE_MOVEMENT_DELAY)); } /** @@ -210,7 +200,7 @@ void lowerFillerTubes() { void moveBeerBelt() { Serial.println( "Moving beer belt" ); digitalWrite(BEER_BELT_SOL, HIGH); - delay(MOVE_BEER_BELT_PERIOD); + delay(EEPROM16_Read(EEPROM_MOVE_BEER_BELT_PERIOD)); digitalWrite(BEER_BELT_SOL, LOW); } @@ -231,7 +221,17 @@ void idleState() { void startState() { moveBeerBelt(); lowerFillerTubes(); - purgeCO2(); + changeProgramState(PRE_PURGE); +} + +/** + * Code to run when we are in the PRE_PURGE ProgramState. + */ +void prePurgeState() { + Serial.println("Purging CO2"); + digitalWrite(CO2_PURGE_SOL, HIGH); + delay(EEPROM_CO2_PRE_PURGE_PERIOD); + digitalWrite(CO2_PURGE_SOL, LOW); openAllBeerFillerTubes(); changeProgramState(FILLING); } @@ -242,16 +242,31 @@ void startState() { void fillingState() { // Check if we are done filling. if(allFillSensorsTriggered()){ + cansFilled++; raiseFillerTubes(); - purgeCO2(true); - resetFillSensorTriggers(); - // If done filling, check if we want to do continuous filling or go back to the UNDEF state. - #if defined(CONINUOUS_FILLING) - changeProgramState(START); - #else - changeProgramState(IDLE); - #endif; + changeProgramState(POST_PURGE); + } +} + +/** + * Code to run when we are in the POST_PURGE ProgramState. + */ +void postPurgeState() { + if(CO2_POST_PURGE_PERIOD) { + Serial.println("Purging CO2"); + digitalWrite(CO2_PURGE_SOL, HIGH); + delay(EEPROM16_Read(EEPROM_CO2_POST_PURGE_PERIOD)); + digitalWrite(CO2_PURGE_SOL, LOW); } + + resetFillSensorTriggers(); + + // If done filling, check if we want to do continuous filling or go back to the UNDEF state. + #if defined(CONTINUOUS_FILLING) + changeProgramState(START); + #else + changeProgramState(IDLE); + #endif; } /** @@ -268,7 +283,7 @@ void stopState() { */ void readStartButton() { if( - HIGH==digitalRead(START_BUTTON) + LOW==digitalRead(START_BUTTON) && hasProgramState(IDLE) ) { Serial.println("Start Button Pressed"); @@ -281,7 +296,7 @@ void readStartButton() { */ void readStopButton() { if( - HIGH==digitalRead(START_BUTTON) + LOW==digitalRead(START_BUTTON) && !hasProgramState(IDLE) && !hasProgramState(START) ) { @@ -313,7 +328,7 @@ void changeProgramState(ProgramState state) { } currentState = state; Serial.print("Program state changed: "); - Serial.println(currentState); + Serial.println(ProgramStateText[currentState]); } /** @@ -331,6 +346,8 @@ bool hasProgramState(ProgramState state) { */ void alwaysRun() { readStopButton(); + rotEncRead(); + showDisplay(ProgramStateText[currentState], cansFilled); } /** @@ -344,6 +361,8 @@ void alwaysRun() { */ void setup() { Serial.begin(115200);//Serial.begin(9600); + firstRunSettings(); + setupLCD(); setupPins(); setupFillSensorsTimer(); resetUnit(); @@ -360,9 +379,15 @@ void loop() { case START: startState(); break; + case PRE_PURGE: + prePurgeState(); + break; case FILLING: fillingState(); break; + case POST_PURGE: + postPurgeState(); + break; case STOP: stopState(); break; diff --git a/ReprapLCD.cpp b/ReprapLCD.cpp new file mode 100644 index 0000000..de55920 --- /dev/null +++ b/ReprapLCD.cpp @@ -0,0 +1,267 @@ + +#pragma once +#include "ReprapLCD.h" + +/* Setup 2040 LCD */ +LiquidCrystal_I2C lcd(0x27, 20, 4); +char lines[4][21]; + +/* Setup Rotary Encoder */ +#define ENCODER_OPTIMIZE_INTERRUPTS +Encoder rotEnc(ROT_ENC_A, ROT_ENC_B); +int oldPosition, newPosition; +bool initializing = true; +int buttonState; +int lastButtonState = HIGH; +unsigned long lastDebounceTime = 0; +unsigned long debounceDelay = 50; + +/* Setup Menu Stuff */ +int menuScreenPosition, cursorPos = 0; +enum displayPage {HOME,MENU,VALUE}; +displayPage currentPage = HOME; +enum menuSelect { + HOME_SCREEN, + MOVE_BEER_BELT_TIME, + FILL_TRIG, + PRE_PURGE_TIME, + POST_PURGE_TIME, + POST_PURGE_DELAY, + FILLER_TUBE_DELAY + +}; +menuSelect currentSelection = HOME_SCREEN; +char menuListText[][21] = { + "Home Screen", + "Belt Movement Time", + "Fill Trigger", + "Pre Purge Time", + "Post Purge Time", + "Post Purge Delay", + "Filler Tube Delay" +}; +int menuListLength = ((sizeof(menuListText))/(sizeof(menuListText[0]))); +int valueBuffer; +bool fromMenu; + + +void updateLine(int line, char output[]) { + sprintf(lines[line], "%-20s", output); +} + + +void updateLCD() { + for (int i = 0; i < 4; i++) { + lcd.setCursor(0,i); + lcd.print(lines[i]); + } +} + + +void setupLCD() { + lcd.init(); + lcd.backlight(); + updateLine(0, "Initialising..."); + pinMode(ROT_ENC_BUTTON, INPUT); + initializing = false; + updateLCD(); +} + + +void showDisplay(char currentStateText[], int filledCans) { + char lineText[4][21] = {" ", " ", " ", " "}; + + switch(currentPage) { + case HOME: + sprintf(lineText[0], "%s%s", "Status: ", currentStateText); + sprintf(lineText[1], "%s%i", "Cans Filled: ", filledCans); + break; + + case MENU: + cursorPos = currentSelection - menuScreenPosition; + lineText[cursorPos][0] = 62; // > + + sprintf(lineText[0], "%s%s", lineText[0], menuListText[menuScreenPosition]); + sprintf(lineText[1], "%s%s", lineText[1], menuListText[menuScreenPosition+1]); + sprintf(lineText[2], "%s%s", lineText[2], menuListText[menuScreenPosition+2]); + sprintf(lineText[3], "%s%s", lineText[3], menuListText[menuScreenPosition+3]); + break; + + case VALUE: + switch (currentSelection) { + case MOVE_BEER_BELT_TIME: + sprintf(lineText[0], "%s", "Belt Movement"); + sprintf(lineText[2], "%s%i", "Time in ms: ", valueBuffer); + break; + case FILL_TRIG: + sprintf(lineText[0], "%s", "Fill Trigger"); + sprintf(lineText[2], "%s%i", "Trigger value: ", valueBuffer); + break; + case PRE_PURGE_TIME: + sprintf(lineText[0], "%s", "Pre Purge Time"); + sprintf(lineText[2], "%s%i", "Time in ms: ", valueBuffer); + break; + case POST_PURGE_TIME: + sprintf(lineText[0], "%s", "Post Purge Time"); + sprintf(lineText[2], "%s%i", "Time in ms: ", valueBuffer); + break; + case POST_PURGE_DELAY: + sprintf(lineText[0], "%s", "Post Purge Delay"); + sprintf(lineText[2], "%s%i", "Time in ms: ", valueBuffer); + break; + case FILLER_TUBE_DELAY: + sprintf(lineText[0], "%s", "Filler Tube Delay"); + sprintf(lineText[2], "%s%i", "Time in ms: ", valueBuffer); + break; + } + break; + } + + updateLine(0, lineText[0]); + updateLine(1, lineText[1]); + updateLine(2, lineText[2]); + updateLine(3, lineText[3]); + updateLCD(); +} + + +int valueLowHigh(int rotEncncPosition, int lowVal, int highVal) { + if (rotEncncPosition < (lowVal+1)) { rotEncncPosition = lowVal; rotEnc.write(lowVal); } + if (rotEncncPosition > (highVal-1)) { rotEncncPosition = highVal; rotEnc.write(highVal); } + return rotEncncPosition; +} + + +void readRotEncRotation() { + newPosition = rotEnc.read() / 4; + switch(currentPage) { + case HOME: + break; + + case MENU: + if (newPosition < 1) { newPosition = 0; rotEnc.write(0);} + if (newPosition > (menuListLength-2)) { newPosition = menuListLength-1; rotEnc.write(newPosition * 4);} + + // Check if we need to scroll the screen + // Cursor Moved Down + if (newPosition > currentSelection) { + if ((newPosition-3) > menuScreenPosition) { + menuScreenPosition++; + } + } + + // Cursor Moved Up + if (newPosition < menuScreenPosition) { + menuScreenPosition--; + } + + // Stop the cursor going too far + if (newPosition > (menuScreenPosition + 3)) {newPosition = menuScreenPosition + 3;} + currentSelection = newPosition; + break; + + case VALUE: + int x; + if (fromMenu) { + newPosition = valueBuffer; + fromMenu = false; + } + + switch(currentSelection) { + case MOVE_BEER_BELT_TIME: + x = valueLowHigh(newPosition, 0, 32767); + break; + case FILL_TRIG: + x = valueLowHigh(newPosition, 0, 1023); + break; + case PRE_PURGE_TIME: + x = valueLowHigh(newPosition, 0, 32767); + break; + case POST_PURGE_TIME: + x = valueLowHigh(newPosition, 0, 32767); + break; + case POST_PURGE_DELAY: + x = valueLowHigh(newPosition, 0, 32767); + break; + case FILLER_TUBE_DELAY: + x = valueLowHigh(newPosition, 0, 32767); + break; + } + valueBuffer = x; + break; + } +} + + +void readRotEncButton() { + int reading = digitalRead(ROT_ENC_BUTTON); + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > debounceDelay) { + if (reading != buttonState) { + buttonState = reading; + + // only toggle if the button state is LOW + if (buttonState == LOW) { + + switch(currentPage) { + case HOME: + currentPage = MENU; + currentSelection = HOME_SCREEN; + rotEnc.write(0); + break; + + case MENU: + fromMenu = true; + valueBuffer = EEPROM16_Read(currentSelection*10); + rotEnc.write(valueBuffer*4); + + switch(currentSelection) { + case HOME_SCREEN: + currentPage = HOME; + break; + case MOVE_BEER_BELT_TIME: + currentPage = VALUE; + break; + case FILL_TRIG: + currentPage = VALUE; + break; + case PRE_PURGE_TIME: + currentPage = VALUE; + break; + case POST_PURGE_TIME: + currentPage = VALUE; + break; + case POST_PURGE_DELAY: + currentPage = VALUE; + break; + case FILLER_TUBE_DELAY: + currentPage = VALUE; + break; + } + break; + + case VALUE: + if (EEPROM16_Read(currentSelection*10) != valueBuffer) { + // Only write to EEPROM if we have to + EEPROM16_Write(currentSelection*10, valueBuffer); + } + newPosition = currentSelection; + rotEnc.write(currentSelection*4); + currentPage = MENU; + break; + } + } + } + } + + lastButtonState = reading; +} + + +void rotEncRead() { + readRotEncButton(); + readRotEncRotation(); +} diff --git a/ReprapLCD.h b/ReprapLCD.h new file mode 100644 index 0000000..86b41dc --- /dev/null +++ b/ReprapLCD.h @@ -0,0 +1,13 @@ + +#pragma once +#include +#include // LiquidCrystal_I2C.h: https://github.com/johnrickman/LiquidCrystal_I2C +#include // http://www.pjrc.com/teensy/td_libs_Encoder.html + +#include "InputConfig.h" +#include "Settings.h" + +void updateLine(int line, char output[]); +void setupLCD(); +void rotEncRead(); +void showDisplay(char currentStateText[], int filledCans); diff --git a/Settings.cpp b/Settings.cpp new file mode 100644 index 0000000..f5a7b7a --- /dev/null +++ b/Settings.cpp @@ -0,0 +1,34 @@ + +#pragma once +#include "Settings.h" +int settingsVersion = 1; + + + +void EEPROM16_Write(uint8_t a, uint16_t b) { + Serial.println("Writing to EEPROM"); + EEPROM.write(a, lowByte(b)); + EEPROM.write(a+1, highByte(b)); +} + + +uint16_t EEPROM16_Read(uint8_t a) { + return word(EEPROM.read(a+1), EEPROM.read(a)); +} + + +void firstRunSettings() { + // If EEPROM position 0 has not been set to settingsVersion, assume first run + if (!(EEPROM16_Read(0) == settingsVersion)) { + + // Write defaults to EEPROM + EEPROM16_Write(EEPROM_MOVE_BEER_BELT_PERIOD, MOVE_BEER_BELT_PERIOD); + EEPROM16_Write(EEPROM_FILL_SENSOR_TRIGGER, FILL_SENSOR_TRIGGER); + EEPROM16_Write(EEPROM_CO2_PRE_PURGE_PERIOD, CO2_PRE_PURGE_PERIOD); + EEPROM16_Write(EEPROM_CO2_POST_PURGE_PERIOD, CO2_POST_PURGE_PERIOD); + EEPROM16_Write(EEPROM_CO2_POST_PURGE_DELAY, CO2_POST_PURGE_DELAY); + EEPROM16_Write(EEPROM_FILLER_TUBE_MOVEMENT_DELAY, FILLER_TUBE_MOVEMENT_DELAY); + + EEPROM16_Write(0, settingsVersion); + } +} diff --git a/Settings.h b/Settings.h new file mode 100644 index 0000000..a1ee048 --- /dev/null +++ b/Settings.h @@ -0,0 +1,17 @@ + +#pragma once +#include +#include +#include "Config.h" +#include "InputConfig.h" + +#define EEPROM_MOVE_BEER_BELT_PERIOD 10 +#define EEPROM_FILL_SENSOR_TRIGGER 20 +#define EEPROM_CO2_PRE_PURGE_PERIOD 30 +#define EEPROM_CO2_POST_PURGE_PERIOD 40 +#define EEPROM_CO2_POST_PURGE_DELAY 50 +#define EEPROM_FILLER_TUBE_MOVEMENT_DELAY 60 + +void firstRunSettings(); +void EEPROM16_Write(uint8_t a, uint16_t b); +uint16_t EEPROM16_Read(uint8_t a);