Tutorial: Using analog input for multiple buttons
Please note that the tutorials are not currently compatible with Arduino IDE v1.0. Please continue to use v22 or v23 until further notice.
This is chapter twenty-five of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A tutorial on the Arduino universe.
The first chapter is here, the complete series is detailed here. Please note from November 1, 2010 files from tutorials will be found here.
Welcome back fellow arduidans!
The purpose of this article is demonstrate how you can read many push buttons (used for user-input) using only one analog input pin. This will allow you to save digital I/O pins for other uses such as LCD modules and so on. Hopefully you recall how we used analogRead() in chapter one, and how we used a potentiometer to control menu options in exercise 10.1. For this article, we will be looking at reading individual presses, not simultaneous (i.e. detecting multiple button presses).
To recap, an analog input pin is connected to an analog to digital (ADC) converter in our Arduino’s microcontroller. It has a ten bit resolution, and can return a numerical value between 0 and 1023 which relates to an analog voltage being read of between 0 and 5 volts DC.
With this sketch:
/* Example 25.1 - Demonstrating analogRead()
http://tronixstuff.com/tutorials > chapter 25
CC by-sa-nc*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 2, 3);
int a=0;
void setup()
{
lcd.begin(20, 4);
pinMode(A5, INPUT);
}
void loop()
{
a = analogRead(5);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(" analogRead() ");
lcd.setCursor(0,1);
lcd.print(" value is :");
lcd.print(a);
delay(250);
}
and in the following short video, we have demonstrated the possible values returned by measuring the voltage from the centre pin of a 10k ohm potentiometer, which is connected between 5V and GND:
As the potentiometer’s resistance decreases, the value returned by analogRead() increases. Therefore at certain resistance values, analogRead() will return certain numerical values. So, if we created a circuit with (for example) five buttons that allowed various voltages to be read by an analog pin, each voltage read would cause analogRead() to return a particular value. And thus we can read the status of a number of buttons using one analog pin.
Example 25.2
The following circuit is an example of using five buttons on one analog input, using the sketch from example 25.1:
And here it is in action:
Where is the current coming from? Using pinMode(A5, INPUT); turns on the internal pull-up resistor in the microcontroller, which gives us ~4.8V to use. Some of you may have notice that when the right-most button is pressed, there is a direct short between A5 and GND. When that button is depressed, the current flow is less than one milliamp due to the pull-up resistor protecting us from a short circuit. Also note that you don’t have to use A5, any analog pin is fine.
As shown in the previous video clip, the values returned by analogRead() were:
- 1023 for nothing pressed (default state)
- 454 for button one
- 382 for button two
- 291 for button three
- 168 for button four
- 0 for button five
So for our sketches to react to the various button presses, they need to make decisions based on the value returned by analogRead(). Keeping all the resistors at the same value gives us a pretty fair spread between values, however the values can change slightly due to the tolerance of resistors and parasitic resistance in the circuit.
So after making a prototype circuit, you should determine the values for each button, and then have your sketch look at a range of values when reading the analog pin. Doing so becomes more important if you are producing more than one of your project, as resistors of the same value from the same batch can still vary slightly.
Using the circuit from example 25.2, we will use a function to read the buttons and return the button number for the sketch to act upon.
/* Example 25.3 - Digital buttons with analog input
http://tronixstuff.com/tutorials > chapter 25 CC by-sa-nc */
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 2, 3);
int a=0;
void setup()
{
lcd.begin(20, 4);
pinMode(A5, INPUT); // sets analog pin for input } int readButtons(int pin) // returns the button number pressed, or zero for none pressed // int pin is the analog pin number to read {
int b,c = 0;
c=analogRead(pin); // get the analog value if (c>1000)
{
b=0; // buttons have not been pressed
} else
if (c>440 && c<470)
{
b=1; // button 1 pressed
} else
if (c<400 && c>370)
{
b=2; // button 2 pressed
} else
if (c>280 && c<310)
{
b=3; // button 3 pressed
} else
if (c>150 && c<180)
{
b=4; // button 4 pressed
} else
if (c<20)
{
b=5; // button 5 pressed
}
return b;
}
void loop()
{
a=readButtons(5);
lcd.clear();
if (a==0) // no buttons pressed
{
lcd.setCursor(0,1);
lcd.print("Press a button");
} else
if (a>0) // someone pressed a button!
{
lcd.setCursor(0,2);
lcd.print("Pressed button ");
lcd.print(a);
}
delay(1000); // give the human time to read LCD }
And now our video demonstration:
So now you have a useful method for receiving input via buttons without wasting many digital input pins. I hope you found this article useful or at least interesting.
Have fun and keep checking into tronixstuff.com. Why not follow things on twitter, Google+, subscribe for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other – and we can all learn something.





But what about detecting multiple simultaneous presses?
In a future article.
John
This works nicely for a single button pressed at a time, but will always return the rightmost button if multiple buttons are pressed at the same time. I wonder if it’s possible/feasible to design a network that would give a unique value for all possible combinations of buttons?
I’m thinking maybe something like a pull-up resistor between VCC and A5, and then in parallel, each button would connect A5 to ground through a resistor, with each button going through a different value of resistor. You’d have to calculate the values so that every possible combination of buttons would result in a different voltage at A5. With just a couple of buttons it would be easy, but more buttons would start making it difficult to have values far enough apart to reliably distinguish between them.
Yes, good point. I probably should have articulated in the post a little better, that the goal was for extra buttons but nothing too complex.
However you do have a good, workable suggestion.
Cheers
john
A high-value pull-up and then having each button go to GND via a unique value resistor is a pretty common technique for this sort of thing, and if you don’t need too many buttons it’s really easy to determine the values you need. Just treat it like binary: double the value each time, so use, say, the nearest equivalent within the available resistor range to 500R, 1K, 2K, 4K, 8K, etc. You’ll need to use a very weak pull-up (perhaps 100k) but then you’ll easily be able to determine any combination of buttons pressed simultaneously.
Thank you for that. I’m in the process of writing a part two for this later, to deal with simultaneous presses.
Cheers
john
The interesting part will be the software. Rather than hand-coding each potential voltage range to a button combination, creating a generalised method to parse the analogueRead() value.
Including an upper/lower tolerances makes it a bit more curly. Otherwise, you could just map the range (say [0,468] on analgueRead() to an integer [0,128] then perform binary AND operations with 1,2,4,8,16,32&64 to determine the buttons down.
Maybe a setup loop that makes an array of range values (upper and lower limits) for all button combinations is the way to go. Then the main code can search through the range array for a hit.
Just thinking out aloud here…
Maybe i’m wrong but i think that the first from the left couple resistor/button should be connected to 5V
That is another way of doing it. In my example I have turned on the internal pullup resistor with pinMode(a5, INPUT) which gives me 4.79V to use.
john
thanks for this John, always good to know how to save a few pins!
Russ
Any thoughts on how to best integrate a debounce into this? It’s the one thing that’s kept me from using this technique in the past…for the projects I work on, debounce ends up being very important.
Hello
Thanks for your question. Some people go to the extreme of putting a 555 monostable timer, me – I just put a delay() function after the analogRead(). Usually 200~300 ms is long enough, as long as you’re not making some sort of action game you should be ok.
cheers
john
I am having a hard time understanding this. Is it possible to wire this up with jumper wires instead of buttons? I don’t have any momentary buttons and the ones at Radioshack look to big.
Yes of course, a button is just a break in a wire.
cheers
John
Great tutorial. How come you don’t have the sketch for example 25.2?
Hi
It uses example 25.1 – …”The following circuit is an example of using five buttons on one analog input, using the sketch from example 25.1:”
cheers
John
I was wondering if this also works with interrupts.
Because the interrupt pins are limited, maybe this way you are able to have more buttons on one interrupt pin.
The question is, can you still readout a value from an interrupt pin after a short press on one of the analog buttons?
Afraid not, interrupts are only available on digital pins.
Hi John,
Just noticed that the code – 25.3 – is not correct. It only contains the setup() function.
Thanks. It has now been updated.
can 1 get the circuit for example 25.1??
It is only a normal LCD connected to the Arduino. See chapter 24 for how to do this.