6
\$\begingroup\$

I'm using analog pin 5 on Arduino to detect presses from 6 push-buttons. On the picture top-right button is number 1 and then from right to left they go as 2, 3, 4, 5, 6. Program should print 0 when none of the buttons is pressed and if one of them is presses, it should print its position as I mentioned before. Currently the problem is that if I press say second button, it will (instead only once) sometimes print 2 a couple of times. I guess it is because of the "noise" when button is pressed and that it should be debounced, but I don't know how to debounce analog pin.

My code:

int old_button = 0;
int button;
int pressed_button;
int z;

void setup () {
  Serial.begin(9600);
  pinMode(A5, INPUT);
}

void loop () {
  z = analogRead(5);
  if (z > 1021) button = 0;                                           
  else if (z > 511 && z < 514) button = 1;                     
  else if (z > 680 && z < 684) button = 2;                
  else if (z > 766 && z < 770) button = 3;                
  else if (z > 817 && z < 822) button = 4;             
  else if (z > 851 && z < 856) button = 5; 
  else if (z > 875 && z < 880) button = 6;
  else button = 0;                                                      

  if (old_button == button) {                                           
    old_button = button;                                              
    pressed_button = 0;                                               
  }  

  else {                                                                
    old_button = button;                                             
    pressed_button = button;                                        
  }
  Serial.println(pressed_button);
}

Circuit (2200 ohm resistors):

enter image description here

\$\endgroup\$
3
  • 1
    \$\begingroup\$ An electrical schematic would be better than a Fritzing diagram, in this specific case. Use the builtin schematic editor. \$\endgroup\$
    – Passerby
    Commented Mar 2, 2014 at 1:06
  • 1
    \$\begingroup\$ @Mate Just FYI. There is now an specialized Arduino stack: arduino.stackexchange.com \$\endgroup\$ Commented Mar 2, 2014 at 19:08
  • \$\begingroup\$ Can this solution work with DEEP SLEEP mode (using 0.2 uA on average with an ATmega or ATtiny on breadboard), and Pin Change Interrupt (PCINT)? \$\endgroup\$
    – Basj
    Commented Nov 24, 2019 at 13:46

4 Answers 4

5
\$\begingroup\$

When you detect a significant difference in ADC reading, wait 20 milliseconds and then average a few readings then make a decision. If one of the readings still looks badly quantifiable, wait another short period of time.

\$\endgroup\$
7
  • \$\begingroup\$ +1 for answer. hey Andy, is it wisely to ground a 104 capacitor from the buttons line(for debancing)? \$\endgroup\$
    – Roh
    Commented Mar 1, 2014 at 16:45
  • \$\begingroup\$ @roh not wise if it means connecting a switch across a charged up 100nF cap because the current pulse could reset the MCU. Best to put 100 ohms in series with the switch. \$\endgroup\$
    – Andy aka
    Commented Mar 1, 2014 at 17:03
  • \$\begingroup\$ @Andyaka: Could you please write me a pseudocode for your answer? \$\endgroup\$
    – Nick
    Commented Mar 1, 2014 at 20:16
  • 1
    \$\begingroup\$ @Mate I'm a verbal sort of dude (well, most of the time and some of the time undecipherable!) and I haven't written pseudo code in millenia. It shouldn't be difficult to visualize what I'm saying - I like circuits but I didn't ask you to change that awful fritzing thing to one LOL \$\endgroup\$
    – Andy aka
    Commented Mar 1, 2014 at 20:21
  • 1
    \$\begingroup\$ @Andyaka The current pulse from a 100nF could reset the MCU?! What kind of worthless crap MCU is that?! Throw it out instantly, get a real one... \$\endgroup\$
    – Lundin
    Commented Mar 4, 2014 at 9:25
3
\$\begingroup\$

Yes, it is debounce noise. You need to wait a bit (say 50 ms) and read the analog input again. If the result matches, then the button value can be considered valid. Something like this:

int old_button = 0;

int getButton()
{
  int i, z, sum
  int button;

  sum = 0;
  for (i=0; i < 4; i++)
  {
     sum += analogRead(5);
  }
  z = sum / 4;
  if (z > 1021) button = 0;                                           
  else if (z > 511 && z < 514) button = 1;                     
  else if (z > 680 && z < 684) button = 2;                
  else if (z > 766 && z < 770) button = 3;                
  else if (z > 817 && z < 822) button = 4;             
  else if (z > 851 && z < 856) button = 5; 
  else if (z > 875 && z < 880) button = 6;
  else button = 0;

  return button;
}


void loop ()
{
  int button, button2, pressed_button;  
  button = getButton();
  if (button != old_button)
  {
      delay(50);        // debounce
      button2 = getButton();

      if (botton == button2)
      {
         old_button = button;
         presed_button = button;
         Serial.println(pressed_button);
      }
   }
}

Since Andy didn't want to provide any code, I added some averaging when taking the ADC readings. So the averaging inside getButton accounts for any noise reading the analog line coming into the ADC, and the 50 ms delay takes care of detecting switch bounce.

\$\endgroup\$
1
  • \$\begingroup\$ @Mate I added he code for averaging out the ADC noise to my answer. Thanks to Andy aka for suggesting it, it was a good addition. \$\endgroup\$
    – tcrosley
    Commented Mar 2, 2014 at 0:29
3
\$\begingroup\$

Many switches of the style you're using are hard to debounce well, and trying to use a resistive multiplexer won't make it easier. Still, if you can afford two port pins (one analog), I can offer a recipe for success which should work even if your switches are pretty lousy. I'd suggest moving the bus-bar resistor (ignore the bottom bus bar of the breadboard) to the left of your leftmost one, and wiring that to a the non-analog port pin. The common wire on the top should be connected to the analog port pin. The wire from the right-most resistor should connect to ground.

When your unit is "idle" [you don't think any buttons are pushed] set the common output to high and float the other one. It will read low when no buttons are pushed, and will read close to VDD when any button is pushed.

To find out which button is pushed, float the common wire and drive the left-side wire high. Briefly set the common wire high or low (see note) and float it again, then read the voltage on that pin a little while later (keeping the left-side wire high). Once the reading has been taken, you may if desired turn off power to the left-side wire (turn it on again before the next reading cycle).

While a button is pushed, the voltage on the common wire should be a nice fraction of VDD (if there are six buttons and seven resistors, the buttons should read 1/7, 2/7, 3/7, etc. up to 6/7); the voltage should not be overly affected by whether the common pin had been briefly pulsed high or low. If no button is pushed, readings after the pin had been pulsed high will be much higher than when after it had been pulsed low. This will indicate that the button has been released.

Once the button has been released, you may go back to the "idle" configuration. When the left resistor is driven high, the resistor string will draw current whether or not any buttons are pressed, but when the left pin is floated and the common wire is high, no current will be drawn until a button is pushed.

To get good results with cheap switches, you should delay long enough after each momentary "ground" or "VDD" pulse on your common wire that if the switch is making any contact at all it will yield a good reading (try putting a 100K resistor in parallel with a switch, and adjust the delay and sensitivity so that it "barely" registers the switch as being held). Cheap switches have a resistance of well over a meg when totally released, and less than 10 ohms when fully pressed, but their resistance can wander all over the place between those states, and conventional debounce timing won't help. What will help is having a circuit that won't detect a button press until the resistance gets fairly low, and will regard a button as held unless its resistance gets much higher. For the circuit I've described, the left-side button will be more "sensitive" to "new" button pushes than the right-side button, and "hold" sensitivity may be different for all six buttons, but you should have little trouble ensuring that every button's "hold" sensitivity is much higher than its "new push" sensitivity, which is the requirement for reliable debouncing.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ This sounds really great but needs some kind of graphic. \$\endgroup\$
    – Krista K
    Commented Sep 6, 2019 at 1:17
  • \$\begingroup\$ Completely agree. A circuit diagram (even a rough sketch) would really help elucidate the otherwise excellent answer. \$\endgroup\$
    – bdutta74
    Commented Jan 5, 2020 at 6:23
3
\$\begingroup\$

I have just recently started working with Arduino, and whilst this is an old question, I found this thread when trying to extend the number of buttons I could listen to.

I worked from the original Fritzing illustration and produced a slight variant in both hardware and software.

Arduino Analog listening to 6 pushbuttons

I modified the resistor set to use different values to try and produce a more consistent step between switches rather than the logarithmic steps produced by using the same resistors throughout.

From 5V, the resistors are:

  • 1kΩ
  • 180Ω
  • 240Ω
  • 330Ω
  • 510Ω
  • 800Ω

This produces steps of around 0.5V between each switch.

I also built some basic software which looks for the same value for 3 readings in a row before acknowledging a change in voltage. This eliminates small variations in the reading.

int sensorPin = A5; // The input port
int sensorValue; // Current reading
int outputValue; // The reported reading
int lastValues[3] = {0,0,0}; // The last 3 readings

void setup() {
  Serial.begin(9600);
}

void loop() {
  // read the value from the sensor:
  sensorValue = analogRead(sensorPin);
  // Initialise variables for checks
  int i;
  int updateOutput = 1;
  // Loop through previous readings
  for( i = 0 ; i<3 ; i++ ){
    // If this historic value doesn't match the current reading,
    // we will not update the output value
    if( lastValues[i] != sensorValue ){
      updateOutput = 0;
    }
    // Shift the array elements to make room for new value
    if( i>0 ){
      lastValues[(i-1)] = lastValues[i];
    }
  }
  // Update if needed
  if( updateOutput == 1 ){
    outputValue = sensorValue;
  }
  // Append the new value
  lastValues[2] = sensorValue;
  // Debugging output
  Serial.print(sensorValue);
  Serial.print(" ");
  Serial.println(outputValue);
}

Obviously, the lastValues array could be made to whatever length you wanted - the longer the array, the longer a switch must be depressed to be detected.

In my testing, the sensor values reported were:

  • 1023: No button
  • 0: Button #1
  • 196: Button #2
  • 301: Button #3
  • 405: Button #4
  • 508: Button #5
  • 613: Button #6

(Tested as per diagram.)

My rough maths suggests that you could even extend this out to 8 buttons, by adding the following resistors next (which should see the voltage again drop in 0.5V steps):

  • 1.7kΩ
  • 5kΩ
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.