How to use buttons on 16x2 LCD

I purchased the 16x2 LCD with the 5 buttons - up/down/left/right/select

Ive managed to get the text to appear using

#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);

etc and various print.lcd stuff

now i want to be able to use buttons to do various things

but i dont really know how

Ive tried to search the learning resources but i havent really found anything

Thanks in advance

I have had a play with LCD in one of my projects but am still at a basic level. I used a IRremote as the input but it is simply the form of the input that changes. It is still "on input, do something".

First analyse what you actually want to do with each button. I used > to change cursor position of the number I wished to change then up/down to vary the value. Note that when you change the value, the cursor moves to the next position each time you write the value so you need to write then move back to the same cursor position again for the next press.

Also if you are say, setting the time on a RTC, you also need to look at under and over flow for minutes and hours.

Weedpharma

i kind want to make some sort of graphical interface where buttons do various things.

For example, one button could clear the screen, another can cause some text to show up, etc etc

This is giving all possibilities with help of hardware MCP23017 you are able working I2C two wiring

Hi again guys, more code troubles: I have got most of the functions working the way i want them ,i can take measurements of light. However I want to be able to navigate through my LCD with buttons it has. Up/down/left/right/Select

I want it such that the program starts up, displays a message. Press select then gives a choice

left for continuous measurement and right for a hold feature.
If i hit left, i get the continuous measurement but whilst trying to implement a back button it means i need to a while loop and its just failing and i cant navigate through that screen. If i hit right on the first screen, i can take single measurements with a press of the button, i can keep on pressing right to make more measurements. Hitting select takes me back to the first screen.

Depending on where i am in the whole interface, sometimes buttons i dont want to do anything at the moment -do stuff anyway - i think simply because of their position within the switch case

advice in how to control my flow and maybe how to call certain cases within another case would help. Thanks in advance

#include <LiquidCrystal.h>

/******************************************
 * 
 * 
 * 
 ******************************************/

LiquidCrystal lcd(8,9,4,5,6,7);

/*****************************************
 * Definitions
 *****************************************/

int lcd_key = 0;
int adc_key_in = 0;
#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5



/* ---------Button Information--------------
 
 Right: 0.00V ; 0 @ 10 bit
 
 UP: 0.71V ; 145 @ 10 bit
 
 DOWN: 1.61V ; 329 @ 10 bit
 
 LEFT: 2.47V ; 505 @ 10 bit
 
 SELECT: 3.62V ; 741 @ 10 bit
 
 -------------------------------------------*/


/*******************************************
 * Setup
 *******************************************/
void setup()
{
  lcd.begin(16,2); // start
  lcd.setCursor(0,0);
  lcd.print("Hi");
  lcd.setCursor(0,1);
  lcd.print("There");
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("MENU: Press");  
  lcd.setCursor(0,1);
  lcd.print("SELECT to start");
}

/*****************************************************
 * Main loop
 ******************************************************/

void loop()
{
  lcd_key = read_LCD_buttons(); //read the buttons
  button_switch_case(lcd_key);
}


/**********************************************
 * Reading the buttons
 ***********************************************/

int read_LCD_buttons()
{
  adc_key_in = analogRead(A0); // read value

  if(adc_key_in > 1000) return btnNONE;

  if(adc_key_in < 50) return btnRIGHT;

  if(adc_key_in < 250) return btnUP;

  if(adc_key_in < 450) return btnDOWN;

  if(adc_key_in < 650) return btnLEFT;

  if(adc_key_in < 850) return btnSELECT;

  return btnNONE; // If all else fails
} 


/*********************************************************************************
 * Switch case for buttons
 **********************************************************************************/

void button_switch_case(int lcd_key)
{
  switch (lcd_key)    //Depending on button pressed, perform action
  {

    /*******************************************************************************
     * Right Button
     *******************************************************************************/
  case btnRIGHT:    
    {
      lcd.clear();
      float photoDiode_1 = analogRead(A1);
      float photoDiode_2 = analogRead(A1);
      float photoDiode_3 = analogRead(A1);
      float averagetoprint = ((photoDiode_1)+(photoDiode_2)+(photoDiode_3))/3;

      float voltage_1 = photoDiode_1 * ((5.0 / 1023.0)*1000);
      float voltage_2 = photoDiode_2 * ((5.0 / 1023.0)*1000);
      float voltage_3 = photoDiode_3 * ((5.0 / 1023.0)*1000);
      float averageVoltage = ((voltage_1)+(voltage_2)+(voltage_3))/3;

      float lux = 2.67857 * averageVoltage;

      lcd.print("SEL:BACK >:AGAIN");
      lcd.setCursor(0,1);
      lcd.print(lux);
      lcd.print( " Lux");
      delay(1000);
      //if(adc_key_in<=650 && adc_key_in>=450)lcd_key=btnLEFT;
      
      break;
     
    }
    /*******************************************************************************
     * Left Button
     *******************************************************************************/
  case btnLEFT:
    {
      while(!lcd_key < btnSELECT)
      {
        lcd.clear();
        float photoDiode_1 = analogRead(A1);
        float photoDiode_2 = analogRead(A1);
        float photoDiode_3 = analogRead(A1);
        float averagetoprint = ((photoDiode_1)+(photoDiode_2)+(photoDiode_3))/3;

        float voltage_1 = photoDiode_1 * ((5.0 / 1023.0)*1000);
        float voltage_2 = photoDiode_2 * ((5.0 / 1023.0)*1000);
        float voltage_3 = photoDiode_3 * ((5.0 / 1023.0)*1000);
        float averageVoltage = ((voltage_1)+(voltage_2)+(voltage_3))/3;

        float lux = 2.67857 * averageVoltage;

        lcd.print("SEL: BACK");
        lcd.setCursor(0,1);
        lcd.print(lux);
        lcd.print( " Lux");
        delay(1000);
      }

    }
    /*******************************************************************************
     * UP Button
     *******************************************************************************/
  case btnUP:
    {
      btnSELECT;
      break;
    }

    /*******************************************************************************
     * Down Button
     *******************************************************************************/
  case btnDOWN:
    {
      lcd.clear();
      lcd.print("Calibration");
      break;
    }

    /*******************************************************************************
     * Select Button
     *******************************************************************************/
  case btnSELECT:
    {
      lcd.clear();
      lcd.print("<: Continuous");
      lcd.setCursor(0,1);
      lcd.print(">: Hold,^ RETURN");
      break;
    }
    /*******************************************************************************
     * No Button Pressed
     *******************************************************************************/
  case btnNONE:
    {
      break;
    }
  }
}

some of the code i currently have in the while loop etc are wrong, i was just experimenting

Thanks in advance

First of all build a simple sketch with Serial and read the inputs taken on A0, pressing each button and writting down the values(don’t forget the ‘not pressing any button value’).

Then just check for that Analog input on your loop method. In my case this are my values reading the buttons on my LCD screen:

select   722-721
left      481
up       131-132
down   308-309
right    0
none    1023

After this, you can notice that there might be some variability on those values(they are quite stable), but we can put an offset of +50.

int lcd_button = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

int read_lcd_keypad() {
 lcd_button = analogRead(0); 
 if (lcd_button > 1000) return btnNONE; 
 if (lcd_button < 50)   return btnRIGHT;  
 if (lcd_button < 180)  return btnUP; 
 if (lcd_button < 360)  return btnDOWN; 
 if (lcd_button < 530)  return btnLEFT; 
 if (lcd_button < 770)  return btnSELECT;  
}

Well, so we have a function that reads our value on A0 and then it returns an int, wich we have constants defined for every button case. Now you need a switch case for every button:

switch(read_lcd_keypad()){
  case btnRIGHT:

break;
  case btnLEFT:

break;
  case btnDOWN:
  
break;
case btnUP:
  
break;
case btnSELECT:
  
break;
case btnNONE:

break;
}

Also, for your code, try to put this in your while loop:

lcd_key = read_LCD_buttons();

Now it will update the value while it is looping so if you press the select button it will stop.

@myownway,

thanks, my buttons do actually work already and yea i finally realised that i needed the

lcd_key = read_LCD_buttons()

i ended up using a system in my while look which looks for a flag essentially, while(flag!=0)

then when it was 0 as a result of the select button, it took me back. That solves that issue to some degree

( i need to have a delay to actually see my measurement, but that means when i hit the button, it wont respond depending on when i press it because of the delay, so i just hold it instead)

My flow of navigation is so weird though, in some spots i can press random buttons and get other locations in the interface

How can I say within a case that i want to go to another case if something happens?

for example

id go:

lcd_key=read_LCD_buttons();
if(adc_key_in<850 && adc_key_in>650)
{
//now something in here to make me go to another case (not sure how to do that or if its possible)
}

MrDropsy:
( i need to have a delay to actually see my measurement, but that means when i hit the button, it wont respond depending on when i press it because of the delay, so i just hold it instead)

Well, you could use the millis() function and an unsigned long variable plus an interval variable to set the loop method check the interval and then refresh your data on the lcd, this would be a simple example:

#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);

unsigned long last_check;
unsigned long interval = 1500; // Refresh time in milliseconds
unsigned long data = 1; // A data variable, it's just an example

void setup(){
  // Initialize lcd screen
  lcd.begin(16,2); 
  lcd.clear();
  // Initial data display
  lcd.print("Data value:");
  lcd.setCursor(0,1);
  lcd.print(data);
  // Setting the start value of time for reference
  last_check = millis(); 
}
void loop(){
  // Check if the time has reached the interval
  if((last_check + interval) <= millis() ){
    // Storing current time elapsed since startup for the next interval
    last_check = millis();

    // Refreshing your screen
    lcd.setCursor(0,1);
    lcd.print(data);
  }
  // If the time hasn't reached the interval, the code between the if brackets won't run, 
  //but this will, even if the interval isn't reached
  //
  // As an example I'll change the value of data ramdomly every loop
  data += random(10);
  
  lcd.setCursor(0,0);
  lcd.print("Data value:");
  lcd.print(data);
  
  // This value will change on every loop,
  //but the lcd will just be refreshed every 1500 milliseconds(as we set on the interval definition)
  
  // Some delay to prevent data from overflow so quickly
  delay(100);
}

MrDropsy:
My flow of navigation is so weird though, in some spots i can press random buttons and get other locations in the interface

How can I say within a case that i want to go to another case if something happens?

for example

id go:

lcd_key=read_LCD_buttons();
if(adc_key_in<850 && adc_key_in>650)
{
//now something in here to make me go to another case (not sure how to do that or if its possible)
}

Right now I’m working on a Menu class to work with lcd screens. Get here and look at my code a bit and see if you can get an idea

Thanks ill try to use the millis later on.

I have figured out how i can control the first screen.

I need to figure out how to effectively do nothing when something i dont want to pressed, is pressed.

I have the none case but, since it just breaks, it just goes around and does the next thing in line at the top of the case.

MrDropsy:
Thanks ill try to use the millis later on.

I have figured out how i can control the first screen.

I need to figure out how to effectively do nothing when something i dont want to pressed, is pressed.

I have the none case but, since it just breaks, it just goes around and does the next thing in line at the top of the case.

That's quite easy, just use a variable (or maybe more than one) to store the current place you are on the menu, example:

  • MAIN MENU --> ITEM 1 | ITEM 2
  • ITEM 1 SUBMENU --> ITEM 1 | ITEM 2 | ITEM 3

and so on...

I'd have a variable that tells me on which menu I am and then make a conditional depending on the value of that variable for the button actions, for this example we could have a structure like this:

  • btnLeft (we'll use it as our 'back' button)

  • If our variable tells us we are on main menu 'do nothing'

  • If our variable tells us we are on a submenu, return to main menu

  • btnRight(our 'go into submenu')

  • If we are on main, load submenu

  • If we are on submenu, do whatever that item is suposed to do

  • btnUp(Navigation)

  • previous item

  • btnDown(Navigation)

  • next item

Note that this is just a structure example, I hope it helps you.

Thanks for your help, will try to follow the structure.

My next question is about EEPROM, i essentially have a value which can be calibrated and i want it to be stored even when i power down.

My understanding is that i need to write it as a 8 bit value between 0 and 255. So i need to somehow make a conversion from a decimal value.

and then read the value.

Im trying to read about it, but im not really sure i understand how to use it and where i need to use it.

I would probably want it to be saved after i exit my calibration menu.

My understanding is that i need to write it as a 8 bit value between 0 and 255. So i need to somehow make a conversion from a decimal value.

Or as a collection of 8 bit values. Decimal is horrid type dreamed up by the dip shits in Redmond. It is NOT a C or C++ type.

MrDropsy:
My next question is about EEPROM, i essentially have a value which can be calibrated and i want it to be stored even when i power down.

My understanding is that i need to write it as a 8 bit value between 0 and 255. So i need to somehow make a conversion from a decimal value.

and then read the value.

Im trying to read about it, but im not really sure i understand how to use it and where i need to use it.

I would probably want it to be saved after i exit my calibration menu.

so… you just have to take your datatype (int, long?) and break it into bytes which can be stored in EEPROM. Retrieve/Assemble them back together accordingly:

Example of an unsigned long stored to EEPROM:

void saveDateToEEPROM(unsigned long theDate)
{
  Serial.println(F("Saving Last Run date"));
  if (EEPROM.read(0) != 0xFF) 
  {
    EEPROM.write(0,0xFF); // EEPROM flag for last date saved stored in EEPROM (location zero)
  }
  //
  for (int i = 1; i < 5; i++)
  {
    EEPROM.write(5-i, byte(theDate >> 8 * (i-1)));// store epoch datestamp in 4 bytes of EEPROM starting in location one
  }
}

and retrieve from EEPROM that unsigned long, same sketch:

//check for saved date in EEPROM
  Serial.println(F("Checking EEPROM for stored date:"));
  if (EEPROM.read(0) == 0xFF); // EEPROM flag
  {
    Serial.println(F("Retreiving last run time from EEPROM..."));
    for (int i = 0; i < 4 ; i++)
    {
      lastTimeRun = lastTimeRun << 8;
      lastTimeRun = lastTimeRun | EEPROM.read(i+1); // assemble 4 bytes into an ussigned long epoch timestamp
    }
  }