I'm working on a control system that will have several relays, a bunch of LEDs and a number of switches. I was rapidly running out of port pins on a TAP-28 board to handle all the desired I/O. Possible solutions included adding an I2C port expander such as the Microchip 23018, but this was an additional complexity to the already-expanding project.
Reading the switches seemed like the easy place to save some port pins. The control system needs at least 5 switches but maybe more. These switches are momentary contact to set various options or to initiate actions, and only one will be pressed at a time. Borrowing from Graham's idea to use a pot (variable resistor) as a multi-position switch and my post on an idea to use an analog input to read switches and a pot, the answer seemed pretty simple. Use an analog input and a resistor network to read a series of switches. I made a prototype with 10 switches but the concept could be greatly expanded.
The schematic is shown below. Ten resistors form a multi-tap voltage divider between +5V and ground. I used 440Ω resistors for each of these (actually, I used 2 220Ω resistors in series for each because of the format of my prototype board). Each switch connects a different tap to an analog input, resulting in a different analog voltage for each switch. I added an additional resistor to pull the analog value up to +5V when no button is pressed. Its value is about 100x the value of the other resistors.
The resistors add almost nothing to the cost of the switches. If the switches have leads spaced on 0.1" centers, building a prototype on a piece of perf board is simple.
The resistors I used are 5% tolerance. If the resistors were perfectly matched, the expected values would be 0%, 10%, 20% and so on of the full scale reading. Because the resistors aren't matched, the actual voltage values that will be measured vary around the expected values. I used a case-select statement to break the readings into 10 zones. The listing below is in Swordfish Basic - converting to other languages should be straight forward.
When I tried the initial version of the software, I would get an erroneous reading occassionally - a higher number button than the button actually pressed was indicated. What was happening was that the ADC read was only catching part of the button press, resulting in a ADC value greater than it should have been. I added the statement to require two consistent reads in a 50 µs period to be a valid read. This completely eliminates erroneous readings and doesn't make reading the keys feel sluggish at all.
The approach will be very stable. The ADC and the voltage divider are both referenced to supply voltage, so the output is ratio-metric and independent of supply voltage. The resistors, in close proximity, will be subject to the same thermal drift and the measurement increments are large so drift won't be an issue.
The maximum number of buttons used is limited by the ADC resolution and the tolerance of the resistors. I'll leave it as an exercise for the reader to determine the range of values for a given voltage divider tap, given that each resistor may be ±5% from its rated value.
One port pin.
Extra cost almost nothing.