Go Down

Topic: Debounce on a switch with variable resistance (Read 1 time) previous topic - next topic

GeoDave

May 27, 2012, 06:32 am Last Edit: May 27, 2012, 09:55 am by GeoDave Reason: 1
I have been playing with the debounce example found in the menubackend example.  I am able to get this working with four buttons.  

The problem.  I have a multi-directional tactile switch that outputs different resistance based on the direction you push the switch:  http://www.mouser.com/ProductDetail/ALPS/SKQUAAA010/?qs=oKW7zmyQiO62qWuFl5QVBw%3d%3d

I got the menubackend example I found here working with the switch:  http://www.coagula.org/content/pages/tutorial-manage-menu-and-lcd-display-arduino

The benefit of this button over four is that it only takes up one analog pin!  The only problem is that I cannot figure out how to adapt the debounce part of the code to work with this type of switch.  The switch is pretty unusable right now because the smallest touch causes it to drill all the way down into the menu.  I have tried a ton of things.  Any ideas on how I could approach this problem?  I am a hobbyist when it comes to programming so maybe that is why I am having trouble making the example code work with the special switch. A pointer in the right direction would be awesome!  Note I use a funky serial LCD so that is why the print to serial code is a little weird.  Here is the jumpy code I have without any attempt at debounce:

Code: [Select]

/*
IMPORTANT: to use the menubackend library by Alexander Brevig download it at http://www.arduino.cc/playground/uploads/Profiles/MenuBackend_1-4.zip and add the next code at line 195
void toRoot() {
setCurrent( &getRoot() );
}
*/
#include <MenuBackend.h>    //MenuBackend library - copyright by Alexander Brevig

#define buttonPinLeft (1<<0)
#define buttonPinRight (1<<1)
#define buttonPinEsc   (1<<2)
#define buttonPinEnter  (1<<3)

int lastButtonPushed = 0;

// LiquidCrystal display with:
// rs on pin 7
// rw on ground
// enable on pin 6
// d4, d5, d6, d7 on pins 5, 4, 3, 2


//Menu variables
MenuBackend menu = MenuBackend(menuUsed,menuChanged);
//initialize menuitems
   MenuItem menu1Item1 = MenuItem("Item1");
     MenuItem menuItem1SubItem1 = MenuItem("Item1SubItem1");
     MenuItem menuItem1SubItem2 = MenuItem("Item1SubItem2");
   MenuItem menu1Item2 = MenuItem("Item2");
     MenuItem menuItem2SubItem1 = MenuItem("Item2SubItem1");
     MenuItem menuItem2SubItem2 = MenuItem("Item2SubItem2");
     MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3");
   MenuItem menu1Item3 = MenuItem("Item3");


void setup()
{

 Serial.begin(9600);

 
 //lcd.begin(16, 2);

 //configure menu
 menu.getRoot().add(menu1Item1);
 menu1Item1.addRight(menu1Item2).addRight(menu1Item3);
 menu1Item1.add(menuItem1SubItem1).addRight(menuItem1SubItem2);
 menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3);
 menu.toRoot();
 //lcd.setCursor(0,0);  
 Serial.print("$GO 1 1\r\n");
 //lcd.print("Hello World!");
 Serial.print("$PRINT Hello World!\r\n");
}  // setup()...


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,1); //set the start position for lcd printing to the second row
 
 if(newMenuItem.getName()==menu.getRoot()){
     //lcd.print("Main Menu       ");
     Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Main Menu       \r\n");
 }else if(newMenuItem.getName()=="Item1"){
   Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item1       \r\n");  
   //lcd.print("Item1           ");
 }else if(newMenuItem.getName()=="Item1SubItem1"){
   Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item1SubItem1       \r\n");  
   //lcd.print("Item1SubItem1");
 }else if(newMenuItem.getName()=="Item1SubItem2"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item1SubItem2       \r\n");
   //lcd.print("Item1SubItem2   ");
 }else if(newMenuItem.getName()=="Item2"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item2       \r\n");  
   //lcd.print("Item2           ");
 }else if(newMenuItem.getName()=="Item2SubItem1"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item2SubItem1       \r\n");  
   //lcd.print("Item2SubItem1   ");
 }else if(newMenuItem.getName()=="Item2SubItem2"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item2SubItem2       \r\n");  
   //lcd.print("Item2SubItem2   ");
 }else if(newMenuItem.getName()=="Item2SubItem3"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item2SubItem3       \r\n");  
   //lcd.print("Item2SubItem3   ");
 }else if(newMenuItem.getName()=="Item3"){
     Serial.print("$CLEAR 2 1\r\n");
     Serial.print("$GO 2 1\r\n");
     Serial.print("$PRINT Item3       \r\n");  
   //lcd.print("Item3           ");
 }
}

void menuUsed(MenuUseEvent used){
//do something here
}


void  readButtons(){  //read buttons status
unsigned char button = analogRead(0) >> 2;
 if (button == 0){
   lastButtonPushed=0;
 }

 //Serial.print("BtnRaw ");
 //Serial.println(button, DEC);

 if (button > 20 && button < 60){
   lastButtonPushed=buttonPinLeft;
 }
 else if (button > 60 && button < 100){
   lastButtonPushed=buttonPinEnter;
 }
 else if (button > 140 && button < 160){
   lastButtonPushed=buttonPinEsc;
 }
 else if (button > 160 && button < 200){
   lastButtonPushed=buttonPinRight;  
 }
 else {
   lastButtonPushed=0;
 }            
}

void navigateMenus() {
 MenuItem currentMenu=menu.getCurrent();
 
 switch (lastButtonPushed){
   case buttonPinEnter:
     if(!(currentMenu.moveDown())){  //if the current menu has a child and has been pressed enter then menu navigate to item below
       menu.use();
     }else{  //otherwise, if menu has no child and has been pressed enter 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
}




Grumpy_Mike

Don't just take the analogue reading but compute a running average.

GeoDave

Tried the running average.  That was fun to read about but it didn't work or I haven't identified the correct problem. The resistance values that come from the switch are very stable, however, when you toggle the switch it seems the commands (Enter, Esc, Left, Right) are coming so fast that that the code interprets one toggle as dozens of toggles.  Thus, it advances through the menu until it reaches the last item for that particular command.  At least I think that is what is going on.  Did I implement the running average correctly?  Any other ideas? Thanks! 

Here is the running average code I tried:
Code: [Select]

/*
IMPORTANT: to use the menubackend library by Alexander Brevig download it at http://www.arduino.cc/playground/uploads/Profiles/MenuBackend_1-4.zip and add the next code at line 195
void toRoot() {
setCurrent( &getRoot() );
}
*/
#include <MenuBackend.h>    //MenuBackend library - copyright by Alexander Brevig

#define buttonPinLeft (1<<0)
#define buttonPinRight (1<<1)
#define buttonPinEsc   (1<<2)
#define buttonPinEnter  (1<<3)

int lastButtonPushed = 0;

const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

int inputPin = 0;

// LiquidCrystal display with:
// rs on pin 7
// rw on ground
// enable on pin 6
// d4, d5, d6, d7 on pins 5, 4, 3, 2


//Menu variables
MenuBackend menu = MenuBackend(menuUsed,menuChanged);
//initialize menuitems
    MenuItem menu1Item1 = MenuItem("Item1");
      MenuItem menuItem1SubItem1 = MenuItem("Item1SubItem1");
      MenuItem menuItem1SubItem2 = MenuItem("Item1SubItem2");
    MenuItem menu1Item2 = MenuItem("Item2");
      MenuItem menuItem2SubItem1 = MenuItem("Item2SubItem1");
      MenuItem menuItem2SubItem2 = MenuItem("Item2SubItem2");
      MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3");
    MenuItem menu1Item3 = MenuItem("Item3");


void setup()
{

  Serial.begin(9600);

   for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0; 

  //lcd.begin(16, 2);

  //configure menu
  menu.getRoot().add(menu1Item1);
  menu1Item1.addRight(menu1Item2).addRight(menu1Item3);
  menu1Item1.add(menuItem1SubItem1).addRight(menuItem1SubItem2);
  menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3);
  menu.toRoot();
  //lcd.setCursor(0,0); 
  Serial.print("$GO 1 1\r\n");
  //lcd.print("Hello World!");
  Serial.print("$PRINT Hello World!        \r\n");
}  // setup()...


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)
 
  // subtract the last reading:
  total= total - readings[index];         
  // read from the sensor: 
  readings[index] = analogRead(inputPin);
  // add the reading to the total:
  total= total + readings[index];       
  // advance to the next position in the array: 
  index = index + 1;                   

  // if we're at the end of the array...
  if (index >= numReadings)             
    // ...wrap around to the beginning:
    index = 0;                           

  // calculate the average:
  average = total / numReadings;         
  // send it to the computer as ASCII digits
  Serial.println(average);   
  delay(1);        // delay in between reads for stability             
                 
} //loop()...


void menuChanged(MenuChangeEvent changed){
 
  MenuItem newMenuItem=changed.to; //get the destination menu
 
  //lcd.setCursor(0,1); //set the start position for lcd printing to the second row
 
  if(newMenuItem.getName()==menu.getRoot()){
      //lcd.print("Main Menu       ");
      Serial.print("$CLEAR 2 1\r\n");
       Serial.print("$GO 2 1\r\n");
       Serial.print("$PRINT Main Menu       \r\n");
  }else if(newMenuItem.getName()=="Item1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1       \r\n"); 
    //lcd.print("Item1           ");
  }else if(newMenuItem.getName()=="Item1SubItem1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem1       \r\n"); 
    //lcd.print("Item1SubItem1");
  }else if(newMenuItem.getName()=="Item1SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem2       \r\n");
    //lcd.print("Item1SubItem2   ");
  }else if(newMenuItem.getName()=="Item2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2       \r\n");   
    //lcd.print("Item2           ");
  }else if(newMenuItem.getName()=="Item2SubItem1"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem1       \r\n");   
    //lcd.print("Item2SubItem1   ");
  }else if(newMenuItem.getName()=="Item2SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem2       \r\n");   
    //lcd.print("Item2SubItem2   ");
  }else if(newMenuItem.getName()=="Item2SubItem3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem3       \r\n");   
    //lcd.print("Item2SubItem3   ");
  }else if(newMenuItem.getName()=="Item3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item3       \r\n");   
    //lcd.print("Item3           ");
  }
}

void menuUsed(MenuUseEvent used){
//do something here
}


void  readButtons(){  //read buttons status

unsigned char button = average >> 2;
  if (button == 0){
    lastButtonPushed=0;
  }

  //Serial.print("BtnRaw ");
  //Serial.println(button, DEC);

  if (button > 20 && button < 60){
    lastButtonPushed=buttonPinLeft;
  }
  else if (button > 60 && button < 100){
    lastButtonPushed=buttonPinEnter;
  }
  else if (button > 140 && button < 160){
    lastButtonPushed=buttonPinEsc;
  }
  else if (button > 160 && button < 200){
    lastButtonPushed=buttonPinRight; 
  }
  else {
    lastButtonPushed=0;
  }             
}

void navigateMenus() {
  MenuItem currentMenu=menu.getCurrent();
 
  switch (lastButtonPushed){
    case buttonPinEnter:
      if(!(currentMenu.moveDown())){  //if the current menu has a child and has been pressed enter then menu navigate to item below
        menu.use();
      }else{  //otherwise, if menu has no child and has been pressed enter 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
}

Grumpy_Mike

Quote
Did I implement the running average correctly?

No.
You implemented an average not a running average.
If your readings are changing too fast then increase the number of terms in the average.

How are you wiring this up? You do have a pull up resistor on the analogue input as well do you?

PeterH

The problem is not the debouncing as such, it is that you are not detecting CHANGES in the button state. If the button remains 'pressed' you will process that as a separate command each time round the loop.

What you need to do is apply your range checks to determine the current button state, which will presumably be one of a set of discrete values representing the different directions your button can be operated, plus a value (let's say zero) for the 'not pressed' state.

In your readButtons() function you need to remember what poosition the button was in previously and do your range checks to see what position it is in now. If they are different, the button has been pressed or released so set lastButtonPushed to indicate the new position. Otherwise (the position has not changed) set lastButtonPushed to zero, or whatever value you're using to represent the 'not pressed' position.
I only provide help via the forum - please do not contact me for private consultancy.

GoForSmoke

Assuming that each position has an output voltage not close to the others (I look at the circuit and that seems like a safe assumption) that you can know each one,

Maybe when you detect a change, you keep reading over and over until the new value stays within +/- perhaps .2V of one of the accepted values for 20 to 50 reads in a row which should take less than 10 ms. You could be pretty certain the switch has settled by then.
Nick Gammon on multitasking Arduinos:
1) http://gammon.com.au/blink
2) http://gammon.com.au/serial
3) http://gammon.com.au/interrupts

GeoDave

#6
May 27, 2012, 06:37 pm Last Edit: May 27, 2012, 06:56 pm by GeoDave Reason: 1

Quote
Did I implement the running average correctly?

No.
You implemented an average not a running average.
If your readings are changing too fast then increase the number of terms in the average.

How are you wiring this up? You do have a pull up resistor on the analogue input as well do you?


Pin 1 on the button schematic goes to 5v.  Pin 2 goes to analog 0 and a 1K resistor tied to gnd.   I am going to play some more and see what I can come up with based on the comments.  Thanks again!

GeoDave


In your readButtons() function you need to remember what poosition the button was in previously and do your range checks to see what position it is in now. If they are different, the button has been pressed or released so set lastButtonPushed to indicate the new position. Otherwise (the position has not changed) set lastButtonPushed to zero, or whatever value you're using to represent the 'not pressed' position.


I got it working much better.  Thoughts on improvements?  Here is what I did: 

Code: [Select]
/*
IMPORTANT: to use the menubackend library by Alexander Brevig download it at http://www.arduino.cc/playground/uploads/Profiles/MenuBackend_1-4.zip and add the next code at line 195
void toRoot() {
setCurrent( &getRoot() );
}
*/
#include <MenuBackend.h>    //MenuBackend library - copyright by Alexander Brevig

#define buttonPinLeft (1<<0)
#define buttonPinRight (1<<1)
#define buttonPinEsc   (1<<2)
#define buttonPinEnter  (1<<3)

int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0; 
int buttonPushed;
int lastButtonPushed;

int inputPin = 0;

// LiquidCrystal display with:
// rs on pin 7
// rw on ground
// enable on pin 6
// d4, d5, d6, d7 on pins 5, 4, 3, 2


//Menu variables
MenuBackend menu = MenuBackend(menuUsed,menuChanged);
//initialize menuitems
    MenuItem menu1Item1 = MenuItem("Item1");
      MenuItem menuItem1SubItem1 = MenuItem("Item1SubItem1");
      MenuItem menuItem1SubItem2 = MenuItem("Item1SubItem2");
    MenuItem menu1Item2 = MenuItem("Item2");
      MenuItem menuItem2SubItem1 = MenuItem("Item2SubItem1");
      MenuItem menuItem2SubItem2 = MenuItem("Item2SubItem2");
      MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3");
    MenuItem menu1Item3 = MenuItem("Item3");


void setup()
{

  Serial.begin(9600);


  //lcd.begin(16, 2);

  //configure menu
  menu.getRoot().add(menu1Item1);
  menu1Item1.addRight(menu1Item2).addRight(menu1Item3);
  menu1Item1.add(menuItem1SubItem1).addRight(menuItem1SubItem2);
  menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3);
  menu.toRoot();
  //lcd.setCursor(0,0); 
  Serial.print("$GO 1 1\r\n");
  //lcd.print("Hello World!");
  Serial.print("$PRINT Hello World!        \r\n");
}  // setup()...


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,1); //set the start position for lcd printing to the second row
 
  if(newMenuItem.getName()==menu.getRoot()){
      //lcd.print("Main Menu       ");
      Serial.print("$CLEAR 2 1\r\n");
       Serial.print("$GO 2 1\r\n");
       Serial.print("$PRINT Main Menu       \r\n");
  }else if(newMenuItem.getName()=="Item1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1       \r\n"); 
    //lcd.print("Item1           ");
  }else if(newMenuItem.getName()=="Item1SubItem1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem1       \r\n"); 
    //lcd.print("Item1SubItem1");
  }else if(newMenuItem.getName()=="Item1SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem2       \r\n");
    //lcd.print("Item1SubItem2   ");
  }else if(newMenuItem.getName()=="Item2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2       \r\n");   
    //lcd.print("Item2           ");
  }else if(newMenuItem.getName()=="Item2SubItem1"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem1       \r\n");   
    //lcd.print("Item2SubItem1   ");
  }else if(newMenuItem.getName()=="Item2SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem2       \r\n");   
    //lcd.print("Item2SubItem2   ");
  }else if(newMenuItem.getName()=="Item2SubItem3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem3       \r\n");   
    //lcd.print("Item2SubItem3   ");
  }else if(newMenuItem.getName()=="Item3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item3       \r\n");   
    //lcd.print("Item3           ");
  }
}

void menuUsed(MenuUseEvent used){
//do something here
}


void  readButtons(){  //read buttons status
unsigned char button = analogRead(inputPin) >> 2;
  if (button == 0){
    buttonPushed=0;
  }

  if (button > 20 && button < 60){
    buttonPushed=buttonPinLeft;
  }
  else if (button > 60 && button < 100){
    buttonPushed=buttonPinEnter;
  }
  else if (button > 140 && button < 160){
    buttonPushed=buttonPinEsc;
  }
  else if (button > 160 && button < 200){
    buttonPushed=buttonPinRight; 
  }
  else {
    buttonPushed=0;
  } 
// read the pushbutton input pin:
  buttonState = buttonPushed;

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, increment the counter
    if (buttonState != 0 ) {
      // if the current state is HIGH then the button
      // wend from off to on:
      buttonPushCounter++;
      lastButtonPushed = buttonPushed;     
    }
    else {
      // if the current state is LOW then the button
      // wend from on to off:
      lastButtonPushed = 0;
    }
  }
  // save the current state as the last state,
  //for next time through the loop
  lastButtonState = buttonState;
}

void navigateMenus() {
  MenuItem currentMenu=menu.getCurrent();
 
  switch (lastButtonPushed){
    case buttonPinEnter:
      if(!(currentMenu.moveDown())){  //if the current menu has a child and has been pressed enter then menu navigate to item below
        menu.use();
      }else{  //otherwise, if menu has no child and has been pressed enter 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
}



PaulS

Code: [Select]
unsigned char button = analogRead(inputPin) >> 2;
Unsigned char? Why? Why not byte?

PeterH

I'm a bit perplexed by the number of state variables you are using in readButtons.

Using buttonState and lastButtonState looks fine to hold the current detected state and previously detected state so you can detect changes. Using lastButtonPushed as a way to pass the change from readButtons() to navigateMenus() (and then clear it once it's been processed) is also OK. Personally I'd have preferred to have readButtons() return the key that was pressed (or 0 is nothing was pressed) rather than using a global variable, because this reduces the amount of global data and that is always a good thing, but that's mainly a matter of style. But then you also have buttonPushed and buttonPushCounter, and I have no idea what they're for. If you can eliminate them that would reduce the complexity by a useful amount.
I only provide help via the forum - please do not contact me for private consultancy.

GoForSmoke

I don't think that dividing the range of analog read values so widely is going to help debounce the multi-switch.

Code: [Select]
  if (button > 20 && button < 60){
    buttonPushed=buttonPinLeft;
  }
  else if (button > 60 && button < 100){
    buttonPushed=buttonPinEnter;
  }
  else if (button > 140 && button < 160){
    buttonPushed=buttonPinEsc;
  }
  else if (button > 160 && button < 200){
    buttonPushed=buttonPinRight; 
  }
  else {
    buttonPushed=0;
  } 


I think you want discrete intervals for a settled button press. The first two and last two are back to back. At the speed the Arduino does even analog reads you can see the voltage change before it is done changing, you can 'see the bounce'. When there's no room between two states, you will get a 'valid' state even as the state itself is changing.

If you press the Esc button and leave it for a while, do repeated analog reads fall in the range of 140 to 160 or something closer to 150?

Maybe you can find a way to widen the total range beyond 200?
Nick Gammon on multitasking Arduinos:
1) http://gammon.com.au/blink
2) http://gammon.com.au/serial
3) http://gammon.com.au/interrupts

GeoDave

Menu now works perfectly with the multi-directional button!  I hope this is useful to someone in the future.

Here is a better schematic:



Code: [Select]
unsigned char button = analogRead(inputPin) >> 2;
Unsigned char? Why? Why not byte?

Ehh...good point.  Thanks!  Still learning...


I'm a bit perplexed by the number of state variables you are using in readButtons.

Using buttonState and lastButtonState looks fine to hold the current detected state and previously detected state so you can detect changes. Using lastButtonPushed as a way to pass the change from readButtons() to navigateMenus() (and then clear it once it's been processed) is also OK. Personally I'd have preferred to have readButtons() return the key that was pressed (or 0 is nothing was pressed) rather than using a global variable, because this reduces the amount of global data and that is always a good thing, but that's mainly a matter of style. But then you also have buttonPushed and buttonPushCounter, and I have no idea what they're for. If you can eliminate them that would reduce the complexity by a useful amount.



Another good point.  Got the code from the ButtonStateChange example.  I was running out the door when I added it and didn't clean it up very well.  Not sure how it would look to eliminate the lastButtonPushed.  This is what I have now: 
Code: [Select]
/*
IMPORTANT: to use the menubackend library by Alexander Brevig download it at http://www.arduino.cc/playground/uploads/Profiles/MenuBackend_1-4.zip and add the next code at line 195
void toRoot() {
setCurrent( &getRoot() );
}
*/
#include <MenuBackend.h>    //MenuBackend library - copyright by Alexander Brevig
//#include <LiquidCrystal.h>  //this library is included in the Arduino IDE

#define buttonPinLeft (1<<0)
#define buttonPinRight (1<<1)
#define buttonPinEsc   (1<<2)
#define buttonPinEnter  (1<<3)

int buttonState = 0;         // current state of the button
int lastButtonState = 0;     //last button state
int lastButtonPushed = 0;


int inputPin = 0;

// LiquidCrystal display with:
// rs on pin 7
// rw on ground
// enable on pin 6
// d4, d5, d6, d7 on pins 5, 4, 3, 2
//LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

//Menu variables
MenuBackend menu = MenuBackend(menuUsed,menuChanged);
//initialize menuitems
    MenuItem menu1Item1 = MenuItem("Item1");
      MenuItem menuItem1SubItem1 = MenuItem("Item1SubItem1");
      MenuItem menuItem1SubItem2 = MenuItem("Item1SubItem2");
    MenuItem menu1Item2 = MenuItem("Item2");
      MenuItem menuItem2SubItem1 = MenuItem("Item2SubItem1");
      MenuItem menuItem2SubItem2 = MenuItem("Item2SubItem2");
      MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3");
    MenuItem menu1Item3 = MenuItem("Item3");


void setup()
{

  Serial.begin(9600);


  //lcd.begin(16, 2);

  //configure menu
  menu.getRoot().add(menu1Item1);
  menu1Item1.addRight(menu1Item2).addRight(menu1Item3);
  menu1Item1.add(menuItem1SubItem1).addRight(menuItem1SubItem2);
  menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3);
  menu.toRoot();
  //lcd.setCursor(0,0); 
  Serial.print("$GO 1 1\r\n");
  //lcd.print("Hello World!");
  Serial.print("$PRINT Hello World!        \r\n");
}  // setup()...


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,1); //set the start position for lcd printing to the second row
 
  if(newMenuItem.getName()==menu.getRoot()){
      //lcd.print("Main Menu       ");
      Serial.print("$CLEAR 2 1\r\n");
       Serial.print("$GO 2 1\r\n");
       Serial.print("$PRINT Main Menu       \r\n");
  }else if(newMenuItem.getName()=="Item1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1       \r\n"); 
    //lcd.print("Item1           ");
  }else if(newMenuItem.getName()=="Item1SubItem1"){
    Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem1       \r\n"); 
    //lcd.print("Item1SubItem1");
  }else if(newMenuItem.getName()=="Item1SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item1SubItem2       \r\n");
    //lcd.print("Item1SubItem2   ");
  }else if(newMenuItem.getName()=="Item2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2       \r\n");   
    //lcd.print("Item2           ");
  }else if(newMenuItem.getName()=="Item2SubItem1"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem1       \r\n");   
    //lcd.print("Item2SubItem1   ");
  }else if(newMenuItem.getName()=="Item2SubItem2"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem2       \r\n");   
    //lcd.print("Item2SubItem2   ");
  }else if(newMenuItem.getName()=="Item2SubItem3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item2SubItem3       \r\n");   
    //lcd.print("Item2SubItem3   ");
  }else if(newMenuItem.getName()=="Item3"){
      Serial.print("$CLEAR 2 1\r\n");
      Serial.print("$GO 2 1\r\n");
      Serial.print("$PRINT Item3       \r\n");   
    //lcd.print("Item3           ");
  }
}

void menuUsed(MenuUseEvent used){
//do something useful here
}

void  readButtons(){  //read buttons status
byte button = analogRead(inputPin) >> 2;
  if (button == 0){
    buttonState=0;
  }
  if (button > 20 && button < 60){
    buttonState=buttonPinLeft;
  }
  else if (button > 60 && button < 100){
    buttonState=buttonPinEnter;
  }
  else if (button > 140 && button < 160){
    buttonState=buttonPinEsc;
  }
  else if (button > 160 && button < 200){
    buttonState=buttonPinRight; 
  }
  else {
    buttonState=0;
  } 

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    if (buttonState != 0 ) {
      lastButtonPushed = buttonState;     
    }
    else {
      lastButtonPushed = 0;
    }
  }
  // save the current state as the last state for next time through the loop
  lastButtonState = buttonState;
}

void navigateMenus() {
  MenuItem currentMenu=menu.getCurrent();
  switch (lastButtonPushed){
    case buttonPinEnter:
      if(!(currentMenu.moveDown())){  //if the current menu has a child and has been pressed enter then menu navigate to item below
        menu.use();
      }else{  //otherwise, if menu has no child and has been pressed enter 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
}

GeoDave

#12
May 28, 2012, 07:49 am Last Edit: May 28, 2012, 07:53 am by GeoDave Reason: 1

If you press the Esc button and leave it for a while, do repeated analog reads fall in the range of 140 to 160 or something closer to 150?


Doesn't seem to be a problem.  I got the code from Bryan Mayland's PID smoker controller the "Linkmeter" (Google it).  Built one! Love it!

hold down Esc = value varies between 151-153

Go Up