Introduction: Driving Dozens of Buttons
This project came about because I was trying to think of the best way to get input from a large number of buttons, while requiring the minimum number of Arduino pins. I started with the wonderful tutorial at the Arduino site demonstrating how to get input from one button (see: https://www.arduino.cc/en/Tutorial/Button). There are also a number of execllent Instructables which demonstrate clever ways to combine multiple buttons with resistors so you only need one Arduino pin.
However, my goal was to be able to detect button presses from dozens buttons, and most of the tutorials above were restricted in the number of buttons they could address. Also, I noticed that many of them were not able to detect multiple, simultaneous button presses, which was another feature I wanted to support.
Step 1: Thinking It Through
So, let's think this through.
If you look at the tutorial given above, you can see that the brute-force solution is that for every button we need one power pin, one drain through a 10 KOhm resistor and one input pin. The power pin and drain can be shared across all buttons, but, in the circuit shown there, we cannot get around the fact that we need a separate input pin for each individual button. If our application requires 10 buttons, we need 10 Arduino pins. Ouch.
As I gave the matter some thought, though, it occurred to me that this was similar to a problem I faced in a different project I had worked on, namely addressing multiple LED display digits, something we see everywhere from microwave ovens to alarm clocks. So please have patience with me while I digress into LED land for a moment.
In most circuits which include multiple LED digits, each digit is not activated with a constant current. Rather, the controller sets up the segment-pattern for a particular target digit (say, the pattern which makes up the number "5"), and outputs that pattern to all the digits in the display simultaneously. It then sets up the pattern for the next digit, outputs that, etc., cycling through all the digits and repeating forever.
But, you may ask, how is it possible, then, to see different numbers on each digit? This is possible because the controller uses transistors to turn on the target digit and turn off all the others before outputting the pattern.
For example, let's say we have a 4 digit LED display (each digit named A, B, C and D), and we want to show the numbers "5678" on this display. Here's what happens:
- the controller turns A on and turns digits B, C and D off
- it outputs the pattern for the number "5" to all the digits
- it turns B on and A, C, D off
- outputs pattern for number "6"
- turns C on and A, B, D off
- outputs "7"
- turns D on and A, B, C off
- outputs "8"
- repeats from step 1
This allows the electronics involved to be much simpler and cheaper. And it only works because the controller and the transistors can turn digits on and off so unbelievably fast that each individual segment on each digit doesn't actually have time to fade before it is refreshed again. This process is known as "multiplexing" or "strobing", and it allows you to drive, theoretically, any number of LED digits. (In actuality, however, there is a limit: as you add more and more digits, you will notice that they begin to flicker, because the controller can't refresh them fast enough. Eventually, this flicker becomes so bad that you are no longer able to recognize the numbers.)
OK, back to button land.
I saw a connection between the LED multiplexing above and this multiple buttons problem: what if we could multiplex the buttons in some way? Instead of multiplexing the output, we would multiplex the input: this means that we would turn off all but one button, detect that button's up/down state, store it, and then do the same for the next button, etc., cycling through all the buttons very, very quickly. Since only one button is active at any given instant, and the controller knows which one it is, we could use the same, single input pin for any number of buttons.
Sounds good, but hold on: we still need to activate every button, one at a time. So, haven't we just turned the problem around, since now we need X output pins for X buttons?
Well, this is where we bring in shift registers. The shift register is an IC which receives a pattern of 8 bits one after another (i.e., serially), and, when commanded, will drive that pattern to 8 output pins simultaneously (i.e., in parallel). Just search Instructables for "shift register" and you'll find many tutorials. Shift registers are fast, easy to use, very cheap and tremendously powerful. But, we do still need to command the shift register, and it takes 3 Arduino pins to do so. However, the wonderful part is that you can connect multiple shift registers together in a chain, and still drive the whole chain with the same 3 Arduino pins!
OK, so where does all this get us? If we can figure out how to multiplex the button inputs, we can use one input pin, and if we can leverage shift registers, we only need 3 output pins. Hey! We've brought the problem down to requiring only 4 pins total to drive a (theoretically) limitless number of buttons. The breaking point is 4: if you need 4 or less buttons, just brute-force it: use 4 input pins, since this approach is unnecessarily complex and doesn't gain you anything. If, however, you need 5 or, especially many more, you win if you use the following circuit and code.
Step 2: Parts
For this circuit, you'll need the following parts:
- Arduino Uno
- bread board and patch wires
- 3 standard, momentary buttons
- 1 10 KOhm resistor
- 1 74HC595 shift register (DigiKey, Amazon, etc.)
This is only a proof-of-concept, so I'm only using 3 buttons here. The circuit and code can be extended easily to drive many, many more buttons.
Step 3: The Circuit
The circuit is given above.
In order to see it do anything, you'll have to use the Serial output window: when you press any button you'll see "Button X Down", and when you let go you'll see "Button X Up".
Step 4: The Code
Attached is the code.
The real crux of the script happens in a function which must be called every loop. This function checks exactly one button. It sends a pattern of bits to the shift register which turns off all the buttons except the one target button for this loop. It then checks the state of the input pin at this instant: if it's HIGH, this button is pressed, if LOW the button isn't. It then compares this state against the previous state for this button to decide if the user just did something new or not. It stores state for this button and advances an index so the next button in order will be checked in the next loop. I also included a timer so you can tune the script for CPU usage vs. button sensitivity.
Let me know if you find any bugs.
I hope this helps anyone who is faced with the problem of needing to handle input from a large number of buttons using the least number of Arduino pins. I'm very interested in hearing from anyone who has solved this problem in other ways.