Close

Adding a Simple Menu System

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/26/2018 at 13:550 Comments

A Simple Menu System

As a demo I added a clock set menu. Here is a drawing of the menu and the value ranges:

So it is pretty simple, two levels: "Menu Item" and "Item Value". Just roll the encoder to move from one item to the next, push the button to toggle between levels. To exit the menu just toggle the done value from "N" to "Y" and push the button again. To get back into the menu, push the button (at least as long as any delays in the main loop).

The "Item Value" do have minimum and maximum vaues enforced but it does not know that February cannot have 31 days!  Enter 18 for the year 2018, etc.

Here are my menu arrays:

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

The menu item names include the necessary format spacing between the name and the value. And values can be numeric ('Y') or Y/N ('N').

Here is the code:

#define Clk 5
#define DT  4
#define SW  3
// 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_COMPA_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;
    }
  }
}

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

void setup() {

  // Keyes rotary encoder
  pinMode(Clk,INPUT_PULLUP); // Rotary Clk (has 10k pullup)
  pinMode(DT,INPUT_PULLUP);  // Rotary DT  (has 10k pullup)
  pinMode(SW,INPUT_PULLUP);  // Rotary SW  (has no pullup)
  pinMode(2,OUTPUT);         // Rotary + (supply power to encoder)
  digitalWrite(2,HIGH);      // Turn on power for rotary encoder

  // Turn on polling ISR
  OCR0A=0x80;
  TIMSK0|=1<<OCIE0A;

  Serial.begin(9600);
  while (!Serial);

  Serial.println("Rotary encoder and push button example");
  Serial.println("ITEM   VALUE");
  Serial.print(menuName[EncoderPos]);
  if (menuNumeric[EncoderPos]=='Y') {
    Serial.println(menuValue[EncoderPos],DEC);
  } else {       
    if (menuValue[EncoderPos]!=0) {
      Serial.println("Y");
    } else {
      Serial.println("N");      
    }
  }
}

bool processMenu(void) {
  static char lastPos=0;
    
  // 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;
      Serial.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]=='Y') {
        Serial.println(menuValue[EncoderPos],DEC);
      } else {       
        if (menuValue[EncoderPos]!=0) {
          Serial.println("Y");
        } else {
          Serial.println("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;
      Serial.print(menuName[lastPos]);
      if (menuNumeric[lastPos]=='Y') {
        Serial.println(menuValue[lastPos],DEC);
      } else {       
        if (menuValue[lastPos]!=0) {
          Serial.println("Y");
        } else {
          Serial.println("N");      
        }
      }
    }
  }

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

void loop() {
  int i;

  if (!processMenu()) { // Menu not active
    // Present the results
    for (i=0;i<menuSize;i++) {
      if (menuNumeric[i]=='Y') {
        Serial.print(menuValue[i],DEC);
      } else {       
        if (menuValue[i]!=0) {
          Serial.print("Y");
        } else {
          Serial.print("N");      
        }
      }
      Serial.print(" "); 
    }  
    Serial.println(); 
    delay(3000);
  }

}
Magic

Discussions