Close

Updated Rotary Encoder Menu

A project log for Polling a Push Button Rotary Encoder

A push button rotary encoder is a neat input device for an Arduino. Unfortunately interfacing to it is not!

agp.cooperagp.cooper 01/30/2018 at 08:241 Comment

Updated Rotary Encoder Menu

I updated the Rotary Encoder menu for the LCD display:

// Include LCD library and initialise
#include <LiquidCrystal.h>
// Define Adruino pin numbers for LCD
#define en   7
#define rs   8
#define d4   9
#define d5  10
#define d6  11
#define d7  12
#define led 13
// Set LCD
LiquidCrystal lcd(rs,en,d4,d5,d6,d7);

/* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */
#define Clk 2
#define DT  3
#define SW  4
// Poll and debounces a Keyes rotary and push button switch
// Inputs:
//   Clk (Pin A with 10k pullup resistor)
//   DT  (Pin B with 10k pullup resistor)
//   SW  (Switch without a pullup resistor)
//   +   (Power for pullup resistors)
//   Gnd
// Exports:
//   UpdateSwitch (bool)   [true|false]
//   Switch (byte)         [LOW|HIGH]
//   EncoderPos (char)     [-128..127]
volatile bool UpdateSwitch=false;
volatile byte Switch=HIGH;
volatile char EncoderPos=0;
ISR(TIMER0_COMPB_vect) {
  static byte testClk=(PIND>>Clk)&1;
  static byte testDT=(PIND>>DT)&1;
  static byte lastClk=LOW;
  static byte lastDT=LOW;
  static bool flagClk=false;
  static bool flagDT=false;
  static char encoderDir=0;
  static byte testSW=HIGH;
  static byte statusSW=HIGH;
  static byte cntSW=0;  
  
  // Update Encoder Position
  lastClk=testClk;            // Save Clk
  lastDT=testDT;              // Save DT
  testClk=(PIND>>Clk)&1;      // Get Clk
  testDT=(PIND>>DT)&1;        // Get DT
  if (testClk!=lastClk) {     // Change in Clk?
    flagClk=true;             // Flag Clk has changed
    encoderDir=-1;            // Assume it is the last flag to change
  }
  if (testDT!=lastDT) {       // Change in DT?
    flagDT=true;              // Flag DT has changed
    encoderDir=1;             // Assume it is the last flag to change
  }
  if (flagClk&&flagDT) {      // Both flags have changed
    EncoderPos+=encoderDir;   // Update the encoder
    flagClk=false;            // Reset Clk flag
    flagDT=false;             // Reset DT flag
  }
  
  // Update switch with 10ms debounce
  testSW=(PIND>>SW)&1;
  if (testSW!=statusSW) {
    statusSW=testSW;
    cntSW=10;
  }
  if (cntSW>0) {
    cntSW--;
    if (cntSW==0) {
      Switch=statusSW;
      UpdateSwitch=true;
    }
  }
}


/* MENU SET UP */
char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second ","Adjustment "};
int menuSize=sizeof(menuName)/sizeof(char*);
char menuValue[]=   {  0,  0,  1,  1,  0,  0,  0,   0};
char menuNumeric[]= {'N','Y','Y','Y','Y','Y','Y', 'Y'};
char menuValueMin[]={  0,  0,  1,  1,  0,  0,  0,-126};
char menuValueMax[]={  1,126, 12, 31, 23, 59, 59, 126};
char menuLevel=0;

void setup() {
  // Initialise rotary encoder and push button
  pinMode(Clk,INPUT_PULLUP); // Rotary Clk
  pinMode(DT,INPUT_PULLUP);  // Rotary DT
  pinMode(SW,INPUT_PULLUP);  // Rotary SW

  // Turn on polling ISR
  OCR0B=0xA0;
  TIMSK0|=1<<OCIE0B;

  // Initialise LCD
  pinMode(led,OUTPUT);
  digitalWrite(led,HIGH);
  lcd.begin(16,2);
  lcd.print("Rotary Encoder");      
  delay(3000);   
}

bool processMenu(void) {
  static char lastPos=0;
  static char lastMenuLevel=-1;

  // Pre-empt menu level display 
  if (menuLevel!=lastMenuLevel) {
    lastMenuLevel=menuLevel;
    lcd.clear();
    lcd.setCursor(0,0);
    if (menuLevel==0) {
      lcd.print("Menu:");
      lcd.setCursor(0,1);
      lcd.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]=='Y') {
        lcd.print((int)menuValue[EncoderPos]);
      } else {       
        if (menuValue[EncoderPos]!=0) {
          lcd.print("Y");
        } else {
          lcd.print("N");      
        }
      }          
    } else if (menuLevel==1) {
      lcd.print("Set:");
      lcd.setCursor(0,1);
      lcd.print(menuName[lastPos]);
      if (menuNumeric[lastPos]=='Y') {
        lcd.print((int)menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]!=0) {
          lcd.print("Y");
        } else {
          lcd.print("N");      
        }
      }      
    } else {
      lcd.print("Rotary Encoder?");      
    }
  }
  
  // If push button pushed toggle menuLevel
  if (UpdateSwitch) {
    UpdateSwitch=false;
    if (Switch==LOW) {
      // Re-enter menu if button pushed (for long enough)
      if (menuLevel==-1) {
        menuLevel=0;    // Item menu
        lastPos=1;      // Trigger menu update 
        EncoderPos=0;   // Done
        menuValue[0]=0; // N
      } else {
        // Toggle menu level
        menuLevel=1-menuLevel;
        if (menuLevel==0) {
          // Save item menu position
          EncoderPos=lastPos;
          // Exit menu if done!
          if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1;
        } else {
          // Set value for edit menu
          EncoderPos=menuValue[lastPos];
        }
      }
    }
  }
  
  // If encoder turned
  if (menuLevel==0) { // Select menu item
    if (lastPos!=EncoderPos) {
      if (EncoderPos>=menuSize) EncoderPos=0;
      if (EncoderPos<0) EncoderPos=menuSize-1;
      lastPos=EncoderPos;
      
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Menu:");
      lcd.setCursor(0,1);
      lcd.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]=='Y') {
        lcd.print((int)menuValue[EncoderPos]);
      } else {       
        if (menuValue[EncoderPos]!=0) {
          lcd.print("Y");
        } else {
          lcd.print("N");      
        }
      }

    }
  } else if (menuLevel==1) { // Edit menu item value
    if (menuValue[lastPos]!=EncoderPos) {
      if (EncoderPos>menuValueMax[lastPos]) EncoderPos=menuValueMin[lastPos];
      if (EncoderPos<menuValueMin[lastPos]) EncoderPos=menuValueMax[lastPos];
      menuValue[lastPos]=EncoderPos;
      
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Set:");
      lcd.setCursor(0,1);
      lcd.print(menuName[lastPos]);
      if (menuNumeric[lastPos]=='Y') {
        lcd.print((int)menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]!=0) {
          lcd.print("Y");
        } else {
          lcd.print("N");      
        }
      }
      
    }
  }

  return (menuLevel!=-1); // Return true if menu active
}

void loop() {

  if (!processMenu()) { // Menu not active
    // Present the results
    int i;

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Running");
    lcd.setCursor(0,1);
    for (i=0;i<menuSize;i++) {
      if (menuNumeric[i]=='Y') {
        lcd.print((int)menuValue[i]);
      } else {       
        if (menuValue[i]!=0) {
          lcd.print("Y");
        } else {
          lcd.print("N");      
        }
      }
      lcd.print(" "); 
    }  

    delay(3000);
  }

}

The main change was to pre-empt the menu level display. Previously it was only updated after the rotary button was turned or the push button pushed. Not very intuitive!

Pretty happy with the menu system now. Time to merge in the UT Clock code.

Magic

Discussions

Michal wrote 02/03/2019 at 00:06 point

Thanks! Great idea how to build user interface on a small display.

I try used this your code and it is working fine, except one thing - every other menu item is missing.

Doesn't matter how fast the encoder is turning. If I turn it long enough I eventually can access the missing menu items, but some times it takes over a minute.

I have thought it might be a bouncing but increasing cntSW didn't help.

Any ideas?

Michal

  Are you sure? yes | no