four buttons on one ADC pin


Sometimes you just barely get away with the number of available microcontroller GPIO pins to implement the desired functionality — And then you discover that you have no more pins left to read out any buttons that a user could push to actually operate the device.

What I present you now is a solution to get away with sacrificing just one µC pin and get four buttons in return without adding any additional digital (or analog) ICs.

You might have guessed it, it can’t be just any GPIO pin but it has to be an ADC pin. With a tiny trick the circuit even allows to correctly decode combinations of two buttons pressed simultaneously.

This technique is especially useful if you use an ATTiny85, i.e. you only have six GPIO pins to start with, but at least four of them can be used as ADC.

Not The Circuit

Okay, you say, that is simple – 4 buttons that is 4 bits of information = 16 different states which can easily be mapped equidistantly across the range of the ADC. A regular 10 bit ADC should have no problem distinguishing between those 16 different voltages. So far, so good.

a 4 bit R2R DAC

Maybe the schematic of an R2R-DAC pops into your mind – with buttons as digital signal sources at each stage (a0 … a3) of the resistor ladder – problem solv… oh dang – a regular push button can only actively pull low OR high (not both). Hmm. (But I bet this would work with SPDT switches!)

The Circuit

Let me propose something different instead:

a different kind of ladder

We even get away with six instead of eight resistors in comparison to the R2R ladder.

I guess you immediately get the basic idea: RA and R1..4 comprise a resistor ladder giving us four distinct voltage levels and by pressing one of the push buttons you select the respective ladder voltage to be measured by the ADC. RB is just a weak pull down, so the ADC sees 0V when no button is pressed.

“DUH!” – you say, “that’s not four independent buttons! I want to use key combinations.”

Okay at this point I have to admit: This will not give you the full 4 bit of button information, sorry. — However, if you like key combinations – ask yourself what is the maximum number of buttons you intend to push down at the same time? If you only need combinations of two buttons then this still is for you.

You see the ladder is not entirely symmetric. RA is 680R, while the rest is 1k. If they all were 1k then you could not distinguish between, for example, the combination SWA+SWD and the combination SWB+SWC, since both would produce close to VCC/2.

With RA different from the other ladder resistors we can actually resolve all ambiguities for combinations of one or two simultaneous button presses. The value of 680R is not the optimal value (750R) but it is close enough – and more importantly – part of the E12 resistor series. So you should have no trouble finding suitable resistors.

The Calculation

“So there are no ambiguities, but you made a complete mess with that crooked divider. What voltage corresponds to which button?! I don’t want to calculate that mess!” — okay I hear you. Look I already did the calculation (with a tiny python program) so you don’t have to. Here are the results:

 SW pressed:
 A, B, C, D
[0, 0, 0, 0]  ->  V_ADC/VCC: 0.000
[1, 0, 0, 0]  ->  V_ADC/VCC: 0.212
[0, 1, 0, 0]  ->  V_ADC/VCC: 0.423
[1, 1, 0, 0]  ->  V_ADC/VCC: 0.270
[0, 0, 1, 0]  ->  V_ADC/VCC: 0.634
[1, 0, 1, 0]  ->  V_ADC/VCC: 0.371
[0, 1, 1, 0]  ->  V_ADC/VCC: 0.539
[0, 0, 0, 1]  ->  V_ADC/VCC: 0.850
[1, 0, 0, 1]  ->  V_ADC/VCC: 0.593
[0, 1, 0, 1]  ->  V_ADC/VCC: 0.743
[0, 0, 1, 1]  ->  V_ADC/VCC: 0.811

Here is the above table as a scatter plot:

or alternatively if we sort the configurations by voltage:

Or once again – grouped into zero, one or two buttons pressed:

The closest distance of two voltage values is 3.9% of VCC. Q: How do I know this is close to the optimum? – A: More brute force calculation in python.

Arduino Code

So we can get our button information back by implementing a lookup table in the Arduino code. In order for the measurement and the decoding to be robust I allow for a margin of error of ±3.9/2% from the theoretical ADC value (a value region 38 LSB wide). In the following table I assume we measure the voltage divider ladder with a 10bit ADC (typical AVR based Arduino).

 ADC value -> sw DCBA
 0000-0018 ->  0b0000
 0197-0235 ->  0b0001
 0257-0294 ->  0b0011
 0360-0398 ->  0b0101
 0413-0451 ->  0b0010
 0531-0569 ->  0b0110
 0587-0625 ->  0b1001
 0629-0667 ->  0b0100
 0740-0778 ->  0b1010
 0810-0848 ->  0b1100
 0850-0888 ->  0b1000

Here is some Arduino code that will do the job – the lower nibble of the return value represents the buttons that are currently pressed. You might notice, that I only check the ADC value against the upper boundary, which is totally sufficient.

# in this example we have the buttons on A0
# change this to your ADC pin
const int button_adc_pin = 0;

uint8_t decode_adc_buttons(){

  int adcval = analogRead(button_adc_pin);
  if (adcval <= 100)
    return 0b0000;
  else if (adcval <= 253)
    return 0b0001;
  else if (adcval <= 294)
    return 0b0011;
  else if (adcval <= 398)
    return 0b0101;
  else if (adcval <= 451)
    return 0b0010;
  else if (adcval <= 569)
    return 0b0110;
  else if (adcval <= 625)
    return 0b1001;
  else if (adcval <= 667)
    return 0b0100;
  else if (adcval <= 778)
    return 0b1010;
  else if (adcval <= 848)
    return 0b1100;
  else if (adcval <= 888)
    return 0b1000;
    return 0b0000;

Some more thoughts

Can you do five buttons on one pin WITH combinations of two? – I quickly simulated that, but It does not look promising. Too many combinations too close together. But hey, if you got two analog pins to spare, you could get already eight buttons!

“Help, I have a microcontroller with a 12 bit ADC, how can I use this method?” – Divide the result of the analogRead by 4. Then you have effectively a 10 bit ADC.

“It’s almost working but sometimes I get weird results. Some buttons seem to trigger the functions of other buttons?!” – Just like digital buttons bounce, our analog button ensemble can “bounce”. By that I mean trying to decode nonsensical data when the ADC reads the voltage while the µC pin is in the middle of being charged (or discharged) to the target voltages of the divider. Just like with digital bouncing you can de-bounce the output of “decode_adc_buttons()” by calling the function twice or more and check if a “button” (a bit of the function’s return value) transitions from LO to HI and stays HI for a certain number of cycles or milliseconds. Et voila.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.