#include <Arduino.h>
#include <Wire.h> // needed for LCD with PCF8574 port expander
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <Pushbutton.h>
#define DISP_ITEM_ROWS 3
#define DISP_CHAR_WIDTH 20
#define PACING_MS 25
#define FLASH_RST_CNT 30
#define SETTINGS_CHKVAL 3647
//----------------------------------------
// DECLARATIONS
//----------------------------------------
// I/O PORT ALLOCATIONS
const int BTN_OK = A0;
const int BTN_BACK = A3;
const int BTN_UP = A2;
const int BTN_DOWN = A1;
const int BTN_PLUS = 2;
const int BTN_MINUS = 3;
// BUTTONS
Pushbutton btnOk(BTN_OK);
Pushbutton btnBack(BTN_BACK);
Pushbutton btnUp(BTN_UP);
Pushbutton btnDown(BTN_DOWN);
Pushbutton btnPlus(BTN_PLUS);
Pushbutton btnMinus(BTN_MINUS);
// MENU STRUCTURE
enum pageType {
MENU_ROOT,
MENU_SUB1,
MENU_SUB1_A,
MENU_SUB1_B,
MENU_SUB2,
MENU_SUB3,
MENU_SUB4,
MENU_SETTINGS,
};
enum pageType currPage = MENU_ROOT;
void page_MenuRoot();
void page_MenuSub1();
void page_MenuSub1_A();
void page_MenuSub1_B();
void page_MenuSub2();
void page_MenuSub3();
void page_MenuSub4();
void page_MenuSettings();
// MENU INTERNALS
uint32_t loopStartMs;
bool updateAllItems;
bool updateItemsValue;
uint8_t ItemsCnt;
uint8_t pntrPos;
uint8_t dispOffset;
uint8_t flashCntr;
bool flashIsOn;
void initMenuPage(String title, uint8_t itemCount);
void captureButtonDownStates();
void adjustBoolean(boolean *v);
void adjustUint8_t(uint8_t *v, uint8_t min, uint8_t max);
void doPointerNavigation();
bool isFlashChanged();
void pacingWait();
bool menuItemPrintable(uint8_t xPos, uint8_t yPos);
// PRINT TOOLS
void printPointer();
void printOffsetArrows();
void printOnOff(bool val);
void printUint32_tAtWidth(uint32_t value, uint8_t width, char c, boolean isRight);
// SETTINGS
struct MySettings{
boolean Test1_OnOff = false;
uint8_t Test2_Num = 60;
uint8_t Test3_Num = 255;
uint8_t Test4_Num = 0;
uint8_t Test5__OnOff = true;
uint8_t Test6_Num = 197;
uint16_t settingCheckValue = SETTINGS_CHKVAL;
};
MySettings settings;
void sets_SetDefaults();
void sets_Load();
void sets_Save();
// DISPLAY
LiquidCrystal_I2C lcd(0x27, 20, 4);
byte chrUp[] = {0b00000,
0b00100,
0b00100,
0b01110,
0b01110,
0b11111,
0b11111,
0b00000,};
byte chrDn[] = {0b00000,
0b11111,
0b11111,
0b01110,
0b01110,
0b00100,
0b00100,
0b00000,};
byte chrF1[] = {0b10000,
0b01000,
0b10100,
0b01010,
0b10100,
0b01000,
0b10000,
0b00000,};
byte chrF2[] = {0b00001,
0b00010,
0b00101,
0b01010,
0b00101,
0b00010,
0b00001,
0b00000,};
byte chrAr[] = {0b00000,
0b01100,
0b11110,
0b10010,
0b11110,
0b01100,
0b00000,
0b00000,};
// =======================================================================================
// || SETUP ||
// =======================================================================================
void setup() {
lcd.init();
lcd.createChar(1, chrUp);
lcd.createChar(2, chrDn);
lcd.createChar(3, chrF1);
lcd.createChar(4, chrF2);
lcd.createChar(5, chrAr);
lcd.backlight(); // turn on backlight
lcd.clear();
sets_Load();
}
// =======================================================================================
// || MAIN LOOP ||
// =======================================================================================
void loop() {
switch (currPage){
case MENU_ROOT: page_MenuRoot(); break;
case MENU_SUB1: page_MenuSub1(); break;
case MENU_SUB1_A: page_MenuSub1_A(); break;
case MENU_SUB1_B: page_MenuSub1_B(); break;
case MENU_SUB2: page_MenuSub2(); break;
case MENU_SUB3: page_MenuSub3(); break;
case MENU_SUB4: page_MenuSub4(); break;
case MENU_SETTINGS: page_MenuSettings(); break;
}
}
// =======================================================================================
// || ROOT MENU ||
// =======================================================================================
void page_MenuRoot(){
// initializes the menu page
initMenuPage(F("MAIN MENU"), 5);
//inner loop
while(true){
// print the display items when requested
if (updateAllItems){
// print the visible items
if (menuItemPrintable(1, 1)){lcd.print(F("Sub Menu #1"));}
if (menuItemPrintable(1, 2)){lcd.print(F("Sub Menu #2"));}
if (menuItemPrintable(1, 3)){lcd.print(F("Sub Menu #3"));}
if (menuItemPrintable(1, 4)){lcd.print(F("Sub Menu #4"));}
if (menuItemPrintable(1, 5)){lcd.print(F("Settings "));}
}
// update any flashing items
if (isFlashChanged()){printPointer();}
// always clear update flags by this point
updateAllItems = false;
// capture the button down states
captureButtonDownStates();
// check for the OK button
if (btnOk.PressReleased()){
switch (pntrPos){
case 1: currPage = MENU_SUB1; return;
case 2: currPage = MENU_SUB2; return;
case 3: currPage = MENU_SUB3; return;
case 4: currPage = MENU_SUB4; return;
case 5: currPage = MENU_SETTINGS; return;
}
}
// otherwise check for pointer up or down buttons
doPointerNavigation();
// keep a specific pace
pacingWait();
}
}
// =======================================================================================
// || MENU SUB#1 ||
// =======================================================================================
void page_MenuSub1(){
while(true){}
}
// =======================================================================================
// || MENU SUB#1_A ||
// =======================================================================================
void page_MenuSub1_A(){
while(true){}
}
// =======================================================================================
// || MENU SUB#1_B ||
// =======================================================================================
void page_MenuSub1_B(){
while(true){}
}
// =======================================================================================
// || MENU SUB#2 ||
// =======================================================================================
void page_MenuSub2(){
while(true){}
}
// =======================================================================================
// || MENU SUB#3 ||
// =======================================================================================
void page_MenuSub3(){
while(true){}
}
// =======================================================================================
// || MENU SUB#4 ||
// =======================================================================================
void page_MenuSub4(){
while(true){}
}
// =======================================================================================
// || MENU SETTINGS ||
// =======================================================================================
void page_MenuSettings(){
while(true){}
}
// =======================================================================================
// || TOOLS - MENU INTERNALS ||
// =======================================================================================
void initMenuPage(String title, uint8_t itemCount){
// clear the screen
lcd.clear();
// set the cursor position
lcd.setCursor(0,0);
// print the title with fill characters
uint8_t fillCnt = (DISP_CHAR_WIDTH - title.length()) / 2;
if (fillCnt > 0){for(uint8_t i = 0; i < fillCnt; i++){lcd.print(F("\03"));}} // 01234567890123456789
lcd.print(title);
if ((title.length() % 2) == 1){fillCnt++;}
if (fillCnt > 0){for(uint8_t i = 0; i < fillCnt; i++){lcd.print(F("\04"));}} //>>>>>>MAIN MENU<<<<<<
// clear all button down states
btnUp.ClearWasDown();
btnDown.ClearWasDown();
btnOk.ClearWasDown();
btnBack.ClearWasDown();
btnPlus.ClearWasDown();
btnMinus.ClearWasDown();
// set the menu item count
itemCnt = itemCount;
// current pointer position
pntrPos = 1;
//display offset
dispOffset = 0;
// flash counter > force immediate draw at startup
flashCntr = 0;
//flash state > will switch to being on at startup
flashIsOn = false;
// force a full update
updateAllItem = true;
// capture start time
loopStartMs = millis();
}
//void initMenuPage(String title, uint8_t itemCount){}
void captureButtonDownStates(){
btnUp.CaptureDownState();
btnDown.CaptureDownState();
btnOk.CaptureDownState();
btnBack.CaptureDownState();
btnMinus.CaptureDownState();
btnPlus.CaptureDownState();
}
void adjustBoolean(boolean *v){}
void adjustUint8_t(uint8_t *v, uint8_t min, uint8_t max){}
void doPointerNavigation(){
// move pointer up
if (btnUp.PressReleased() && pntrPos > 1){
// erase the current pointer & reset the flash state to get imediate redraw
flashIsOn = false; flashCntr = 0; printPointer();
// if the cursor is already at the top of the display then resquest a list update too
if (pntrPos - dispOffset == 1){updateAllItems = true; dispOffset--;}
// move the pointer
pntrPos--;
}
// move pointer down
else if (btnDown.PressReleased() && pntrPos < itemCnt){
// erase the current pointer & reset the flash state to get imediate redraw
flashIsOn = false; flashCntr = 0; printPointer();
// if the cursor is already at the top of the display then resquest a list update too
if (pntrPos - dispOffset == DISP_ITEM_ROWS){updateAllItems = true; dispOffset++;}
// move the pointer
pntrPos++;
}
}
bool isFlashChanged(){
// check if counter expired
if (flashCntr == 0){
// flip the pointer stade
flashIsOn = !flashIsOn;
// reset the pointer counter
flashCntr = FLASH_RST_CNT;
// indicate it is time to flash
return true;
}
// decrase the flash counter and send a negative response
else {flashCntr--; return false}
}
void pacingWait(){
// do the pacing wait
while (millis() - loopStartMs < PACING_MS) {delay(1);}
// capture start time
loopStartMs = millis();
}
bool menuItemPrintable(uint8_t xPos, uint8_t yPos){
// basic check to see if the basic condition to allow showing this item
if (!(updateAllItems || (updateItemValue && pntrPos == yPos))){return false;}
// make a value use later to check if ther is enough offset to make the value visible
uint8_t yMaxOffset = 0;
// this case means the value is typically beyond the offset display, so we remove the visible row count
if (yPos > DISP_ITEM_ROWS) {yMaxOffset = yPos - DISP_ITEM_ROWS;}
// talking into account any offset, check if the item position is currently visible -> position cursor and return true if so
if (dispOffset <= (yPos-1) && dispOffset >= yMaxOffset) {lcd.setCursor(xPos, yPos - dispOffset); return true;}
// otherwise just return false
return false;
}
// =======================================================================================
// || TOOLS - DISPLAY ||
// =======================================================================================
void printPointer(){
//dont allow printing pointer if less than 2 items
if (itemCnt < 2){return;}
// move the cusor
lcd.setCursor(0, pntrPos - dispOffset);
// show the pointer if set
if (flashIsOn){lcd.print(F("\05"));}
// otherwise hide the pointer
else {lcd.print(F(" "));}
}
void printOffsetArrows(){}
void printOnOff(bool val){}
void printUint32_tAtWidth(uint32_t value, uint8_t width, char c, boolean isRight){}
// =======================================================================================
// || TOOLS - SETTINGS ||
// =======================================================================================
void sets_SetDefaults(){
MySettings tempSets;
memcpy(&settings, &tempSets, sizeof settings);
}
void sets_Load(){
EEPROM.get(0, settings);
if (settings.settingCheckValue != SETTINGS_CHKVAL){sets_SetDefaults();}
}
void sets_Save(){
EEPROM.put(0, settings);
}
/*
void printSelected(uint8_t p1, uint8_t p2){
if(p1 == p2){
Serial.print(F("--> "));
}
else {
Serial.print(F(" "));
}
}
void clearScreen(void){
for (uint8_t i = 0; i < 100; i++) {Serial.println();}
}
void printDivider(void){
for (uint8_t i = 0; i < 40; i++) {Serial.print("-");}
Serial.println();
}
}
boolean updateDisplay = true;
boolean btn_Up_WasDown = false;
boolean btn_Down_WasDown = false;
boolean btn_Accept_WasDown = false;
constexpr byte cols = 20; // columns/characters per row
constexpr byte rows = 4; // how many rows
constexpr byte addr = 0x27; // set the LCD address to 0x3F or 0x27
LiquidCrystal_I2C lcd(addr, cols, rows); // create lcd object - with support of special characters
// HERE IS PART#1
#define ROOT_MENU_CNT 3
#define SUB_MENU1_CNT 4
#define SUB_MENU2_CNT 5
#define SUB_MENU3_CNT 2
// setup the emum with all the menu pages options
enum pageType {ROOT_MENU, SUB_MENU1, SUB_MENU2, SUB_MENU3};
// holds which page is currently selected
enum pageType currPage = ROOT_MENU;
// selected item pointer for the root menu
uint8_t root_Pos = 1;
// constants holding port addresses
// =======================================================================================
// || PAGE - ROOT MENU ||
// =======================================================================================
void page_RootMenu(void) {
//flag for updating the display
boolean updateDisplay = true;
// tracks when entered top of loop
uint32_t loopStartMs;
//tracks button states
boolean btn_Up_WasDown = false;
boolean btn_Down_WasDown = false;
boolean btn_Accept_WasDown = false;
//inner loop
while (true){
// capture start time
loopStartMs = millis();
// print the display
if (updateDisplay){
// clear the update flag
updateDisplay = false;
//clear the display
clearScreen();
//menu title
Serial.println(F("[ MAIN MENU ]"));
//print a divider line
printDivider();
// print the items
printSelected(1, root_Pos); Serial.println(F("Sub Menu One"));
printSelected(2, root_Pos); Serial.println(F("Sub Menu Two"));
printSelected(3, root_Pos); Serial.println(F("Sub Menu Three"));
Serial.println();
Serial.println();
//print a divider line
printDivider();
}
// capture the button down states
if (btnIsDown(BTN_UP)) {btn_Up_WasDown = true;}
if (btnIsDown(BTN_DOWN)) {btn_Down_WasDown = true;}
if (btnIsDown(BTN_ACCEPT)) {btn_Accept_WasDown = true;}
//move the pointer down
if (btn_Down_WasDown && btnIsUp(BTN_DOWN)){
if (root_Pos == ROOT_MENU_CNT) {root_Pos = 1;} else {root_Pos++;}
updateDisplay = true;
btn_Down_WasDown = false;
}
//move the pointer Up
if (btn_Up_WasDown && btnIsUp(BTN_UP)){
if (root_Pos == 1) {root_Pos = ROOT_MENU_CNT;} else {root_Pos--;}
updateDisplay = true;
btn_Up_WasDown = false;
}
//move to the selected page
if (btn_Accept_WasDown && btnIsUp(BTN_ACCEPT)){
switch (root_Pos) {
case 1: currPage = SUB_MENU1; return;
case 2: currPage = SUB_MENU2; return;
case 3: currPage = SUB_MENU3; return;
}
}
// keep a specific pace
while (millis() - loopStartMs < 25) {delay(2);}
}
}
// =======================================================================================
// || PAGE - SUB MENU1 ||
// =======================================================================================
void page_SubMenu1(void) {
//flag for updating the display
boolean updateDisplay = true;
// tracks when entered top of loop
uint32_t loopStartMs;
//tracks button states
boolean btn_Up_WasDown = false;
boolean btn_Down_WasDown = false;
boolean btn_Cancel_WasDown = false;
// selected item pointer
uint8_t sub_Pos = 1;
//inner loop
while (true){
// capture start time
loopStartMs = millis();
// print the display
if (updateDisplay){
// clear the update flag
updateDisplay = false;
//clear the display
clearScreen();
//menu title
Serial.println(F("[ SUB MENU #1 ]"));
//print a divider line
printDivider();
// print the items
printSelected(1, sub_Pos); Serial.println(F("The First Item"));
printSelected(2, sub_Pos); Serial.println(F("The Second Item"));
printSelected(3, sub_Pos); Serial.println(F("The Third Item"));
printSelected(4, sub_Pos); Serial.println(F("The Forth Item"));
Serial.println();
//print a divider line
printDivider();
}
// capture the button down states
if (btnIsDown(BTN_UP)) {btn_Up_WasDown = true;}
if (btnIsDown(BTN_DOWN)) {btn_Down_WasDown = true;}
if (btnIsDown(BTN_CANCEL)) {btn_Cancel_WasDown = true;}
//move the pointer down
if (btn_Down_WasDown && btnIsUp(BTN_DOWN)){
if (sub_Pos == SUB_MENU1_CNT) {sub_Pos = 1;} else {sub_Pos++;}
updateDisplay = true;
btn_Down_WasDown = false;
}
//move the pointer Up
if (btn_Up_WasDown && btnIsUp(BTN_UP)){
if (sub_Pos == 1) {sub_Pos = SUB_MENU1_CNT;} else {sub_Pos--;}
updateDisplay = true;
btn_Up_WasDown = false;
}
//move to the go to the root menu
if (btn_Cancel_WasDown && btnIsUp(BTN_CANCEL)){currPage = ROOT_MENU; return;}
// keep a specific pace
while (millis() - loopStartMs < 25) {delay(2);}
}
}
// =======================================================================================
// || PAGE - SUB MENU2 ||
// =======================================================================================
void page_SubMenu2(void) {
//flag for updating the display
boolean updateDisplay = true;
// tracks when entered top of loop
uint32_t loopStartMs;
//tracks button states
boolean btn_Up_WasDown = false;
boolean btn_Down_WasDown = false;
boolean btn_Cancel_WasDown = false;
// selected item pointer
uint8_t sub_Pos = 1;
//inner loop
while (true){
// capture start time
loopStartMs = millis();
// print the display
if (updateDisplay){
// clear the update flag
updateDisplay = false;
//clear the display
clearScreen();
//menu title
Serial.println(F("[ SUB MENU #2 ]"));
//print a divider line
printDivider();
// print the items
printSelected(1, sub_Pos); Serial.println(F("The First Item"));
printSelected(2, sub_Pos); Serial.println(F("The Second Item"));
printSelected(3, sub_Pos); Serial.println(F("The Third Item"));
printSelected(4, sub_Pos); Serial.println(F("The Forth Item"));
printSelected(5, sub_Pos); Serial.println(F("The Fifth Item"));
//print a divider line
printDivider();
}
// capture the button down states
if (btnIsDown(BTN_UP)) {btn_Up_WasDown = true;}
if (btnIsDown(BTN_DOWN)) {btn_Down_WasDown = true;}
if (btnIsDown(BTN_CANCEL)) {btn_Cancel_WasDown = true;}
//move the pointer down
if (btn_Down_WasDown && btnIsUp(BTN_DOWN)){
if (sub_Pos == SUB_MENU2_CNT) {sub_Pos = 1;} else {sub_Pos++;}
updateDisplay = true;
btn_Down_WasDown = false;
}
//move the pointer Up
if (btn_Up_WasDown && btnIsUp(BTN_UP)){
if (sub_Pos == 1) {sub_Pos = SUB_MENU2_CNT;} else {sub_Pos--;}
updateDisplay = true;
btn_Up_WasDown = false;
}
//move to the go to the root menu
if (btn_Cancel_WasDown && btnIsUp(BTN_CANCEL)){currPage = ROOT_MENU; return;}
// keep a specific pace
while (millis() - loopStartMs < 25) {delay(2);}
}
}
//HERE IS PART#2
// =======================================================================================
// || PAGE - SUB MENU3 ||
// =======================================================================================
void page_SubMenu3(void) {
//flag for updating the display
boolean updateDisplay = true;
// tracks when entered top of loop
uint32_t loopStartMs;
//tracks button states
boolean btn_Up_WasDown = false;
boolean btn_Down_WasDown = false;
boolean btn_Cancel_WasDown = false;
// selected item pointer
uint8_t sub_Pos = 1;
//inner loop
while (true){
// capture start time
loopStartMs = millis();
// print the display
if (updateDisplay){
// clear the update flag
updateDisplay = false;
//clear the display
clearScreen();
//menu title
Serial.println(F("[ SUB MENU #3 ]"));
//print a divider line
printDivider();
// print the items
printSelected(1, sub_Pos); Serial.println(F("The First Item"));
printSelected(2, sub_Pos); Serial.println(F("The Second Item"));
Serial.println();
Serial.println();
Serial.println();
//print a divider line
printDivider();
}
// capture the button down states
if (btnIsDown(BTN_UP)) {btn_Up_WasDown = true;}
if (btnIsDown(BTN_DOWN)) {btn_Down_WasDown = true;}
if (btnIsDown(BTN_CANCEL)) {btn_Cancel_WasDown = true;}
//move the pointer down
if (btn_Down_WasDown && btnIsUp(BTN_DOWN)){
if (sub_Pos == SUB_MENU3_CNT) {sub_Pos = 1;} else {sub_Pos++;}
updateDisplay = true;
btn_Down_WasDown = false;
}
//move the pointer Up
if (btn_Up_WasDown && btnIsUp(BTN_UP)){
if (sub_Pos == 1) {sub_Pos = SUB_MENU3_CNT;} else {sub_Pos--;}
updateDisplay = true;
btn_Up_WasDown = false;
}
//move to the go to the root menu
if (btn_Cancel_WasDown && btnIsUp(BTN_CANCEL)){currPage = ROOT_MENU; return;}
// keep a specific pace
while (millis() - loopStartMs < 25) {delay(2);}
}
}
// =======================================================================================
// || TOOLS - BUTTON PRESSING ||
// =======================================================================================
boolean btnIsDown(int btn){
return digitalRead(btn) == LOW && digitalRead(btn) == LOW;
}
boolean btnIsUp(int btn){
return digitalRead(btn) == HIGH && digitalRead(btn) == HIGH;
}
*/