20×4 LCD menu with rotary encoder

In this example, I’m using a rotary encoder with a 20×4 LCD display in order to control a menu. This is a simple example that also uses the rotary library from Ben Buxton and modified by Phill Fisk, who included the push button feature. In this example, cursor moves up and down until the top or bottom of the page is encountered.

A rotary encoder is a position sensor that is used to determine angular position of the rotating shaft. Usually, a rotary encoder has 5 pins: 5V, GND, push button, DT and CLK that are used for Bit A and Bit B. The encoder uses Gray Code sequence where two successive values differ in only one bit (binary digit). The reflected binary code was originally designed to prevent spurious output from electromechanical switches.

The basic way of decoding these is to watch for which bits change. For example, a change from “00” to “10” indicates one direction, whereas a change from “00” to “01” indicates the other direction. The library uses a Finite State Machine (FSM) algorithm that follows a Gray Code sequence. The biggest advantage of using a state machine over other algorithms is that this has inherent debounce built in. Other algorithms emit spurious output with switch bounce, but this one will simply flip between sub-states until the bounce settles, then continue along the state machine.

The setup is very simple.  First connect an 20×4 LCD to an Arduino as in the following picture:

20×4 LCD

Then connect a rotary encoder to A0 (pin1), A1(pin2) and A2 (push button) of the Arduino and upload the following sketch:

#include <LiquidCrystal.h>
#include <rotary.h>                 // rotary handler

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

#define PINA A0
#define PINB A1
#define PUSHB A2

// Initialize the Rotary object
// Rotary(Encoder Pin 1, Encoder Pin 2, Button Pin) Attach center to ground
Rotary r = Rotary(PINA, PINB, PUSHB);        // there is no must for using interrupt pins !!

int CursorLine = 1;
int DisplayFirstLine = 1;

char* MenuLine[] = {" Option 1", " Option 2", " Option 3", " Option 4", " Option 5", " Option 6", " Option 7"};

int MenuItems = 7;

void setup ()
{
  digitalWrite (PINA, HIGH);     // enable pull-ups
  digitalWrite (PINB, HIGH);
  digitalWrite (PUSHB, HIGH);

  lcd.begin (20, 4); 
  lcd.clear (); // go home
  lcd.print ("Menu Master");
  lcd.setCursor(0, 1);
  lcd.print("Please Select");
  delay(2000);
  print_menu();

}  // end of setup

void loop ()
{
  volatile unsigned char result = r.process();

  if (result == DIR_CCW) {
    move_up();
    print_menu();
  } else if (result == DIR_CW) {
    move_down();
    print_menu();
  }

  if (r.buttonPressedReleased(25)) {
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print("You selected:");
    lcd.setCursor(0, 2);
    selection();
    print_menu();
  } //endif buttonPressedReleased
} //End loop()

void print_menu()
{
  int n = 4;                      //4 rows
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("   Main Menu  ");
  if (MenuItems == 1) {           //if only 1 item
    n = 2;
  } else if (MenuItems == 2) {    //if only 2 item
    n = 3;
  }
  for (int i = 1; i < n; i++)     // row 0 is used for title Main Menu
  {
    lcd.setCursor(1, i);
    lcd.print(MenuLine[DisplayFirstLine + i - 2]);
  }
  lcd.setCursor(0, (CursorLine - DisplayFirstLine) + 1);
  lcd.print("<");
} //end print_menu

void move_down()
{
  if (CursorLine == (DisplayFirstLine + 3 - 1)) {
    DisplayFirstLine++;
  }
  //If reached last item...roll over to first item
  if (CursorLine == MenuItems) {
    CursorLine = 1;
    DisplayFirstLine = 1;
  } else {
    CursorLine = CursorLine + 1;
  }
} //end move_down

void move_up()
{
  if ((DisplayFirstLine == 1) & (CursorLine == 1)) {
    if (MenuItems < 3) {
      //Do nothing
    } else {
      DisplayFirstLine = MenuItems - 2;
    }
  } else if (DisplayFirstLine == CursorLine) {
    DisplayFirstLine--;
  }

  if (CursorLine == 1) {
    if (MenuItems < 3) {
      //Do nothing
    } else {
      CursorLine = MenuItems; //roll over to last item
    }
  } else {
    CursorLine = CursorLine - 1;
  }
} //end move_up

void selection()
{
  switch (CursorLine - 1) {
    case 0:
      lcd.print("Option 1    ");
      //set a flag or do something....
      break;
    case 1:
      lcd.print("Option 2    ");
      //set a flag or do something....
      break;
    case 2:
      lcd.print("Option 3    ");
      //set a flag or do something....
      break;
    case 3:
      lcd.print("Option 4    ");
      //set a flag or do something....
      break;
    case 4:
      lcd.print("Option 5    ");
      //set a flag or do something....
      break;
    case 5:
      lcd.print("Option 6    ");
      //set a flag or do something....
      break;
    case 6:
      lcd.print("Option 7    ");
      //set a flag or do something....
      break;
    default:
      break;
  } //end switch

  delay(2000);
  DisplayFirstLine = 1;
  CursorLine = 1;
} //End selection

This code let’s you move the cursor up and down. It uses polling but it can easily be modified to use with interrupts. I used ‘half-step’ mode which emits an event at both the 0-0 and 1-1 positions. This is useful since I wish to change the menu position at every click. In order to activate ‘half-step’ mode, go to “rotary.h” file which is located in your library within your sketchbook folder and uncomment line

//#define HALF_STEP

 

You are done!

 

8 comments

  1. Hallo Freund,
    sehr gute arbeit,ich versuche Ihr Program auf
    “#include
    #include
    LiquidCrystal_I2C lcd(0x27, 20, 4);”
    anzuschliessen, schaffe es aber nicht.

    Like

    1. You will need to change the line ” lcd.begin (20, 4);” to ” lcd.begin ();” also.

      Like

  2. Dear friend,

    I do not speak German but using Google translator it looks that I2C is not working for you. Two things that you should try:

    1) You need to include the I2C library: Wire.h and LiquidCrystal_I2C.h
    2) If it still doesn’t work, it might be that 0x27 is not the right code (usually it is the deafault). There are programs to detect the I2C code…try looking at this site…https://www.makerguides.com/character-i2c-lcd-arduino-tutorial/

    Hope it helps!!

    Like

  3. Hi,

    I’m having issues with the polling mode, can you help on how I can switch to interrupt mode?

    Thanks!!

    Like

    1. Hi Arturo,
      Look into original Buxtronix examples:
      https://github.com/buxtronix/arduino/tree/master/libraries/Rotary/examples/interrupt
      Here you will find an interrupt example. Hope it helps!

      Like

  4. bjarne veien · · Reply

    hi
    how to change a own sub variable based on the selections in the submenu?

    Like

    1. Hi Bjarne,
      In the function “void selection()” you will find a switch selection.
      Here where it says //set a flag or do something….
      replace it with a variable.

      I have a youtube video with a more complete example with a link to a Github where you can find the code.

      I hope it helps.

      Like

  5. Hi Carlos,

    I use an I2C-LCD.

    I made some changes (#include wire.h, #include LiquidCrystal_I2C.h) and ….

    it works perfect!

    Thank you!

    Frank

    Like

Leave a comment