Hello. I'm working on a temperature controller program. I'm playing around with menus and trying to find a good and clear way to handle my menus. I had something that worked but I wanted to try doing it with switch cases to hopefully make it easier for me to think about and program.
I'm missing something about how I have the switch case set up. I have an enum for the menu pages and I'm testing those and using the logic to then advance through the menu pages based on a cursor position that is increased or decreased with buttons. An enter button advances the menu page.
If I have just the first switch case ROOT, all my options appear to work. When I add the next case GLYCOL_T, the code seems to go there even when currMainPage is still at 0. It looks like it's evaluating the switch inside the GLYCOL_T case and locking my cursor between 1 and 3. I'm really not sure what I'm missing.
Thanks for the help. My code is below. I have a custom button library that works fine. I'm including it as well if it's needed.
#include <Arduino.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
#include <Button.h>
// Project defines
// Project structures
struct tempCtrl{
const String label; // Temperature control label
float tempF; // Measured temperature (F)
float tempF_SP; // Temperature setpoint (F)
u_int8_t hyst; // Hysteresis for temperature control (F)
boolean heatEnbl; // Enable heating control
boolean coolEnbl; // Enable cooling control
u_int8_t heatDelay; // Heating control cycle delay (min)
u_int8_t coolDelay; // Cooling control cycle delay (min)
u_int8_t switchDelay; // Heating/Cooling switchover delay (min)
};
LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20 columns and 4 rows
// Configure input button pins
const byte BTN_ENTER = 25;
const byte BTN_INC = 26;
const byte BTN_DEC = 27;
// Menu constants and variables
const String menuLabel[4] = {"(0)Temperatures:", "(1)Temp SP Config", "(2)Temp Hyst Config", "(3)Temp Delay Config"};
const String posLabel[3] = {"Glycl", "Ferm1", "Ferm2"};
float tempValue[3] = {49.2, 66.7, 68.3};
float tempSP[3] = {50, 68, 68};
float hyst[3] = {5, 1, 1};
float delaySP[3] = {10, 1, 1};
const u_int16_t buttonEnterDelay = 2000;
const u_int16_t menuTimeOutDelay = 20000;
u_int8_t menuPos = 0; // Initialize menu position
u_int8_t menuCursorPos = 0; // Initialize menu position
boolean updateDisplay = true; // Update the display
// Temperature control variables
tempCtrl glycol, ferm1, ferm2;
// Menu structure
enum menuPage {
ROOT, // Root menu
GLYCOL_T, // Glycol temperature menu
GLYCOL_C, // Glycol cooling menu
GLYCOL_H, // Glycol heating menu
FERM1_T, // Fermenter 1 temperature menu
FERM1_C, // Fermenter 1 cooling menu
FERM1_H, // Fermenter 1 heating menu
FERM2_T, // Fermenter 1 temperature menu
FERM2_C, // Fermenter 1 cooling menu
FERM2_H // Fermenter 1 heating menu
};
enum menuPage currMainPage = ROOT; // Initialize menuPos to the root menu
enum menuCursor {
POS_0, // Position 0
POS_1, // Position 1
POS_2, // Position 2
POS_3 // Position 3
};
enum menuCursor cursorPos = POS_0;
// Function forward declarations
Button btnEnter(BTN_ENTER, buttonEnterDelay);
Button btnInc(BTN_INC, buttonEnterDelay);
Button btnDec(BTN_DEC, buttonEnterDelay);
void menuNavigation();
void setup() {
Serial.begin(115200); // Initialize serial
btnEnter.Begin();
btnInc.Begin();
btnDec.Begin();
lcd.init(); // Initialize LCD
lcd.backlight(); // Turn on LCD backlight
lcd.clear(); // Clear LCD
}
void loop() {
btnEnter.Pressed(); // Read enter button
btnInc.Pressed(); // Read increase button
btnDec.Pressed(); // Read decrease button
menuNavigation(); // Menu navigation
//if (updateDisplay) {lcd.clear();}
lcd.setCursor(0,0);
lcd.print(currMainPage);
lcd.setCursor(0,1);
lcd.print(menuCursorPos);
//lcd.setCursor(0,2);
//lcd.print(btnDec.btnState, HEX);
//lcd.setCursor(0,3);
//lcd.print(btnEnter.longPress);
updateDisplay = false;
}
void menuNavigation(){
static u_int8_t menuUnit = 0; // Menu unit (0-3)
// Increase/Decrease button scrolling through sub menu positions and roll around when at ends
if (btnInc.shortPress | btnInc.longPress) {
if (menuCursorPos == 3) {menuCursorPos = 1;}
else {menuCursorPos++;}
updateDisplay = true;
}
if (btnDec.shortPress | btnDec.longPress) {
if (menuCursorPos <= 1) {menuCursorPos = 3;}
else {menuCursorPos--;}
updateDisplay = true;
}
switch (currMainPage) {
case ROOT:
switch (menuCursorPos) {
case 0:
break;
case 1:
if (btnEnter.shortPress) {currMainPage = GLYCOL_T; menuCursorPos = 1;}
if (btnEnter.longPress) {/* change SP */}
break;
case 2:
if (btnEnter.shortPress) {currMainPage = FERM1_T; menuCursorPos = 1;}
if (btnEnter.longPress) {/* change SP */}
break;
case 3:
if (btnEnter.shortPress) {currMainPage = FERM2_T; menuCursorPos = 1;}
if (btnEnter.longPress) {/* change SP */}
break;
}
case GLYCOL_T:
if (btnEnter.shortPress) {currMainPage = GLYCOL_C; menuCursorPos = 1;}
switch (menuCursorPos) {
case 0: menuCursorPos = 3; break;
case 1: if (btnEnter.longPress) {/* change SP */} break;
case 2: if (btnEnter.longPress) {/* change SP */} break;
case 3: if (btnEnter.longPress) {/* change SP */} break;
case 4: menuCursorPos = 1; break;
}
}
/*
// Set cursor position enumerations
switch (menuCursorPos) {
case 0: cursorPos = POS_0; break;
case 1: cursorPos = POS_1; break;
case 2: cursorPos = POS_2; break;
case 3: cursorPos = POS_3; break;
default: cursorPos = POS_0; break;
}
// Scroll through main menu positions and roll around when at end
if (cursorPos != POS_0 && btnEnter.shortPress) {
if (menuPos == 0) {menuUnit = menuCursorPos;}
menuPos++;
if (menuPos == 4) {menuPos = 0; menuCursorPos = 0; menuUnit = 0;}
updateDisplay = true;
}
*/
/*
(M) (CP) (M) (CP) (M) (CP) (M) (CP)
(0)ROOT-POS_0 (0)ROOT-POS_1 (0)ROOT-POS_2 (0)ROOT-POS_3
(1)GLYCOL_T-POS_1 (1)FERM1_T-POS_1 (1)FERM2_T-POS_1
(2)GLYCOL_C-POS_1 (2)FERM1_C-POS_1 (2)FERM2_C-POS_1
(3)GLYCOL_H-POS_1 (3)FERM1_H-POS_1 (3)FERM2_H-POS_1
*/
// Set menu page enumerations
/*
switch (menuUnit) {
case 0: currMainPage = ROOT; break;
case 1:
switch (menuPos) {
case 0: currMainPage = ROOT; break;
case 1: currMainPage = GLYCOL_T; break;
case 2: currMainPage = GLYCOL_C; break;
case 3: currMainPage = GLYCOL_H; break;
}
break;
case 2:
switch (menuPos) {
case 0: currMainPage = ROOT; break;
case 1: currMainPage = FERM1_T; break;
case 2: currMainPage = FERM1_C; break;
case 3: currMainPage = FERM1_H; break;
}
break;
case 3:
switch (menuPos) {
case 0: currMainPage = ROOT; break;
case 1: currMainPage = FERM2_T; break;
case 2: currMainPage = FERM2_C; break;
case 3: currMainPage = FERM2_H; break;
}
break;
}
*/
// Reset button press flags
btnEnter.ResetPressed();
btnInc.ResetPressed();
btnDec.ResetPressed();
return;
}
button.h
#ifndef Button_h
#define Button_h
#include <Arduino.h>
/*
* @breif Mutli-function and debounce button object
*/
class Button {
private:
byte _pin;
u_int16_t _LONG_PRESS_MS; // Long press delay Ms
u_int32_t _btnReadPrevMs = 0; // Previous button read Ms
u_int32_t _pressedPrevMs = 0; // Previous button pressed Ms
u_int32_t _currMs;
u_int8_t _state = 0xFF; // Initial state of button captures
public:
Button(byte pin, u_int16_t LONG_PRESS_MS);
boolean isPressed = false; // Button is currently pressed
boolean isLongPress = false; // Button is current pressed for > long time
boolean longPress = false; // Button was released - Press was short
boolean shortPress = false; // Button was released - Press was long
u_int8_t btnState = 0; // Button state
void Begin(); // Setup hardware input pin
void Pressed(); // Button function
void ResetPressed(); // Reset button press triggers
};
#endif
button.cpp
#include "Arduino.h"
#include "Button.h"
Button::Button(byte pin, u_int16_t LONG_PRESS_MS){
_pin = pin;
_LONG_PRESS_MS = LONG_PRESS_MS;
}
void Button::Begin(){pinMode(_pin, INPUT_PULLUP);}
void Button::Pressed(){
_currMs = millis(); // Current Ms
if (_currMs - _btnReadPrevMs > 5) { // Run debounce routine every 5ms
_btnReadPrevMs = _currMs;
_state = (_state<<1) | digitalRead(_pin); // Left shift state OR in current input OR mask out higher byte
btnState = _state;
if (_state == 0x00) { // Set pressed state if button accumulator is all 0's
isPressed = true;
if (_currMs - _pressedPrevMs > _LONG_PRESS_MS) { // Set long pressed if button pressed for > long time
_pressedPrevMs = _currMs;
isLongPress = true;
}
}
}
if (isPressed && !isLongPress && _state == 0xFF) {shortPress = true;} // Set short press when button released < long time
if (isPressed && isLongPress && _state == 0xFF) {longPress = true;} // Set long press when button released > long time
if (_state == 0xFF) {isPressed = false; isLongPress = false; _pressedPrevMs = _currMs;} // Reset button pressed states
}
void Button::ResetPressed() {
shortPress = false;
longPress = false;
}