The traditional way to connect a button to a GPIO pin is to have a pull up resistor to Vcc, and have the switch pull the GPIO to ground when it’s pushed. The catch is that you need one pin for every button. An alternative is to use a matrix: for example, using 4 outputs and four inputs to scan a 16 button keypad. To reduce the number of GPIOs still further, we can use a resistor ladder, something like this example below.
The diagram above is an attempt to use the minimum number of resistors, and a more optimal design would be possible with additional resistors.
When all the switches are open, we will see 3V3 at the analogue input AIN. When a switch is closed, it connects one of the resistors to ground, forming a potential divider and reducing the output voltage. For example, if we press S2 then the output voltage will be half the supply voltage.
When multiple switches are closed, the resistors are connected in parallel, so we will get a different value for each combination of switches. Provided the values are sufficiently different, we can detect all possible switch combinations.
It’s fairly easy to create a spreadsheet to calculate the output voltage for a given set of resistors, and switch states. The table below shows the predicted output voltage, and the actual measured voltage for every switch combination:
The differences between the actual and predicted voltages are due to resistor and supply voltage tolerances. These show fairly good agreement if they are normalised to supply voltage.
This is what the results look like when graphed. Note that the results are not linear, but the values are sufficiently different to be able to reliably detect different key combinations.
Having taken the analogue reading from the sensor, it can be compared to the possible values shown above. This can either be achieved with a look up table, or by fitting a function to the data. Having obtained the integer code 0 to 7, the state of an individual button can be detected by testing individual bits of that code.