LCD interface with buttons not working

Hello, I am trying to make a LCD interface for one of my projects. I am using three pushbuttons, two of them to move a cursor up and down and another will be used to select items. My interface has 8 different pages and all of them are inteconnected as it can be seen in the code in the select() function. However, I am having some troble operating the different pages, moving the cursor is working fine, but the menus 5, 6, and 7 are not even appearing (by that I mean when I select them nothing happens) and in some menus like menu 8 when I press the backwards button ("Voltar") it comesback to menu number 4, instead of 1, and from menu 4 coming back to menu 1 doesn't work, again when I press the backwards cursor nothing happens. If anyone understand the issue and is able to explain I would be very thankful!!!

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

int upButton = 4;
int downButton = 3;
int selectButton = 2;
int menu = 1;
int linha = 1;

void setup() {
  Serial.begin(9600);
  lcd.begin(20, 4);
  lcd.backlight();
  pinMode(upButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);
  pinMode(selectButton, INPUT_PULLUP);
  Umenu();
}

void loop() {
  if (!digitalRead(downButton)){
    down();
    Umenu();
    delay(100);
    while (!digitalRead(downButton));
  }
  if (!digitalRead(upButton)){
    up();
    Umenu();
    delay(100);
    while(!digitalRead(upButton));
  }
  if (!digitalRead(selectButton)){
    select();
    Umenu();
    delay(100);
    while (!digitalRead(selectButton));
  }
  Serial.print(linha);
  Serial.println(menu);
}

void Umenu(){
lcd.clear();
switch (menu){ 
  case 0:
    menu = 1;
    break;
    
  case 1:
    lcd.setCursor(0, (linha-1));
    lcd.print(">");
    lcd.setCursor(1, 0);
    lcd.print("Status");
    lcd.setCursor(1, 1);
    lcd.print("Valores");
    lcd.setCursor(1, 2);     
    lcd.print("Configuracoes");
    lcd.setCursor(1, 3);     
    lcd.print("Modo Manual");
    break;
     
  case 2:
    lcd.setCursor(0, 0);
    lcd.print("Sensores:");
    lcd.setCursor(0, 1);
    lcd.print("Reles:");    
    lcd.setCursor(0, 2);     
    lcd.print("Modo sistema:");
    lcd.setCursor(0, 3);     
    lcd.print(">Voltar");
    break;
    
  case 3:
    lcd.setCursor(0, 0);
    lcd.print("Valor 1:");
    lcd.setCursor(0, 1);
    lcd.print("Valor 2:");
    lcd.setCursor(0, 2);     
    lcd.print(">Voltar");
    break;
    
  case 4:
    lcd.setCursor(0, (linha-1));
    lcd.print(">");
    lcd.setCursor(1, 0);
    lcd.print("Valor Noite:");
    lcd.setCursor(1, 1);
    lcd.print("Corelacao sensores:");
    lcd.setCursor(1, 2);     
    lcd.print("Erro:");
    lcd.setCursor(1, 3);     
    lcd.print("Voltar");
   break;
   
  case 5:
    lcd.setCursor(0, 0);
    lcd.print("Valor Noite:");
    lcd.setCursor(0, 2);
    lcd.print("←    +      -");
    break;
    
  case 6:
    lcd.setCursor(0, 0);
    lcd.print("Correlacao sensores:");
    lcd.setCursor(0, 2);
    lcd.print("←    +      -");
    break;
    
  case 7:
    lcd.setCursor(0, 0);
    lcd.print("Erro:");
    lcd.setCursor(0, 2);
    lcd.print("←    +      -");
    break;
    
  case 8:
    lcd.setCursor(0, (linha-1));
    lcd.print(">");
    lcd.setCursor(1, 0);
    lcd.print("Frente");
    lcd.setCursor(1, 1);
    lcd.print("Tras");
    lcd.setCursor(1, 2);
    lcd.print("Liga/Desliga");
    lcd.setCursor(1, 3);
    lcd.print("Voltar");
    break;

  case 9:
    menu = 8;
    break;
      
  }
}

void select(){
  switch(menu){
    
    case 1:
      switch (linha) {
        case 1:
          menu = 2;
          break;

        case 2:
         menu = 3; 
         break;

        case 3:
          menu = 4;
          break;

        case 4:
          menu = 8;
          break;
     }
    break;
    
    case 2:
      menu = 1;
      break;

    case 3:  
      menu = 1;
      break;  

    case 4:
      switch(linha){
          case 1:
            menu = 5;
            break;

          case 2:
            menu = 6;
            break;

          case 3:
            menu = 7;
            break;

          case 4:
            menu = 1;
            break;
        }

    case 8:
      switch(linha){
        case 1:
          //adicionar funcao pra mexer para tras
          break;

        case 2:
          //adicionar funcao frente
          break;

        case 3:
          //adicionar funcao liga e desliga
          break;

        case 4:
          menu = 1;
          break;
      }

    case 5:
      menu = 4;
      break;

    case 6:
      menu = 4;
      break;

    case 7:
      menu = 4;
      break;
  }
}

void up(){
  switch(menu){

    case 1:
      linha--;
      break;

    case 2:
      break;

    case 3:
      break;

    case 4:
      linha--;
      break;

    case 5:
      //aumentar valor noite
      break;

    case 6:
      //aumentar correlacao de sensor
      break;

    case 7:
      //aumentar erro
      break;

    case 8:
      linha--;
      break;
  }

  if (linha <= 0){
    linha=1;
  }
  else{
    
  }
}


void down(){
  switch(menu){
  
      case 1:
        linha++;
        break;
  
      case 2:
        break;
  
      case 3:
        break;
  
      case 4:
        linha++;
        break;
  
      case 5:
        //aumentar valor noite
        break;
  
      case 6:
        //aumentar correlacao de sensor
        break;
  
      case 7:
        //aumentar erro
        break;
  
      case 8:
        linha++;
        break;
  }
  if (linha >= 5){
    linha = 4;
  }
  else{
    
  }
}

i tested your code and see all 3 buttons functioning, but i don't see the menu changing and not sure what "linha" is?

why don't the up/down button just inc/decrement menu, wrapping as needed?

The idea was to make the menu change with the cursor, "linha" is the line of the cursor so line 1 corresponds to 0 coordinate y in the LCD cursor. So the idea is that the up and down button change the line and them you can use the select button to go to another page. I have done this other typoe of interface that you mentioned, however i was never able to make this one so I would like to try just for the sake of knowing how it works. thanks

are you saying you want to scroll thru up to 8 lines, 4 displayed at a time and of course only one of the 4 the current line that can be "selected"?

if so, seems up/down can still simply inc/decrement line/linha and depending on the line and last button pressed, which 4 of the 8 lines are displayed.

the 8 lines could be described in an array, and the line/button determine the index of the first line to display as well as the current line - instead of duplicating code

you could display lines 3-6 and lines 4 or 5 could be the current line that can be selected. of course lines 1 and 8 also need to be selectable. a little math can keep track of every thing

Hello rodrade
I have had a look at your sketch.
My recommendations are:
Put the menus in a common data structure with struct/arrays.
Do not use a magic number for the switch/case control flow. Use ENUM instead.
Design a button handler with debouncing using a data structure for port pin, timing, min/max values etc.
This makes the sketch easy to maintain and expand with more menus.
Have a nice day and enjoy coding in C++.
Дайте миру шанс!

I added serial debug-output to your code with two special "functions"
These "functions" are not even functions but called "macro"
At the moment it is not important how they work. At this point I want to explain how to use them.
For easier analysing what a code is doint it is comfortable to have information about three things:

  1. where in the code does the serial output occur
  2. name of a variable
  3. value of this variable

This can be done by coding three lines of code

Serial.print("my Z-function ");
Serial.print("myVariable="); // print the NAME of the variable
Serial.println(myVariable); / print the VALUE of the variable

the serial output will loook like this:

my Z-function myVariable=17

not very comfortable as you have to write three lines
.
.
.
the macro with the name dbg reduces the above lines to this single line of code

dbg("my Z-function",myVariable);

serial output

"my Z-function",myVariable=17

You did a very basic version of debugging by coding

  Serial.print(linha);
  Serial.println(menu);

this creates a neverending stream of output printing very fast
the macro dbgi is able to printout only once every 500 milliseconds or once every 1000 milliseconds etc.

Now to your code:

Very old programmer-wisdom: a program does always what you have coded. If the code does something different than you expected you don't yet understand what you have coded.

inside function down() you coded

  if (linha >= 5) {
    linha = 4;
    dbg("donw() if (linha >= 5)", linha);
  }

which limits linha to values between 1 and 4.

attention

I changed the baudrate to the modern value 115200

So here is the code-version which makes visible in the serial monitor what your code is doing by using the macro dbg

//++++++++++++++++++++++++++++++++++++++++++++++++++++
// just some debug-features 
// scroll down to
// ++your code starts here ++++++++++++++++++++++++++++++++++++++++

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *

https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++your code starts here ++++++++++++++++++++++++++++++++++++++++
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

int upButton = 4;
int downButton = 3;
int selectButton = 2;
int menu = 1;
int linha = 1;

void setup() {
  Serial.begin(115200);
  Serial.println( F("setup-Start") );
  lcd.init();                      // initialize the lcd
  lcd.begin(20, 4);
  lcd.backlight();
  pinMode(upButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);
  pinMode(selectButton, INPUT_PULLUP);
  Umenu();
  Serial.println( F("setup done") );
}

void loop() {
  // iputpull-up not pressed = HIGH = 1
  // inverted by ! => result 0 same as false
  // IO-pin-state LOW inverted by ! => result 1 same as ttrue
  // all in all !digitalRead(downButton) if button is pressed
  if (!digitalRead(downButton)) {
    down();
    Umenu();
    delay(100);
    while (!digitalRead(downButton)); // wait for button to be released
  }

  if (!digitalRead(upButton)) {
    up();
    Umenu();
    delay(100);
    while (!digitalRead(upButton));
  }

  if (!digitalRead(selectButton)) {
    select();
    Umenu();
    delay(100);
    while (!digitalRead(selectButton));
  }

  //dbgi("1:", linha, 1000);
  //dbgi("2:", menu, 1000);
}


void Umenu() {
  dbg("Umenu() entering", menu);
  lcd.clear();
  switch (menu) {
    case 0:
      menu = 1;
      break;

    case 1:
      lcd.setCursor(0, (linha - 1));
      lcd.print(">");
      lcd.setCursor(1, 0);
      lcd.print("Status");
      lcd.setCursor(1, 1);
      lcd.print("Valores");
      lcd.setCursor(1, 2);
      lcd.print("Configuracoes");
      lcd.setCursor(1, 3);
      lcd.print("Modo Manual");
      break;

    case 2:
      lcd.setCursor(0, 0);
      lcd.print("Sensores:");
      lcd.setCursor(0, 1);
      lcd.print("Reles:");
      lcd.setCursor(0, 2);
      lcd.print("Modo sistema:");
      lcd.setCursor(0, 3);
      lcd.print(">Voltar");
      break;

    case 3:
      lcd.setCursor(0, 0);
      lcd.print("Valor 1:");
      lcd.setCursor(0, 1);
      lcd.print("Valor 2:");
      lcd.setCursor(0, 2);
      lcd.print(">Voltar");
      break;

    case 4:
      lcd.setCursor(0, (linha - 1));
      lcd.print(">");
      lcd.setCursor(1, 0);
      lcd.print("Valor Noite:");
      lcd.setCursor(1, 1);
      lcd.print("Corelacao sensores:");
      lcd.setCursor(1, 2);
      lcd.print("Erro:");
      lcd.setCursor(1, 3);
      lcd.print("Voltar");
      break;

    case 5:
      lcd.setCursor(0, 0);
      lcd.print("Valor Noite:");
      lcd.setCursor(0, 2);
      lcd.print("←    +      -");
      break;

    case 6:
      lcd.setCursor(0, 0);
      lcd.print("Correlacao sensores:");
      lcd.setCursor(0, 2);
      lcd.print("←    +      -");
      break;

    case 7:
      lcd.setCursor(0, 0);
      lcd.print("Erro:");
      lcd.setCursor(0, 2);
      lcd.print("←    +      -");
      break;

    case 8:
      lcd.setCursor(0, (linha - 1));
      lcd.print(">");
      lcd.setCursor(1, 0);
      lcd.print("Frente");
      lcd.setCursor(1, 1);
      lcd.print("Tras");
      lcd.setCursor(1, 2);
      lcd.print("Liga/Desliga");
      lcd.setCursor(1, 3);
      lcd.print("Voltar");
      break;

    case 9:
      menu = 8;
      break;

  }
  dbg("Umenu() leaving", menu);  
}

void select() {
  dbg("select() entering", menu);
  dbg("select() entering", linha);
  
  switch (menu) {

    case 1:
      switch (linha) {
        case 1:
          menu = 2;
          break;

        case 2:
          menu = 3;
          break;

        case 3:
          menu = 4;
          break;

        case 4:
          menu = 8;
          break;
      }
      break;

    case 2:
      menu = 1;
      break;

    case 3:
      menu = 1;
      break;

    case 4:
      switch (linha) {
        case 1:
          menu = 5;
          break;

        case 2:
          menu = 6;
          break;

        case 3:
          menu = 7;
          break;

        case 4:
          menu = 1;
          break;
      }

    case 8:
      switch (linha) {
        case 1:
          //adicionar funcao pra mexer para tras
          break;

        case 2:
          //adicionar funcao frente
          break;

        case 3:
          //adicionar funcao liga e desliga
          break;

        case 4:
          menu = 1;
          break;
      }

    case 5:
      menu = 4;
      break;

    case 6:
      menu = 4;
      break;

    case 7:
      menu = 4;
      break;
  }
  dbg("select() leaving", menu);
  dbg("select() leaving", linha);  
}


void up() {
  dbg("up() entering", menu);
  dbg("up() entering", linha);

  switch (menu) {

    case 1:
      linha--;
      break;

    case 2:
      break;

    case 3:
      break;

    case 4:
      linha--;
      break;

    case 5:
      //aumentar valor noite
      break;

    case 6:
      //aumentar correlacao de sensor
      break;

    case 7:
      //aumentar erro
      break;

    case 8:
      linha--;
      break;
  }

  if (linha <= 0) {
    linha = 1;
    dbg("up() if (linha <= 0)", linha);
  }
  else {
    dbg("up() esle to if (linha <= 0)", linha);
  }

  dbg("up() leaving", menu);
  dbg("up() leaving", linha);
}


void down() {
  dbg("down() entering", menu);
  dbg("down() entering", linha);
  switch (menu) {

    case 1:
      linha++;
      break;

    case 2:
      break;

    case 3:
      break;

    case 4:
      linha++;
      break;

    case 5:
      //aumentar valor noite
      break;

    case 6:
      //aumentar correlacao de sensor
      break;

    case 7:
      //aumentar erro
      break;

    case 8:
      linha++;
      break;
  }

  dbg("donw() before if (linha >= 5)", linha);
  if (linha >= 5) {
    linha = 4;
    dbg("donw() if (linha >= 5)", linha);
  }
  else {
    //dbg("else to if (linha >= 5)", linha);
  }
  dbg("down() leaving", menu);
  dbg("down() leaving", linha);
}

best regards Stefan

p.s.
paulpaulson is posting short postings that suggest to use high-sophisticated coding-techniques that are way above the head of beginners.

This is useful if you are interested in diving deep into advanced coding-techniques.
Though paulpaulson does not add links or even suggest a senseful order what to learn first before advancing to the keywords he mentioned.

still puzzled

it seems common that 4 buttons are needed to drive a menu system. up and down to scroll thru menus or change values. select to select a menu and probably a 4th button to exit or return from a menu or accept the changes

there are commonly 2 types of menu items, a list of menu items and then final menu item where the up/down buttons change a value.

it's not trivial to to manage the different types of menu items, menu items that change a binary value, an integer value, scroll thru multi digit values or change text.

Hello
This could be achieved with a three-key operation. However, this would require an additional view to indicate which operating mode, i.e. either menu selection or value selection, is currently selected.
I think that two additional buttons [PLUS] and [MINUS] for changing the configuration values are better. All in all, there will be four buttons for menu and configuration selection.
Have a nice day and enjoy coding in C++.
Or Ask Steven.
Дайте миру шанс!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.