Pages: [1]   Go Down
Author Topic: Menu system using Rotary encoder  (Read 3016 times)
0 Members and 1 Guest are viewing this topic.
London-UK
Offline Offline
Full Member
***
Karma: 1
Posts: 106
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I have a working menu using push buttons but now want to use a Rotary encoder instead.

what do I have to do to use the encoder instead of the push buttons to select menu options.

here is the reduced MENU code sketch due to message limitation.

Code:
#include <MenuBackend.h>    //MenuBackend library - copyright by Alexander Brevig
#include <LiquidCrystal.h>  //this library is included in the Arduino IDE

const int buttonPinLeft = 44;      // pin for the Up button
const int buttonPinRight = 46;    // pin for the Down button
const int buttonPinEnter = 45;   // pin for the Enter button
const int buttonPinEsc = 42;     // pin for the Esc button

int lastButtonPushed = 0;

int lastButtonEnterState = LOW;   // the previous reading from the Enter input pin
int lastButtonEscState = LOW;   // the previous reading from the Esc input pin
int lastButtonLeftState = LOW;   // the previous reading from the Left input pin
int lastButtonRightState = LOW;   // the previous reading from the Right input pin


long lastEnterDebounceTime = 0;  // the last time the output pin was toggled
long lastEscDebounceTime = 0;  // the last time the output pin was toggled
long lastLeftDebounceTime = 0;  // the last time the output pin was toggled
long lastRightDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 100;    // the debounce time


LiquidCrystal lcd(7,8,9,10,11,12);


void setup()
{
  pinMode(buttonPinLeft, INPUT);
  pinMode(buttonPinRight, INPUT);
  pinMode(buttonPinEnter, INPUT);
  pinMode(buttonPinEsc, INPUT);


  lcd.begin(16, 2);
}

void loop()
{

  readButtons();  //I splitted button reading and navigation in two procedures because
  navigateMenus();  //in some situations I want to use the button for other purpose (eg. to change some settings)

} //loop()...


void menuChanged(MenuChangeEvent changed){

  MenuItem newMenuItem=changed.to; //get the destination menu

  lcd.setCursor(0,0); //set the start position for lcd printing to the second row

  if(newMenuItem.getName()==menu.getRoot())
  { //this code will change to have time and date displayed
    lcd.print("Main Menu       ");
  }
  //--------------------------------------------------------------menu lcd-----------------------------------------
  else if(newMenuItem.getName()=="menuLcd"){
    lcd.clear();
    lcd.print("Lcd");
  }
  else if(newMenuItem.getName()=="menuItemOn"){
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("On");
  }
  else if(newMenuItem.getName()=="menuItemOff"){
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("Off");

  }
  //--------------------------------------------------------------------menu set clock-----------------------------------

  else if(newMenuItem.getName()=="menuSetClock"){
    lcd.clear();
    lcd.print("Set Clock");
  }
  else if(newMenuItem.getName()=="menuItemHr"){
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("Hr");
  }
  else if(newMenuItem.getName()=="menuItemMin"){
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("Min");
  }
  else if(newMenuItem.getName()=="menuItemSec"){
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("Sec");
  }
  else if(newMenuItem.getName()=="menuItemConfirm"){
    lcd.clear();
    lcd.print("Confirmed");
  }
  //--------------------------------------------------menu set date-------------------------------------------------------------------------

  else if(newMenuItem.getName()=="menuSetDate"){
    lcd.clear();
    lcd.print("Set Date");
  }
  else if(newMenuItem.getName()=="menuItemDay"){
    lcd.clear();
    lcd.print("Set Day");
  }
  else if(newMenuItem.getName()=="menuItemMonth"){
    lcd.clear();
    lcd.print("Set Month");
  }
  else if(newMenuItem.getName()=="menuItemYear"){
    lcd.clear();
    lcd.print("Set Year");
  }
  //------------------------------------------------------------menu set phone number--------------------------------------------------------

  else if(newMenuItem.getName()=="menuSetPhone"){
    lcd.clear();
    lcd.print("Set Phone");
  }
  //---------------------------------------------------------------menu set pin number---------------------------------------------------------

  else if(newMenuItem.getName()=="menuSetPin"){
    lcd.clear();
    lcd.print("Set Pin");
  }
  //---------------------------------------------------------menu set sms---------------------------------------------------------------------

  else if(newMenuItem.getName()=="menuSms"){
    lcd.clear();
    lcd.print("Sms ");
  }
  else if(newMenuItem.getName()=="menuItemSigStr"){
    lcd.clear();
    lcd.print("Signal Stre");
  }
  else if(newMenuItem.getName()=="menuItemBalance"){
    lcd.clear();
    lcd.print("Balance");
  }
  else if(newMenuItem.getName()=="menuItemTest"){
    lcd.clear();
    lcd.print("Test Sms");
  }
  //---------------------------------------------------------------------menu exit to time----------------------------------------------------------------

  else if(newMenuItem.getName()=="menuExit"){
    lcd.clear();
    lcd.print("Exit");
  }

}

void menuUsed(MenuUseEvent used){ //**************if you have a function call it here***********************
  if( used.item == menuItemOn ){
    lcd.display();
    //digitalWrite(10, HIGH);
    //delay(3000);
    //digitalWrite(10, LOW);
    lcd.clear();
    menu.toRoot();
  }
  if ( used.item == menuItemOff ){
    lcd.noDisplay();//your code here
    menu.toRoot();
  }

  //etc.
}

void navigateMenus() {
  MenuItem currentMenu=menu.getCurrent();

  switch (lastButtonPushed){
  case buttonPinEnter: // enter pin
    if(!(currentMenu.moveDown())){  //if the current menu has a child and enter has been pressed  then navigate menu to item below
      menu.use();
    }
    else{  //otherwise, if menu has no child and enter has been pressed---- the current menu is used
      menu.moveDown();
    }
    break;
  case buttonPinEsc:
    menu.toRoot();  //back to main
    break;
  case buttonPinRight:
    menu.moveRight();
    break;     
  case buttonPinLeft:
    menu.moveLeft();
    break;     
  }

  lastButtonPushed=0; //reset the lastButtonPushed variable
}

I have a separate encoder sketch that works in serial monitor that shows each indent of the encoder 1,2,3,4 "you get the idea".

Here is the encoder sketch.

Code:
#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"

byte portA;
byte portB;
byte bit_maskA;
byte bit_maskB;
volatile byte *registerA;
volatile byte *registerB;

volatile static int numberA;

volatile int* attached_val;


void setup()
{
  Serial.begin(9600);
  pinMode(EncoderPinA, INPUT);
  pinMode(EncoderPinB, INPUT);
  digitalWrite(EncoderPinA, HIGH);
  digitalWrite(EncoderPinB, HIGH);

  portA=digitalPinToPort(EncoderPinA);
  portB=digitalPinToPort(EncoderPinB);

  bit_maskA = digitalPinToBitMask(EncoderPinA);
  bit_maskB = digitalPinToBitMask(EncoderPinB);
  registerA = portInputRegister(portA);
  registerB = portInputRegister(portB);

  attached_val = &numberA;
  attachInterrupt(0, doEncoderA, FALLING); // for some reason the new mouser encoders only work on A falling and b rising The other ones don't read fast enough

  //and always count the same way
}

void loop()
{

  Serial.print("numberA = ");
  Serial.println(numberA);

  delay(500);

}


void doEncoderA()
{

  ((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ :

  (*attached_val)--;
}


hope I have included enough code for you to help.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48543
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You need to make changes to navigateMenu() to replace the case values with the rotary encoder value.
Logged

London-UK
Offline Offline
Full Member
***
Karma: 1
Posts: 106
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I have changed the rotary encoder code and now have a somewhat working menu using your advice.
heres the code.

Code:
void navigateMenus()
{
  MenuItem currentMenu=menu.getCurrent();

  char temp = my_encoder1.getKey(); // Use phi_keypads object to access the keypad
  if (temp!=NO_KEY)// Serial.println(temp);

    switch (temp)
    {
    case 'buttonPinEnter': // enter pin
      if(!(currentMenu.moveDown())){  //if the current menu has a child and enter has been pressed  then navigate menu to item below
        menu.use();

      }
      else{  //otherwise, if menu has no child and enter has been pressed---- the current menu is used
        menu.moveDown();
      }
      break;
    case 'U':
      menu.moveRight();
      break;      
    case 'D':
      menu.moveLeft();
      break;
      //case buttonPinEsc:
      //     menu.toRoot();  //back to main
      //     break;    
    }

  lastButtonPushed=0; //reset the lastButtonPushed variable


}


I can move left and right through the menus but I cant get the rotary encoder "buttonPinEnter" to work. This is set up as "pinMode(buttonPinEnter, INPUT);" in "Setup" Any help on this might be enough for me to complete my menu .
« Last Edit: January 08, 2013, 05:08:59 am by mark7w » Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48543
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
    case 'buttonPinEnter': // enter pin
Which ONE key do you press to type buttonPinEnter? Why is buttonPinEnter in quotes? Why is the pin number a case?
Logged

London-UK
Offline Offline
Full Member
***
Karma: 1
Posts: 106
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This is what happens when you cut and paste other peoples code when you dont know what your doing.

Quote
Which ONE key do you press to type buttonPinEnter?

I dont intend to "type" buttonPinEnter if thats what my code is trying to do.

Quote
Why is buttonPinEnter in quotes?

 because 'U' and 'D' is. I thought that when the encoder button was pressed it will enter the next menu. Guess this is not so.

Quote
Why is the pin number a case?

pin number? do you mean why is "buttonPinEnter" a case.

Do i need to remove

Code:
case 'buttonPinEnter': // enter pin
      if(!(currentMenu.moveDown())){  //if the current menu has a child and enter has been pressed  then navigate menu to item below
        menu.use();

      }
      else{  //otherwise, if menu has no child and enter has been pressed---- the current menu is used
        menu.moveDown();
      }
      break;

is the solution a small one?

Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48543
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I dont intend to "type" buttonPinEnter if thats what my code is trying to do.
In order to trigger that case, the 'buttonPinEnter' character needs to be entered. That requires that my_encoder1.getKey() return that ONE character. Won't ever happen.

An encoder is used to enter numeric values. What does getKey() have to do with encoders? What does it do?

Quote
I thought that when the encoder button was pressed it will enter the next menu.
That depends on what the encoder class does. Perhaps it's time for you to share that.

Quote
pin number? do you mean why is "buttonPinEnter" a case.
Code:
const int buttonPinEnter = 45;   // pin for the Enter button
  pinMode(buttonPinEnter, INPUT);
Sure looks like a pin number to me.

Quote
Do i need to remove
Maybe.

Quote
is the solution a small one?
No. Using an encoder in place of a key pad is not a trivial change.
Logged

London-UK
Offline Offline
Full Member
***
Karma: 1
Posts: 106
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
That depends on what the encoder class does. Perhaps it's time for you to share that.

This is what I have its from the phi_interfaces.h


Code:
*/
/** \brief a class for rotary encoders
 * \details This class senses a rotary encoder and reports when the rotary knob is turned one detent up or down.
 * You may use this similarly to a keypad. A call to getKey will yield say 'U' or 'D' for dial up or down. You can also call get_angle to get the orientation of the dial.
 * To use a rotary encoder with a clickable shaft, define a button with phi_buttons class or phi_button_arrays class, with the latter as preferred method.
 * By default both channels are off when the knob is in a groove. I strongly suggest you purchase an encoder that does that instead of both channels on when the knob is in a groove.
 * This class supports important functions such as getKey(), which you need to call periodically inside a loop to update the status of the encoder and sense a dial up or down when they happen. Then if the return is up or down, you can trigger actions.
 * This library is not interrupt driven and thus has no call-back functions.
*/
class phi_rotary_encoders: public multiple_button_input{
  public:
  phi_rotary_encoders(char *na, byte ChnA, byte ChnB, byte det); ///< Constructor for rotary encoder
  byte getKey();            ///< Returns the key corresponding to dial up or down or NO_KEY.
  byte get_status();        ///< Always returns buttons_up since the encoder works differently than other keypads.
  byte get_sensed();        ///< Always returns NO_KEY since the encoder works differently than other keypads.
  byte get_angle();         ///< Get the angle or orientation of the rotary encoder between 0 and detent-1.

  protected:
  byte EncoderChnA;         ///< Arduino pin connected to channel A of the encoder
  byte EncoderChnB;         ///< Arduino pin connected to channel B of the encoder
  byte detent;              ///< Number of detents per rotation of the encoder
  byte stat_seq_ptr;        ///< Current status of the encoder in gray code
  byte counter;             ///< Counts for get_angle() to calculate knob orientation
  char * key_names;         ///< Pointer to array of characters two elements long. Each click up or down is translated into a name from this array such as 'U'.
};

I can see that there is no rotary encoder push button in this class and I saw this in the phi_interfaces.h

Quote
To use a rotary encoder with a clickable shaft, define a button with phi_buttons class or phi_button_arrays class, with the latter as preferred method

Do i create a class in the same file "phi_interfaces.h"

How do I do that. I'm so close to getting this done
« Last Edit: January 09, 2013, 08:27:52 am by mark7w » Logged

Pages: [1]   Go Up
Jump to: