// include desired libraries
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// define the liquid LCD
LiquidCrystal_I2C lcd(0x27, 4, 20);
// define the pins
#define rightButton 18
#define leftButton 3
#define selectButton 2
#define stepPin 8
#define directionPin 7
// define delays
#define buttonsDelay 500
#define selectionMenuDelay 500
#define timerMenuDelay 60000
#define motorSpeed 50
// define variables that saves when an event happened last time
volatile unsigned long lastRightPressed = 0;
volatile unsigned long lastSelectPressed = 0;
volatile unsigned long lastLeftPressed = 0;
volatile unsigned long lastSelectionTime= 0;
volatile unsigned long lastMotorTime = 0;
// define variables that saves the selected duration and menu
volatile int selectedDuration = 0;
volatile int selectedMenu = 0;
int lastSelectedDuration = -1;
const int submenuCount = 11;
const char* submenuItems[submenuCount] = {"2", "6", "24", "48", "96", "168", "240", "480", "720", "1008", "1440"};
// define variables that are used for time menu update
int firstTimerCall = 0;
volatile unsigned long startTime = 0;
volatile unsigned long durationInSeconds = 0;
void updateTimerMenu() {
// check if desired delay has passed since last update
if ((millis() - lastMotorTime < timerMenuDelay) & firstTimerCall != 0){
return;
}
// update lastest time this function was called
lastMotorTime = millis();
// when the function is called for the first time, we add a default print
if (firstTimerCall == 0){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Duration ");
lcd.print(submenuItems[selectedDuration]);
lcd.print("h");
firstTimerCall++;
lcd.setCursor(0, 1);
lcd.print("Time left: ");
}
// calculate the remaining time in seconds
unsigned long currentTime = millis();
unsigned long elapsedTimeInSeconds = (currentTime - startTime) / 1000;
unsigned long remainingTimeInSeconds = durationInSeconds - elapsedTimeInSeconds;
// check if elapsed time is equal zero
if (remainingTimeInSeconds <= 0) {
remainingTimeInSeconds = 0;
selectedMenu = 2;
}
// update the remaining time
lcd.setCursor(11, 1);
lcd.print(remainingTimeInSeconds / 3600);
lcd.print("h ");
lcd.print((remainingTimeInSeconds % 3600) / 60);
lcd.print("m");
}
void updateSelectionMenu() {
// if the selection has not changed, there is no need to update the menu
if (selectedDuration == lastSelectedDuration){
return;
}
// check if desired delay has passed since last update
if (millis() - lastSelectionTime < selectionMenuDelay){
return;
}
// update lastest time this function was called
lastSelectionTime = millis();
lastSelectedDuration = selectedDuration;
// clear the lcd and write new data
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Select Duration: (h)");
int number_of_characters = 0;
// insure selectDuration is between limits
if (selectedDuration >= submenuCount){
selectedDuration = selectedDuration % submenuCount;
}
if (selectedDuration < 0){
selectedDuration = submenuCount + selectedDuration;
}
for (int i = 0; i < submenuCount; i++) {
String write_to_lcd = "";
// add brackets to menu item
if (i == selectedDuration) {
write_to_lcd = write_to_lcd + "[" + submenuItems[i] + "]";
}
else{
write_to_lcd = write_to_lcd + submenuItems[i];
}
if (i < submenuCount - 1){
// add space after durations except the last one
write_to_lcd = write_to_lcd + " ";
}
// check if the size of string overflows the remaining characters in the lcd row
int remaining_characters_in_row = 20 - (number_of_characters % 20);
if (remaining_characters_in_row < write_to_lcd.length()){
number_of_characters = number_of_characters + remaining_characters_in_row;
}
lcd.setCursor(number_of_characters % 20, (number_of_characters / 20) + 1);
lcd.print(write_to_lcd);
number_of_characters = number_of_characters + write_to_lcd.length();
}
}
void updateLCD(){
if(selectedMenu == 0){
updateSelectionMenu();
}
else{
updateTimerMenu();
}
}
void setup() {
// initialize the LCD
lcd.init();
lcd.backlight();
// define input buttons
pinMode(rightButton, INPUT_PULLUP);
pinMode(leftButton, INPUT_PULLUP);
pinMode(selectButton, INPUT_PULLUP);
// attach interrupts to pins
attachInterrupt(digitalPinToInterrupt(leftButton), leftButtonPressed, RISING);
attachInterrupt(digitalPinToInterrupt(rightButton), rightButtonPressed, RISING);
attachInterrupt(digitalPinToInterrupt(selectButton), selectButtonPressed, RISING);
// define output pins
pinMode(stepPin, OUTPUT);
pinMode(directionPin, OUTPUT);
// set direction pin to high
digitalWrite(directionPin, HIGH);
// update the lcd
updateLCD();
}
void leftButtonPressed(){
// calculate the time elapsed
unsigned long time_elapsed = millis() - lastLeftPressed;
// check if enough time has passed since the previous press
if (time_elapsed > buttonsDelay){
// decrease the duration selector
selectedDuration = selectedDuration - 1;
}
// update the time when the button was last pressed
lastLeftPressed = millis();
}
void rightButtonPressed(){
// calculate the time elapsed
unsigned long time_elapsed = millis() - lastRightPressed;
// check if enough time has passed since the previous press
if (time_elapsed > buttonsDelay){
// increase the duration selector
selectedDuration = selectedDuration + 1;
}
// update the time when the button was last pressed
lastRightPressed = millis();
}
void selectButtonPressed(){
// calculate the time elapsed
unsigned long time_elapsed = millis() - lastSelectPressed;
// check if enough time has passed since the previous press
if (time_elapsed > buttonsDelay){
// switch to the timer menu
selectedMenu = 1;
// extract the duration in seconds
int duration = String(submenuItems[selectedDuration]).toInt();
durationInSeconds = duration * 3600UL;
// save start time of the process
startTime = millis();
}
// update the time when the button was last pressed
lastSelectPressed = millis();
}
void loop(){
// update LCD
updateLCD();
// check if the motor movement are required
if (selectedMenu == 1){
digitalWrite(stepPin, HIGH);
delayMicroseconds(map(motorSpeed, 0, 100, 2000, 200));
digitalWrite(stepPin, LOW);
delayMicroseconds(map(motorSpeed, 0, 100, 2000, 200));
}
}