Reading more than a few pushbuttons requires multiplexing, with a parallel-in shift register similar to the old 74LS166 being popular (and supported by the shiftIn()
function). You can also use an Arduino analog input to multiplex the buttons, at the cost of a resistor string that probably draws more current and costs more than a logic IC:

The switches produce voltages at the analog input which are not the evenly spaced 1 V increments you might expect: the 10 kΩ pullup appears in parallel with the sum of all the resistors above the closed switch, so the voltages come out a bit higher. The notation to the right of each switch indicates the voltage and equivalent ADC value, assuming a 5.0 V AVREF
that won’t be quite right for your circuit. The analog input spec recommends less than 10 kΩ source resistance, but you could probably go much higher without any problem; the ADC output value need not be particularly accurate.
If you happen to have a SIP resistor pack containing five separate resistors (not the usual nine resistors in a 10 lead SIP), then the circuitry doesn’t amount to much:

It’s sitting in front of the ZNVL110A MOSFETs driving the RGB LED strip light. Those flat blue surplus buttons came in pairs pre-configured with wire leads and just begged to get out of the heap for this occasion. The encoder knob remains as before, with its shaft push-on momentary switch still going directly to analog input A5
. The new button circuitry connects to that switch lead, ungainly though it may appear, with the gray wire bringing VCC from the cluster of sensor inputs.
To simplify reading the buttons, build an array of threshold voltages about halfway between the calculated switch voltages:
enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS}; word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024};
You could do the circuit calculation and VCC calibration in there, too, but those widely spaced increments don’t pose much of a problem. The table must include an end marker of 1024, greater than any possible analog input.
Then you read the button input voltage and walk upward through the table until the value falls below a threshold, a process I find much cleaner and easier than a pile of conditionals sprinkled with fiddly constants.
byte ReadButtons(byte PinNumber) { word RawButton; byte ButtonNum; RawButton = analogRead(PinNumber); for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){ if (RawButton < ButtonThreshold[ButtonNum]) break; } return ButtonNum; }
As long as the button stays down, that function returns its ID number. You can detect both edges of a button press:
Button = ReadButtons(PIN_BUTTONS); if (PrevButton != Button) { if (Button == N_BUTTONS) { printf("Button %d released\n",PrevButton); } else printf("Button %d pressed\n",Button); PrevButton = Button; }
The demo code produces results like this:
Ed Nisley - KE4ZNU - December 2012 Knob encoder and buttons Ed Nisley - KE4ZNU - December 2012 Knob count: 2 Knob count: 3 Knob count: 4 Knob count: 3 Knob count: 2 Knob count: 1 Knob count: 0 Knob count: 2 Knob count: 4 Knob count: 5 Knob count: 6 Knob count: 7 Knob count: 8 Knob count: 11 Knob count: 15 Knob count: 16 Knob count: 17 Button 0 pressed Button 0 released Button 1 pressed Button 1 released Button 2 pressed Button 2 released Button 3 pressed Button 3 released Button 4 pressed Button 4 released Button 2 pressed Button 2 released
This scheme works for a single button pressed at a time, which is generally how you use discrete buttons. It’s not appropriate for keyboards or multi-axis joystick button arrays, which you could multiplex using resistors that produce accurate binary steps, but that’s fraught with peril and error.
As with all non-interrupt-driven buttons, you must poll the button input at a reasonable rate to have a responsive UI. Non-blocking loop()
code will be your friend.
It made sense to exercise the new buttons in the encoder knob demo code, so this will look familiar…
The Arduino source code:
// Quadrature knob with switch // Ed Nisley - KE4ANU - November 2012 // Based on: // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/ //---------- // Pin assignments const byte PIN_KNOB_A = 2; // knob A switch - must be on ext interrupt 2 const byte PIN_KNOB_B = 4; // .. B switch const byte PIN_BUTTONS = A5; // .. push-close momentary switch const byte PIN_SYNC = 13; // scope sync //---------- // Constants const int UPDATEMS = 10; // update LEDs only this many ms apart #define TCCRxB 0x02 // Timer prescaler enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1}; enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS}; //---------- // Globals volatile char KnobCounter = 0; volatile char KnobState; char PrevKnobCounter = 0; byte Button, PrevButton; // ButtonThreshold must have N_BUTTONS elements, last = 1024 word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024}; unsigned long MillisNow; unsigned long MillisThen; //-- Helper routine for printf() int s_putc(char c, FILE *t) { Serial.write(c); } //-- Knob interrupt handler void KnobHandler(void) { byte Inputs; Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A); // align raw inputs // Inputs ^= 0x02; // fix direction switch (KnobState << 2 | Inputs) { case 0x00 : // 0 00 - glitch break; case 0x01 : // 0 01 - UP to 1 KnobCounter++; KnobState = KNOB_CLICK_1; break; case 0x03 : // 0 11 - DOWN to 1 KnobCounter--; KnobState = KNOB_CLICK_1; break; case 0x02 : // 0 10 - glitch break; case 0x04 : // 1 00 - DOWN to 0 KnobCounter--; KnobState = KNOB_CLICK_0; break; case 0x05 : // 1 01 - glitch break; case 0x07 : // 1 11 - glitch break; case 0x06 : // 1 10 - UP to 0 KnobCounter++; KnobState = KNOB_CLICK_0; break; default : // something is broken! KnobCounter = 0; KnobState = KNOB_CLICK_0; } } //-- Read and decipher analog switch inputs // returns N_BUTTONS if no buttons pressed byte ReadButtons(int PinNumber) { word RawButton; byte ButtonNum; RawButton = analogRead(PinNumber); // printf("RawButton: %d ",RawButton); for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){ // printf(" (%d:%d)",ButtonNum,ButtonThreshold[ButtonNum]); if (RawButton < ButtonThreshold[ButtonNum]) break; } // printf(" ButtonNum %d\n",ButtonNum); return ButtonNum; } //------------------ // Set things up void setup() { pinMode(PIN_SYNC,OUTPUT); digitalWrite(PIN_SYNC,LOW); // show we arrived // TCCR1B = TCCRxB; // set frequency for PWM 9 & 10 // TCCR2B = TCCRxB; // set frequency for PWM 3 & 11 pinMode(PIN_KNOB_B,INPUT_PULLUP); pinMode(PIN_KNOB_A,INPUT_PULLUP); KnobState = digitalRead(PIN_KNOB_A); Button = PrevButton = ReadButtons(PIN_BUTTONS); attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE); Serial.begin(9600); fdevopen(&s_putc,0); // set up serial output for printf() printf("Knob encoder and buttons\r\nEd Nisley - KE4ZNU - December 2012\r\n"); MillisThen = millis(); } //------------------ // Run the test loop void loop() { MillisNow = millis(); if ((MillisNow - MillisThen) < UPDATEMS) { digitalWrite(PIN_SYNC,HIGH); Button = ReadButtons(PIN_BUTTONS); if (PrevButton != Button) { if (Button == N_BUTTONS) { printf("Button %d released\n",PrevButton); } else printf("Button %d pressed\n",Button); PrevButton = Button; } if (PrevKnobCounter != KnobCounter) { printf("Knob count: %d\n",KnobCounter); PrevKnobCounter = KnobCounter; } digitalWrite(PIN_SYNC,LOW); MillisThen = MillisNow; } }
2 thoughts on “Arduino Snippets: Analog Button Input”
Comments are closed.