#define INPUT_PIN A0 void setup() { Serial.begin(9600); pinMode(INPUT_PIN, INPUT); } void loop() { int result = readAnalogButton(); Serial.println(result); } int readAnalogButton() { int button = analogRead(INPUT_PIN); if (button > 921) return 0; if (button < 256) return 1; if (button < 598) return 2; if (button < 726) return 3; if (button < 794) return 4; if (button < 921) return 5; }

Rather than telling our code to look for the exact values we expect the analog pin to read, we set a

*that can be interpreted as belonging to a specific button. We do this by calculating the points*

**range of values***each expected ADC reading and set these as as the boundaries of our ranges. Just like that, we've got 5 buttons on the one pin! We're not done just yet though, because this is just about the worst possible way we could implement this.*

**halfway between**Assuming perfect resistors, the voltage readings for each button should be around 0.0V, 2.5V, 3.33V, 3.75V and 4.0V, with corresponding ADC readings of around 0, 512, 683, 768 and 819. You might have already noticed a trend by this point...

If we add another 5 switches each with a 1kΩ resistor, the subsequent voltages will be 4.17V, 4.29V, 4.38V, 4.44V and 4.5V, and will continue to grow logarithmically with each 1kΩ resistor we add:

This might work for a project with a small number of buttons if you measure each value, and calculate and hard-code your ADC boundaries for each button - but magic numbers are bad, and you should feel bad.

If we change the voltage scaling from logarithmic to linear, each adjacent ADC value will become roughly equidistant. This means that we can

**, rather than hard-coding fixed values. This allows us to use the same piece of code for any number of buttons and as a bonus, uses the full range of our ADC - in the above example, we jump straight from 0 to 512 between the first two buttons - that's half our resolution!**

*dynamically calculate the corresponding button for any given reading*So, how do we know what resistors we should use?

For the following example, I've used a 47k Ω resistor for R1. I've chosen this value as it allows us to operate the circuit at a super low current (by Ohm's law, 5V / 47kΩ = 0.11 mA) and still use a wide-range of commonly available resistor values. We'll use 5 buttons total, and calculate the value for button 1 (which is actually the second button, like a zero-indexed array).

Since we have a fixed value of R1 we can work out the value for R2 using the following formula:

**R**

_{button}= R1 / (1 - (B_{index}/ B_{total})) - R_{total}Where B

_{index}is the index of the button in question, B

_{total}is the total number of buttons and R

_{total}is the combined value of all resistors in the circuit prior to the current (in this case, it's just the 47kΩ for button 0). So from the above, we have 47000 / (1 - (1 / 5)) - 47000 = 11750Ω. We can use a lookup table to find our closest equivalent value, which in this instance is 12kΩ.

If we do this for the remaining buttons, we get values of 20k, 29k and 120k, which should give us corresponding ADC readings of around 0, 208, 415, 616 and 822.

The column you're most interested in is "Closest R" - this performs a lookup of the closest common resistor value to the calculated required value. The "Req'd" columns indicate the exact values we're looking for, while the "Actual" columns indicate the voltages for the calculated closest available resistor. "Variance" simply tells us the difference between the current and previous ADC reading - if the result here is greater than the specified resistor tolerance, this will be indicated by the "In Tolerance" column.

Great - so we've got our resistors all worked out - all that's left now is the code!

#define BUTTONS 5 #define RESOLUTION 1023 int readAnalogButton() { float avg = RESOLUTION / float(BUTTONS); int val = analogRead(ANALOG_PIN); if (val > (BUTTONS - 0.5) * avg) { return 0; } for (int i = 0; i < BUTTONS; i++) { if (val < round((i + 0.5) * avg)) { return i + 1; } } return 0; }

We start by determining the average difference between adjacent ADC readings, enabling us to dynamically calculate each button's expected ADC reading. To test for an open circuit - the most likely result - we check if the measured value is less than halfway between 0 and our dynamically-calculated reading for the first button. If this evaluates to false, we begin by testing for each button in turn, comparing our ADC reading against the range boundaries that lie between adjacent readings, until we find a match.

Note that because "no button pressed" is a valid result in addition to each individual button press, we treat a zero value as an open circuit, and commence indexing our buttons from 1, as opposed to the zero index used during calculation.

With the above code, we can scale up and down by simply changing the value defined by the macro #define BUTTONS 5, with the number of buttons only limited by the tolerance of your resistors and resolution of your microcontroller's ADC. We'll see just how far we can push this in a future post!

If I use 3,3v how modify the spreadsheet?

ReplyDeleteThank you , Simon

Hi Simon, sorry I'm late to respond here!

ReplyDeleteI've modified the spreadsheet so you can enter the voltage yourself - thanks for the feedback! I should recognise that not all ADCs reference 5V - the ESP8266 references only 1V, for example!

You'll need to make a copy of this file to your own Google Drive using the "Make a copy" function from the "File" menu. This will make a copy that you're free to edit and play with as much as you like!

Good luck - let me know if you need any further help with it.

Thanks for this post!

ReplyDelete