// LCD1602 to Arduino with function parameters and char arrays.
////Dec 6 22:08
// Displaying menus, and moving from one menu item to the next by a single button push
//The code searches the char array looking for the next menu item, finds it and places the cursor under the first letter
//This skips menu items of any length and empty spaces.
//1 press of "left" or "right" button skips to the next menu item
//Direction is controlled through A0 (for test using pot) "left" or "right" button skips to the next menu item
//As a test if it finds the "Color" label the menu is changed from the Main menu to the Color menu
//Dec 1
// Have state of menu. Function prints the relevant menu based on state.
//need expand the function that changes state of menu based on label selected
//moving this to VSC/platformIO:
//Need #include<Arduino.h>
//Can't compare char array to string "Color" etc
//The "Color" etc have to be declared as char arrays.
//The pins of the LCD are different
//The values of the input buttons are different
//Some functions say they return, but don't - warning
//One has no return value - warning
#include <LiquidCrystal.h>
///////////////////// LED indicators which are not needed. They show inner working of search wihtin menus
#define labelLED 2
#define spaceLED 3
#define textLED 4
enum stateOfbutton_t {unknown, select, left, up, down, RIGHT};// 5 push button inputs read by analogue voltage
//The values read in the function were set on a simulator & will need to be changed to match the actual buttons/voltages
enum stateOfMenu_t
{MenuUnknown,
showMain1,
show2Color, show2Flicker, show2Day,show2OnOff, // level 2 of hierarchy of menu
show3Color, show3Flicker, show3Day, show3OnOff, // level 3
showNoMenu} menuState;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);// This is set on a simulator. May not theyMatch the actual hardware<<<<<
stateOfbutton_t buttonState=unknown;// buttonState is used in other parts of the software
enum pointTo_t {arrowLeft, arrowRight};
//;char instructions[4]
////menu labels
char
Main = "MAIN",
Flicker="Flicker",
Day="Day",
color = "Color",
OnOff = "OnOff",
EXIT = "EXIT",
OutputMarker = ">OUT<",
SelectLED="SelectLED",
Length="Length",
upDown="up/down",
ROTATE= "ROTATE",
SpeedDepth ="SpeedDepth",
SpeedChance = "SpeedChance";
//////////////////// new stuff
void ShowProjectName(){
Serial.print ("ProejctName");
lcd.clear();
lcd.setCursor(0,0);
lcd.print(" Lights On !");
}
void displayTopRow( char* labelLeft, pointTo_t arrow, char* labelRight ){
lcd.setCursor(0,0);
lcd.print(labelLeft); if(arrow == arrowLeft) lcd.print("<-"); else lcd.print(" ");//delete <-
lcd.setCursor(9,0);
if(arrow == arrowRight) lcd.print("->"); else lcd.print(" ");
lcd.print(labelRight);
}
void displayBottomRow(){
lcd.setCursor(0,1);
lcd.print("Select confirms ");
delay(100);
lcd.setCursor(0,1);
lcd.print("L R to change ");
delay(100);
lcd.setCursor(0,1);
lcd.print("Down to next.. ");
delay(100);
lcd.setCursor(0,1);
lcd.print("Up to go back ");
}
void showMainMenu(){
displayTopRow( "Main" , arrowRight , "EXIT" );
displayBottomRow();
}
void showColorMenu(){
displayTopRow( "Color" , arrowRight , "EXIT" );
displayBottomRow();
}
void showMenu(stateOfMenu_t menuState, pointTo_t arrowPointer){
switch (menuState){
case showNoMenu: ShowProjectName(); break;//does this cause problems?
case MenuUnknown: break;//does this cause problems?
case showMain1: displayTopRow( "Main" , arrowRight , "EXIT" ); break;
case show2Color:displayTopRow( "Color" , arrowRight , "EXIT" ); break;
case show2Flicker:displayTopRow("Flicker" , arrowRight , "EXIT" ); break;
case show2Day: displayTopRow( "Day" , arrowRight , "EXIT" ); break;
case show2OnOff: displayTopRow( "OnOff" , arrowRight , "EXIT" ); break;
case show3Color:displayTopRow( "Colo3 " , arrowRight , "EXIT" ); break;
case show3Flicker:displayTopRow( "F3" , arrowRight , "EXIT" ); break;
case show3OnOff: displayTopRow( "onOff3" , arrowRight , "EXIT" ); break;
}
}
void moveDownToNextMenuLable(stateOfMenu_t &menuState, pointTo_t &arrowPointer){
Serial.print(" In moveDownToNextMenuLabel() "); //buttonState= ");
arrowPointer = arrowRight; //point it at EXIT in new menu
//pointTo_t {arrowLeft, arrowRight};
//Serial.print( buttonState);
Serial.print(" menuState = ");
Serial.print(menuState);
//Serial.print(" arrowPointer = ");
//Serial.print( arrowPointer);
switch (menuState){
case showNoMenu: break;//does this cause problems?
case MenuUnknown: break;//does this cause problems?
case showMain1: menuState=show2Color; break;
case show2Color:menuState=show2Flicker; break;
case show2Flicker:menuState=show2Day; break;
case show2Day: menuState=show2OnOff; break;
case show2OnOff: menuState=showMain1; break;
case show3Flicker: break;
case show3OnOff: break;
}
}
void moveUPToPreviousMenuLable(stateOfMenu_t menuState){
Serial.print(" In moveUPToPreviousMenuLable() ");
}
/////////////////////////////////////// Respond
void respondToButtons(stateOfMenu_t &menuState, pointTo_t &arrowPointer, stateOfbutton_t buttonState)
{
//need take into acount menu state & button state
Serial.print(" In Respond buttonState= ");
Serial.print( buttonState);
Serial.print(" menuState = ");
Serial.print(menuState);
Serial.print(" arrowPointer = ");
Serial.print( arrowPointer);
/////////////// LEFT or RIGHT
// pointTo_t {arrowLeft, arrowRight};
if (buttonState == left || buttonState == RIGHT)
{// swap direction of arrow
if (arrowPointer == arrowLeft) arrowPointer = arrowRight;
else if (arrowPointer == arrowRight) arrowPointer = arrowLeft;
}
Serial.print(" checked LRUD ArrowPointer==");
Serial.print(arrowPointer);
///////////////// UP or DOWN
if (buttonState == down) moveDownToNextMenuLable(menuState, arrowPointer);
else if (buttonState == up) moveUPToPreviousMenuLable(menuState);
else
/////////////////// SELECT
/*
stateOfMenu_t
{MenuUnknown,
showMain1,
show2Color, show2Flicker, show2Day,show2OnOff, // level 2 of hierarchy of menu
show3Flicker, show3Day, show3OnOff, // level 3
showNoMenu} menuState;
enum pointTo_t {arrowLeft, arrowRight};
*/
if (buttonState == select) {
//depends on menu and arrow
Serial.print(" select ");
switch (menuState){
case showNoMenu: if(buttonState!= unknown) menuState= MenuUnknown; // any button changes state
buttonState = unknown; break;//does this cause problems?
case MenuUnknown: if(buttonState) menuState= showMain1; buttonState = unknown;
break; //does this cause problems?
case showMain1: if(arrowPointer == arrowLeft) {
menuState=showNoMenu;
buttonState = unknown;
arrowPointer = arrowRight;}
else if(arrowPointer == arrowRight) {
menuState=showNoMenu;
buttonState = unknown;
}
ShowProjectName();
break;
//clicking either MAIN or Exit means exit
case show2Color:if(arrowPointer == arrowLeft) {menuState=show3Color; buttonState = unknown;}
else if(arrowPointer == RIGHT) {menuState=showMain1; buttonState = unknown;} break;
//Right is always pointing to exit? So go up one level of menu
case show2Flicker: if(arrowPointer == arrowLeft) {menuState=show3Flicker; buttonState = unknown;}
else if(arrowPointer == RIGHT) {menuState=showMain1; buttonState = unknown;} break;
//Right is always pointing to exit? So go up one level of menu break;
case show2Day: break;
case show2OnOff: break;
case show3Flicker: break;
case show3OnOff: break;
}
}
Serial.print(" leaving Rspond buttonState= ");
Serial.print( buttonState);
Serial.print(" menuState = >");
Serial.print(menuState);
Serial.print("< arrowPointer = ");
Serial.println( arrowPointer);
Serial.println();
displayBottomRow();
buttonState = unknown; //try to prevent double response to buttons
}
//////////////////////////////// setup
void setup(){
lcd.begin(16, 2);
Serial.begin(115200);
Serial.println("Newer method");
// pointTo_t {arrowLeft, arrowRight};
pointTo_t arrowPointer = arrowRight;
/*
menuState=showMain1;
menuState=show2Color;
menuState=show2Flicker;
menuState=show2OnOff;
menuState=show2Day;
*/
menuState=showMain1;
showMenu(menuState, arrowPointer);
/*
buttonState = left;
respondToButtons(menuState,arrowPointer, buttonState);
showMenu(menuState, arrowPointer);
buttonState = RIGHT;
respondToButtons(menuState,arrowPointer, buttonState);
showMenu(menuState, arrowPointer);
//prints on right but doesn't delete print on left
*/
buttonState = down;
respondToButtons(menuState,arrowPointer, buttonState);
showMenu(menuState, arrowPointer);
buttonState = left;
respondToButtons(menuState,arrowPointer, buttonState);
showMenu(menuState, arrowPointer);
delay(1000);
buttonState = select; //fails to select the menu label Color<- but menu state is 6
respondToButtons(menuState,arrowPointer, buttonState);
showMenu(menuState, arrowPointer);
}
void loop(){
}
/////////////////////////////////////// end of new stuff
/*
uint8_t DegreeBitmap[]= { 0x6, 0x9, 0x9, 0x6, 0x0, 0, 0, 0 };
byte RightArrow[8]= {
0b00010,
0b00100,
0b01000,
0b10000,
0b0100,
0b00100,
0b00010,
0b00001
};
lcd.createChar(1, RightArrow);
lcd.write(1);
*/
///////////////////// The hierachy of menus for user input
char menu1Main [] {"MAIN Flicker Day Color OnOff EXIT"};//has an extra space between day & color to separate them, but that 1 space is not shown on LCD
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu2Color[] {"COLOR: >OUT< Select_LED EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu2Flicker[] {"FLICKER: >OUT< Select_LED EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu2Day[] {"DAY: Length>OUT< up/down EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu2OnOff[] {"ONOFF:Select_LED >OUT< onOff EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu3Color[] {"ROTATE: >OUT< EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu3Flicker[] {"Speed Depth >OUT Chance EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
char menu3OnOff[] {"Speed Chance >OUT< >OUT< EXIT"}; // this is a 2nd level menu
// 0123456789ABCDEF0123456789ABCDEF0 =33 elements
//stateOfMenu_t menuState = MenuUnknown;
bool row=1; //test only
int startColumn=12 ;//TEST ONLY. Could start with 12,1 as the place where Exit is on lcd
int labelPosition;
char thisLabel[10];
char* menu_ptr=menu1Main;// Not sure if it should start with this value
///////////////////// Put the curso in position and wait so that user can see it move
void displayCursor(int col, bool row){
lcd.setCursor(col,row);
delay(500); // the delay is just here so the movement of the cursor can be seen for testing
}
///////////////////// Read the Menu Label using the known position in the menu array. (So can understand what to do)
char readMenuLabel(char *menu, int labelStart){
//lcd.print(labelStart);delay(1000);
//char thisLabel[10]; //is that big enough for any of the labels?
// if(labelStart==0 ) thisLabel[0]='\0'; //ignore the first label which is at positin 0,0. It's the menu title
// else{
for(int i=labelStart;i<34;i++){//menu has 33 characters including 1 non-printing space separating the rows
thisLabel[i-labelStart] = menu[i];
if( menu[i]==' ') { //the first space found is the end of the label. Not here if already past end of menu
//what if label is EXIT? that is last thing in menu i=33. Menu 34 = null?
thisLabel[i-labelStart]='\0';
break;
// }
// lcd.print(thisLabel);
}
}
//lcd.setCursor(0,0);//lcd.print(":"); /
//lcd.clear();
//lcd.print(thisLabel); //TEST
return thisLabel;
}
///////////////////// theyMatch char arrays
int theyMatch(char a[],char b[]){
for(int i=0;a[i]!='\0';i++){
if (i>10) break;//my limit size
if(a[i]!=b[i])
return 0;
}
return 1;
}
///////////////////// use the label in the menu to set the menustate so that the correct one will be printed
int useLabelToSetMenuState(){//when select button pressed?
//lcd.print (theyMatch(thisLabel , "Color"));//returning 0 always
//lcd.print(thisLabel);
//if(theyMatch(thisLabel , "Color")) {menu_ptr=menu2Color; PrintMenu(menu_ptr);}//MOVE the print
if(theyMatch(thisLabel , "Color")) {menuState = show2Color;}//doesn't change the menu
else
//if(theyMatch(thisLabel , "Flicker")) {menu_ptr=menu2Flicker; PrintMenu(menu_ptr);}//MOVE the print
if(theyMatch(thisLabel , "Flicker")) {menuState = show2Flicker;}//
else
if(theyMatch(thisLabel , "Day")) {menuState = show2Day;}//
else
if(theyMatch(thisLabel , "OnOff")) {menuState = show2OnOff;}//
else
//
if(theyMatch(thisLabel , "EXIT")) {exitToPreviousMenu();}//
//???
}
///////////////////// EXIT was selected. Move to previous menu level or leave the menus & return to LED routine
void exitToPreviousMenu(){
lcd.setCursor(0,0); lcd.print("Exit to previous"); //
//switch (menuState){
// case:
//If was main menu
//set menu state to none
//clear LCD
//Print the name
// Set system state back to active.
//case
//if a level 2 menu go to main
//if level 3 go to relevant level 2
}
///////////////////// Look for next label to LEFT in current menu Difference: decrement index, How handle end of LCD display, order of skip
int8_t FindLabelLeft(char *menu ){ // Return value is used to set labelPosition.
//assuming 12,1 is where EXIT is displayed
//lcd.setCursor(12,1);//????????????????????????????????????/
int i=startColumn;
displayCursor(i, row);
lcd.cursor();// make cursor visible
if(i==0) {i=15; row = !row;}//if at start of LCD row change output to be end of other row
else {//lcd.setCursor(5,1);lcd.print(i);
while(menu[i+(row*17)]!= ' ') //skip spaces
{ i--;
if(i<=0) {i=15; row = !row;}// if reached start of row move to other row end =15
displayCursor(i, row);}
}
//found space between first lable and label to the left
digitalWrite(spaceLED, HIGH);
//delay(1000);
while(menu[i+(row*17)] == ' ')
{i--; if(i<=0) {i=15; row = !row;}// move to other row far right col =15
displayCursor(i, row);} //skip through blank spaces bewteen labels.
//Now found end of left label
digitalWrite(textLED, HIGH);digitalWrite(spaceLED, LOW);
//delay(1000);
while(menu[i+(row*17)]!= ' ' && i>0)//skip text until find the next space
{ i--;
displayCursor(i, row);
}// skip through the left label till find space OR i=0 Label begins at 0 or at i+1
//Now found label to the left
digitalWrite(labelLED, HIGH);digitalWrite(textLED, LOW);
if(i<=0) startColumn=0; else startColumn = i+1; //The label was one column to the right, except at 0
lcd.setCursor(startColumn,row);
//lcd.print(startColumn); delay(2000);lcd.print((startColumn+(row*17)));
return (startColumn+(row*17)); // not sure how this will be used - respond to SELECT button
//when (0,0) this returns 17 which is 16 top row + 1 not displayed space in the array
//top row is (0,0)==0 to (15,0)==15 [added space 16] then
//bottom row (0,1)==17 to (15,1)==32
}
///////////////////// find next label to RIGHT in menu (Difference: increment index, How handle end of LCD display, order of skip
int FindLabelRight(char *menu){ //can left & right be combined? // Return value is used to set labelPosition.
int i=startColumn;
displayCursor(i, row);
lcd.cursor();// make cursor visible
//delay(500);
digitalWrite(textLED, HIGH);//already on text
if(i==15) {i=0; row = !row;}//if at end of LCD row, change to start of other row
else {//lcd.setCursor(5,1);lcd.print(i);
while(menu[i+(row*17)]!= ' ' && i<16)//skip text, but not past end of row
{ i++; //lcd.setCursor(0,1);lcd.print(i);//????
//displayCursor(i, row);delay(1000);
if(i>15) {i=0; row = !row; break;}// move to other row far left col =0
displayCursor(i, row);}
delay(200);//just to make cursor movement visible.
}
//found space between first label and label to the right
digitalWrite(spaceLED, HIGH);
//delay(500);
while(menu[i+(row*17)] == ' ')//skip spaces
{i++; if(i>15) {i=0; row = !row;}// move to other row far right col =15
displayCursor(i, row);
} //skip through blank spaces bewteen labels.
//Now found start of right label
digitalWrite(textLED, HIGH);digitalWrite(spaceLED, LOW);
delay(100);
digitalWrite(labelLED, HIGH);//digitalWrite(textLED, LOW);
displayCursor(i, row);
startColumn =i;//reset for the next movement from this point
return (i+(row*17)); // not sure how this will be used - respond to SELECT button
//probably need to return the position in array not the col position on LCD
}
///////////////////// react To ButtonState Find Label Or Change Menu
int reactToButtonStateFindLabelOrChangeMenu(){
switch (buttonState){
case unknown: break;
case left:return FindLabelLeft(menu_ptr); break;//returns (i+(row*17));giving 18 should be 16
case RIGHT: return FindLabelRight(menu_ptr); break; // (i+(row*17));
case select: useLabelToSetMenuState(); break; // what to do?
default: return 0;
}
}
///////////////////// print the chosen menu
void PrintMenu(char *menu ){
int offset=0;
for (uint8_t row = 0; row<2; row++){
for(uint8_t i=0;i<16;i++){
lcd.setCursor(i,row);//first 0-15 prints top row. then need skip one space in file to then print the second row
offset = row * (17)+i; //the menu has an extra space in between upper & lower row that LCD doesn't have
lcd.print(menu+offset) ;
//delay(100);
}
}
}
/*
enum stateOfMenu_t
{MenuUnknown,
showMain1,
show2Color, show2Flicker, show2Day,show2OnOff,
show3Flicker, show3OnOff,
showNoMenu};
*/
///////////////////// Display menu based on menu state
void DisplayMenuBasedOnMenuState(){
switch (menuState){
case showNoMenu://menu_ptr=nullptr;
break;//does this cause problems?
case MenuUnknown: //menu_ptr=nullptr;
break;//does this cause problems?
case showMain1: menu_ptr = menu1Main; break;
case show2Color:menu_ptr = menu2Color; break;
case show2Flicker:menu_ptr = menu2Flicker; break;
case show2Day: menu_ptr = menu2Day; break;
case show2OnOff: menu_ptr = menu2OnOff; break;
case show3Flicker:menu_ptr = menu3Flicker; break;
case show3OnOff: menu_ptr = menu3OnOff; break;
}
PrintMenu(menu_ptr);
};
///////////////////// Respond to user
void respondToUser(){ //if any button, set lights to 'idle' or stay out of system loop?
DisplayMenuBasedOnMenuState();
if (analogRead(A0) <400) buttonState=left;// need change to match shield<<<<<<<<<<<<<
else if(analogRead(A0)<800) buttonState=RIGHT;
else if(analogRead(A0)>900) buttonState=select;
//for(int i=0;i<7;i++){
digitalWrite(2 , LOW);
digitalWrite(3 , LOW);
digitalWrite(4 , LOW);
int labelPosition = reactToButtonStateFindLabelOrChangeMenu();
readMenuLabel( menu_ptr,labelPosition);//shouldn't call it here?
//depends on which menu is displayed?
delay(500);
delay(500);
}
///////////////////// setup
/*
void setup() {
pinMode (2, OUTPUT); //digitalWrite(spaceLED, HIGH); // indicators which probably won't be in finished product
pinMode (3, OUTPUT); //digitalWrite(textLED, HIGH);
pinMode (4, OUTPUT); //digitalWrite(labelLED, HIGH);
lcd.begin(16, 2);
Serial.begin(115200);
Serial.println("Where?");
//Assuming a button has been pressed and the reaction is to set...
menuState = showMain1;//TEST only - Normally only get to this stage when button pressed <<<<<<<<<<
}
///////////////////// Loop
void loop() {
//this loop should be separate from main LED loop or the other parts just idle?
respondToUser();// not sure which should be in this loop
//when 'select' the cursor goes all over the LCD Need to check why.
delay(100);//just here for test
}
*/