LCD menu w/ buttons

I've been trying to make a lcd menu where you can scroll through different alternatives. The menu lets you pick different functiones like a blink/fade function, and later on i will add a speaker where you can play some songs etc. I've got some parts of the code up and running, but whenever you press the button to go from the starting screen to the first and the second option the screen gets dim/faded.. Have anyone here made something like this, and if so could you possibly help me out?

Im not sure if i should post the code here, so let me know if i should!

Sorry for any mistakes in the language.

You should post your code ;) Please use code tags.

type ``` [code] ```

paste your code after that type ``` [/code] ``` after the pasted code

Anything else connected to your Arduino than just the LCD? Please provide a schematic (a real one or a photo/scan of a hand drawn one).

Are you using a keypad or just some buttons to scroll and select (might be clear from your code).

Here is the code, i’ve connected the button to pin7 and one to pin 6, but the one on pin 6 isnt ready.
I want to make the first part of the code work first.
The second button is for picking the function that is displayed on the lcd.

#include <LiquidCrystal.h>



const int buttonPin = 7; 
//const int buttonPin2 = 6;
int led = 8;
int mode = 0; // Stores the current mode 0=Nothing, 1=Blink, 2=Fade
LiquidCrystal lcd(12,11,5,4,3,2);

void setup(){
  pinMode(buttonPin, INPUT);
  //pinMode(buttonPin2, INPUT);
  pinMode(led, OUTPUT);
  lcd.setCursor(0,1);
  lcd.begin(16,2);
  lcd.print("Pick a function");
}

void loop(){


  if (digitalRead(buttonPin) == HIGH){
    mode++;
    if(mode > 2) mode = 0;
  }

 if (mode == 1){
  lcd.clear();
  lcd.print("Blink");
  //Blink();
 }
 lcd.clear();
 if(mode == 2){
  lcd.clear();
  lcd.print("Fade");
  //Fade();
 }
}

void Blink(){
  digitalWrite(led, HIGH);
  delay(200);
  digitalWrite(led, LOW);
  delay(200);
}
void Fade(){
  int brightness = 0;
  int fadeAmount = 5;
  analogWrite(led, brightness);
  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount;
  }

}

You need to "debounce" your button.

Please also provide your schematics how you connected the button.

Here is the link to circuits.io(not sure if it will work)

Here is a screenshot from Gyazo

Ok, your wiring is ok (you won't need the resistor for the button if you use INPUT_PULLUP and connect the button to GND, but that's no problem).

Your problem becomes clear if you insert another lcd.print("BEGIN"); at the first line in the loop.

Keep in mind that the loop runs really fast.

After you fix that, you need to "debounce" your buttons. Just google that.

The LCD screen I have on hand has the backlight/screen brightness wired separately. If your screen is just dimming, maybe you have something wired up wrong. But I could be misunderstanding what your problem is.

You also need to use a state change detection on the button(s). As said above, your loop runs very fast and as a result it will read your button hundreds of times in a second; so your mode will increment as well at that rate. You can either increment 'mode' when the button is pressed and wait till it's released again before detecting a new press or increment 'mode' when the button is released again.

The IDE come with the StateChangeDetection example; it contains a delay-based debounce that you can use for starters. Have a look at it.

Thanks guys! I will read up on debounce and see if i can get it up and running!

I’ve added the debounce function(i think) and the lcd is stable now. The only problem i have is that the button needs to be pressed down for the first option to be displayed, and when you release it the second option is displayed.
I want it to show the first option and change when you press the button again… I know where the problem is, i just dont know how to fix it.

The problem is located in this part of the code.

void loop(){
  buttonState = digitalRead(buttonPin);

  if (buttonState != lastButtonState) {
    if(buttonState == HIGH){
      lcd.clear();
      lcd.print("Blink");
    }else{
      lcd.clear();
      lcd.print("Fade");
    }
    delay(50);
  }
  lastButtonState = buttonState;
}

Here is the full code:

#include <LiquidCrystal.h>



const int buttonPin = 7; 
//const int buttonPin2 = 6;
int led = 8;
int buttonState = 0;
int lastButtonState = 0;
LiquidCrystal lcd(12,11,5,4,3,2);

void setup(){
  pinMode(buttonPin, INPUT);
  //pinMode(buttonPin2, INPUT);
  pinMode(led, OUTPUT);
  lcd.setCursor(0,1);
  lcd.begin(16,2);
  lcd.print("Pick a function");
}

void loop(){
  buttonState = digitalRead(buttonPin);

  if (buttonState != lastButtonState) {
    if(buttonState == HIGH){
      lcd.clear();
      lcd.print("Blink");
    }else{
      lcd.clear();
      lcd.print("Fade");
    }
    delay(50);
  }
  lastButtonState = buttonState;
}


 

void Blink(){
  digitalWrite(led, HIGH);
  delay(200);
  digitalWrite(led, LOW);
  delay(200);
}
void Fade(){
  int brightness = 0;
  int fadeAmount = 5;
  analogWrite(led, brightness);
  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount;
  }

}

You're right. The problem is located in this section.

Actually you didn't debounce the button. Have a look at the "02 Digital > Debounce" example. Debouncing includes time measuring.

Also, have a look at the very first code sample you posted. The code was totally correct, except that the debouncing routine was missing :)

Oh, ok :o I'll have a look at it :) Thank you so much!

thomai: Also, have a look at the very first code sample you posted. The code was totally correct, except that the debouncing routine was missing :)

Sure that one does not need state change detection?

Ive tried to understand how the debouncing function works, and i think i understand it now. The only problem i have now is how to implement it to my code.. Could anyone try to guide me through it?

Try to implement it. If it doesn't work, post your code and tell us what the problem is.

You don't need this part

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;

and this

  // set the LED:
  digitalWrite(ledPin, ledState);

After removing that, you can copy the 'content' of the debounce loop() to the beginning of your existing loop. So it looks like

void loop()
{
  // debounce code here
  ...
  ...

  // your code here
  // your only interested in buttonState from the debounce code;
  //    that will indicate if the button is HIGH or LOW
  ...
  ...
}

The problem comes when you want to debounce both buttons. You will have to copy all the debounce code making your loop become very big. There are better approaches when you have to debounce multiple buttons.

Tacey: I've added the debounce function(i think) and the lcd is stable now. The only problem i have is that the button needs to be pressed down for the first option to be displayed, and when you release it the second option is displayed. I want it to show the first option and change when you press the button again... I know where the problem is, i just dont know how to fix it.

The problem is located in this part of the code.

void loop(){
  buttonState = digitalRead(buttonPin);

 if (buttonState != lastButtonState) {    if(buttonState == HIGH){      lcd.clear();      lcd.print("Blink");    }else{      lcd.clear();      lcd.print("Fade");    }    delay(50);  }  lastButtonState = buttonState; }

Do not do it like this, for several reasons:

  • [u]delay is evil[/u]: delay is a blocking function. That means that when you call it, the processor sits and does nothing, it just twiddles its thumbs until the delay is over. You probably want to be doing other things during that time, but you can't, you've devoted 1/20 of an entire second just to check one button. 1/20 of a second might not seem like much, but it is an eternity in uC land. You're pissing away 800,000 clock cycles that could be used to do something useful. There is no reason to do this. Discipline yourself to never use any delay longer than maybe 5 milliseconds.
  • [u]delay wastes power[/u]: Subnote to above, if you ever get to projects running standalone on battery power, you cannot use delay. You cannot sleep the processor during a delay, so it's running full speed all the time and wasting precious energy. This is not a concern if you're running off a mains wall wart or USB connection.
  • [u]Diffuse logic is a nightmare[/u]: Your debouncing logic is sprinkled in multiple places in your code, with the operation lodged in the middle of it. This is not good and makes it a real pain to figure out which things are happening in which order. Maybe you'll add one more button and forget to add the lastButtonState = buttonState expression at the end, leading to weird behavior.

In order to keep your sanity, keep all the variables and logic compact into a single structure. This allows all the variables to be packaged together under one name and also member functions that act on those variables. This is how the Serial object if used. You don't see the input and output buffers or have to worry about the exact process of reading and writing to them so as to not clash with the Serial interrupt that's sending data. You just call Serial.println(whatever) and it does it's thing. Do the same with your buttons:

// Single Pole Single Throw button class
struct SPST
{
  typedef int value; // value for the button press.
  typedef unsigned long time_type; // same type as millis() return value.

  SPST(int pin_no, time_type debounce_interval);

  void check(); // reads the button and debounces the level.

  value level() const; // returns the debounced level (HIGH or LOW) of the button.
  bool edge() const; // returns true if the debounced level has changed since the last call to check();

private:
  value _debounced_level;
  value _previous_debounced_level;

  value _last_raw_reading;
  time_type _last_time_it_changed;
};

That's a skeleton of all the methods and variables you need to create a simple class for a SPST button. Your homework then is to implement the check() function without using delay. Extra credit for extending it to detect the difference between a short and long press.

Hint: Use the scheduling technique shown in the Digital > BlinkWithoutDelay example. There is a reason that that sketch is probably the most commonly cited example on this board: it is the most crucial technique to understand in order to create complex multi-tasking sketches. The technique is deceptively simple but too many people struggle to be able to integrate it into their thinking. It is also in Digital > Debounce.

Allright! Now the flickering of the screen is gone. The problem i have now is that when you press the button it goes to “fade” before it goes to “blink”, and sometimes it skips the blink and goes from fade to fade…

#include <LiquidCrystal.h>



const int buttonPin = 7; 
//const int buttonPin2 = 6;
const int led = 8;
int mode = 0; // Stores the current mode 0=Nothing, 1=Blink, 2=Fade
int ledState = LOW;
int buttonState;
int lastButtonState = LOW;


long lastDebounceTime = 0;
long debounceDelay = 1;
LiquidCrystal lcd(12,11,5,4,3,2);

void setup(){
  pinMode(buttonPin, INPUT);
  //pinMode(buttonPin2, INPUT);
  pinMode(led, OUTPUT);
  lcd.setCursor(0,1);
  lcd.begin(16,2);
  lcd.print("Pick a function");
  digitalWrite(led, ledState);
}

void loop(){
  int reading = digitalRead(buttonPin);


  if (reading != lastButtonState){
    lastDebounceTime = millis();
    if (buttonState = HIGH);{
       mode++;
    if(mode > 2) mode = 0;
    }
   
  }
  if ((millis() - lastDebounceTime) > debounceDelay){
    if (reading != buttonState){
      buttonState = reading;
      
    }
  }

  else if (mode == 1){
  lcd.clear();
  lcd.print("Blink");
  //Blink(); 
  } 
  
  else if (mode == 2){
    lcd.clear();
    lcd.print("Fade");
    //Fade();
 }

 lastButtonState = reading;
}

void Blink(){
  digitalWrite(led, HIGH);
  delay(200);
  digitalWrite(led, LOW);
  delay(200);
}
void Fade(){
  int brightness = 0;
  int fadeAmount = 5;
  analogWrite(led, brightness);
  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount;
  }

}

This might not be right at all, but it kinda works…

I didn't check your code, but I saw your debounce delay is 1ms. For the buttons I have, that's way too short.

Try a value of 10 or even 20 and tell if it's still unpredictable.

First of all is that debounce should only do the debouncing and nothing else. So you should not set modify mode in there.

Next there are two bugs in the line last line below.

  if (reading != lastButtonState) {
    lastDebounceTime = millis();
    if (buttonState = HIGH); {

buttonState = HIGH sets button state high; it's not use as a compare (use == instead). Second is the semicolon after the is. As a result mode++ will always be executed

Next you have two else if statements. Because of the first else this can be considered part of the debounce (and as said, debounce should only debounce).

A modified version

void loop()
{
  // debounce
  int reading = digitalRead(buttonPin);

  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
    }
  }

  lastButtonState = reading;


  // other code

  if(buttonState == HIGH)
  {
    mode++;
    if(mode > 2)
    {
      mode = 0;
    }
  }

  // do something with mode here
  ...
  ...
}

Now there is one problem with this; as long as you have the button pressed, the buttonState will be HIGH and mode will changing at a very high rate. As I mentioned before, you need a state change detection and only modify mode if there is a transition from low to high.

To implement this, you will need another variable reflecting the previous debounce button state. This is going to be messy with all the prvious, last etc variables and mistakes are easily made. Therefore in the next post we will implement a function for the debouncing that contains all variables needed for the debouncing without polluting the rest of the code.