example of using one analog pin to read 5 switches

Before I found that I could use the analog pins as digital I/O using pin numbers 14-19 and digitalRead(num), I figured I’d use a series resistor circuit and one analog input. Figuring it might be of interest to someone, I’m posting the code here:

// OneAnalogPin5switches
// Doug LaRue
// November 2008
//
// In some cases when you are running out of digial I/O pins for switches
// and want a simple solution for adding many switches with a small amount
// of added software. This example shows how 5 switches can be identified
// using 5 .5K ohm resistors and one analog pin. It's mostly only important
// that the resistor values are large enough so that there isn't too much
// current through them but enough to have a reliable and stable voltage
// divider circuit. I used .5K resistors because I had them close by but
// 1K, 2.2K, and 4.7K are very common and should work fine.
// If DEBUG_ON is defined, this code will set a pulse width value on the
// ouput digial/PWM pin to dim an attached LED based on the switch position.
// It also puts out strings on the serial port based on what switch is set.
//
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0


#define ERROR_WINDOW 50  // +/- this value
#define BUTTONDELAY 20
#define DEBUG_ON

int ledPin = 9;      // LED connected to digital pin 9
int analogPin = 3;   // switch circuit input connected to analog pin 3
long buttonLastChecked = 0; // variable to limit the button getting checked every cycle

void setup()
{
  pinMode(ledPin, OUTPUT);   // sets the pin as output on a PWM capable pin
  Serial.begin(115200);
}

void loop()
{
  if( buttonLastChecked == 0 ) // see if this is the first time checking the buttons
    buttonLastChecked = millis()+BUTTONDELAY;  // force a check this cycle
  if( millis() - buttonLastChecked > BUTTONDELAY ) { // make sure a reasonable delay passed
    if( int buttNum = buttonPushed(analogPin) ) {
      Serial.print("Button "); Serial.print(buttNum); Serial.println(" was pushed.");  
    }
    buttonLastChecked = millis(); // reset the lastChecked value
  }
}

/* 
 * Read 5( or more or less ) buttons using one analog pin and an equal number of
 * resistors configured in series across +5 and ground.
 * The concept is to create a voltage divider and the switches connect to one
 * analog intput pin the voltage at some fixed position in the series circuit.
 * The software just checks within a wide range of values and returns a button
 * number if a button was determined to have been pushed.
 *
 * The internal 20K resistor is enabled so we can't just divide the 1023 by the
 * number of resistors to get the range for each switch. With the 20K int R, the
 * circuit looks like intR is in parallel with the switch resistors above the
 * switch pushed. For example, SW2 would be 20K in parallel with 1K resulting in
 * an equivalent R of .566K ohms. This then looks almost like there is only 4
 * resistors in series and 1023 / 4 = ~255 and 1023-255=~767. Someone could probably
 * spend the time to calculate this but I just used imperical measurements and a
 * +/- 50 step range.
 *
 *
 *   analogPin                 +5 V
 *      |                         |
 *      |                         \
 *      ----------------          /   
 *                     |          \    .5K
 *                     |          /
 *                     |          \
 *                     |____ \____|
 *                     |   SW1    |
 *                     |          \
 *                     |          /   
 *                     |          \    .5K
 *                     |          /
 *                     |          \
 *                     |____ \____|
 *                     |   SW2    |
 *                     |          |
 *                     |          \
 *                     |          /   
 *                     |          \    .5K
 *                     |          /
 *                     |          \
 *                     |____ \____|
 *                     |   SW3    |
 *                     |          |
 *                     |          \
 *                     |          /   
 *                     |          \    .5K
 *                     |          /
 *                     |          \
 *                     |____ \____|
 *                     |   SW4    |
 *                     |          |
 *                     |          \
 *                     |          /   
 *                     |          \    .5K
 *                     |          /
 *                     |          \
 *                     |          |
 *                     |____ \____|
 *                         SW5    |
 *                                |
 *                                |
 *                              _____   
 *                               ___     ground
 *                                _
 *
 */

int buttonPushed(int pinNum) {
  int val = 0;         // variable to store the read value
    digitalWrite((14+pinNum), HIGH); // enable the 20k internal pullup
    val = analogRead(pinNum);   // read the input pin
    
    #ifdef DEBUG_ON
      Serial.println(val);
      analogWrite(ledPin, val/4); // analog input 0-1023 while analogWrite 0-255
    #endif
    // we don't use the upper position because that is the same as the
    // all-open switch value when the internal 20K ohm pullup is enabled.
    //if( val >= 923 and val <= 1023 )
    //  Serial.println("switch 0 pressed/triggered");
    if( val >= 780 and val <= 880 ) {  // 830
      #ifdef DEBUG_ON
      Serial.println("switch 1 pressed/triggered");
      #endif
      return 1;
    }
    else if ( val >= (630-ERROR_WINDOW) and val <= (630+ERROR_WINDOW) ) { // 630
      #ifdef DEBUG_ON
      Serial.println("switch 2 pressed/triggered");
      #endif
      return 2;
    }
    else if ( val >= (430-ERROR_WINDOW) and val <= (430+ERROR_WINDOW) ) { // 430
      #ifdef DEBUG_ON
      Serial.println("switch 3 pressed/triggered");
      #endif
      return 3;
    }
    else if ( val >= (230-ERROR_WINDOW) and val <= (230+ERROR_WINDOW) ) { // 230
      #ifdef DEBUG_ON
      Serial.println("switch 4 pressed/triggered");
      #endif
      return 4;
    }
    else if( val >= 0 and val <= (20+ERROR_WINDOW) )  {
      #ifdef DEBUG_ON
      Serial.println("switch 5 pressed/triggered");    
      #endif
      return 5;
    }
    else
      return 0;  // no button found to have been pushed
}

Doug

Thanks for the details-- just when I needed two extra inputs! Quite clever...:)

Played around with this for a little while, and while I got consistent values when buttons were pressed, the value varied wildly when none was pressed.

Connecting the analog pin to ground via another resistor solved the problem-- the analog pin reads 0 when no button is down, and only varies by +/- 2 when a button is pressed! I'll use this for the four inputs for my current project, which give me /just/ enough pins not to have to resort to a shift register....;)

So, it seems this could be used for more than five buttons, though it would get tricky figuring out the values when multiple buttons are pressed.

In my circuit, these are the readings I get: No buttons: 0 button 1: 693 button 2: 485 button 3: 326 button 4: 175

Schematic:

/* * * analogPin +5 V * | | * | \ * ---------------- / * | \ 1K * | / * | \ * |____ _| * | SW1 | * | \ * | / * | \ 1K * | / * | \ * |_ _| * | SW2 | * | | * | \ * | / * | \ 1K * | / * | \ * |_ _| * | SW3 | * | | * | \ * | / * | \ 1K * | / * | \ * |_ ___| * | SW4 | * | | * | \ * | / * | \ 1K * | / * | \ * | | * |/\/\/_| * 4.7K | * | * | * _____ * ___ ground * _ * */

you shouldn't need that pulldown if you turn on the internal pull up resistor.

Make sure you have this line in your code as I did in the buttonPushed() method. With this, you should read full scale(1023) with no switches pushed.

digitalWrite((14+pinNum), HIGH); // enable the 20k internal pullup

Note: if this pull up resistor has been blown or failed, you'd have to do a pull down or pull up to keep it from floating.

Excellent-- will do! Thanks!!

Thanks for the info. I'm trying to get a rotary input for a project im working on and managed to get hold of a 4 wire (16 position, 0000 to 1111) rotary switch.

Obviously with this I will need to be able to read all 16 states. I have tried playing around with everything but I cannot get it to come out with 16 significantly unique values (there are 2 replications using 1k dividers and 4.7k down), but as I'm using a rotary switch i will only need to check the values to the left and right of the last known value.

Thanks for the help!

if you can’t resolve the switch voltages over 16 positions and 5V, then one option might be to use 2 analog inputs instead of one. You’ll have to test both pins but atleast there should be enough range between each switch position to get positive readings.

The problem with the switch i was using is that it has a single common pole for all four switches (in one package). Of cause I overlooked the obvious, in that I only really needed to use the two least significant bits of the input to do what I was trying to do (it's a rotary switch that loops over from 1111 to 0000, so I can get everything I need from 0000 to 0011).

You need a different type of D/A converter than the one in the diagram above. You can use a resistor off each switch output with the values R 2R 4R & 8R where R can be anything (say 1K). You can't actually get these exact values so you will have to make some of them up with two resistors. For example 4K can be two 2Ks in series and 8 K could be a 4K7 and a 3K3 all using 1% resistors. Connect the common of the switch up to 5V and have a 1K pull down resistor.

I understand how this works BUT if 2 or more switches were pressed then the software would not operate correctly.

Microchip had a Design guide on this subject. If the resistor values are different then it is possible to detect the combination of keys pressed.

I have just arrived at the point in time when I need to do this (run out of digital I/P and can't afford a Mega) so I will try to get this working.

Matt ;D

So simple yet so useful, I love it! I will definetly be using this for my next project that involves buttons. No need for multiplexing or any shift registers and stuff like that.

EDIT: One noob question: What resistor values should i use for 9 buttons?

Nicely presented, cheers - it helped me to see how you had done what I wanted to do.

I just built a 14 step ladder using 1k resistors connected to a stylophone-like keyboard. Decoding the input is dead easy and with 13 'keys' (one octave) there's still plenty of margin.

Decoding the input is dead easy and with 13 ‘keys’ (one octave) there’s still plenty of margin.

Remember that you will only be able to detect if one button is pressed with a ladder set up. If two or more are pressed then the results will be ambiguous.

That’s certainly true, multiple presses won’t work with same-valued resistors. This is not a problem with a stylophone where you can only ‘press’ one key at a time.

The solution should otherwise be to use power-of-two values, right, such as:
500R, 1k, 2k, 4k, 8k…
so that they can be added up in any combination -
is that right, and does it work? I guess with a bit of breadboarding I could find out…

is that right, and does it work?

Yes that will work but you can't do as many bits. There is only a 10 bit A/D on the arduino so you might think you could do it for 10 switches, but resistor tolerances and noise will limit you to about five or six switches only.

This is really great, simple but very useful, I can use this for a digital joystick switch. To read the value, can we then use arduino "map(analogRead(pinNum), 0, 1023, 0, 4)" function, instead of testing the range value one by one? Tks.

If you'd like to learn more about this technique, Google the Microchip AN234 application note.

slight mod to a more conforming traditional r/2r ladder:

Question: How can you mod this to use transistor switches for input?

I have Hall effect sensors that need to be amplified and inverted before they can be added to the ladder... but the transistors always play with the analog voltage measured.

/*
 *                                                 _
 *     +5 V                                       ___
 *      |                                        _____  ground
 *      |                                          \
 *      ----------------                           /
 *                     |                           \    330
 *                     |                           /
 *                     |                           \
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW1                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW2                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW3                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW4                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |                           |
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW5                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |                           |
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                     |   SW6                     |
 *                     |                           \
 *                     |                           /  
 *                     |                           \    150
 *                     |                           /
 *                     |                           \
 *                     |                           |
 *                     |____ \____/\/\/\ 330 /\/\/\|
 *                         SW7                     |
 *                                                 \
 *                                                 /  
 *                                                 \    150
 *                                                 /
 *                                                 \
 *                                                 |
 *                          |-----/\/\/\ 330 /\/\/\|
 *                        _____                    | 
 *                         ___   ground            | analog pin or LM358
 *                          _
 */

Ok simple mod to get transistors to work as the switch without a resistive influence... Stick a 2n2222 in the middle of the switch apply the external source (5v) to the Base and attach the emitter to the R2R ladder and then supply an additional 5v source to all Collectors.

I now have a 7 input DAC that accepts discrete multiple inputs. Any ideas on the math function to use to decode. I know the basic formula would be (r1 * r2 * r3...)/(r1 + r2 + r3...) = multi input value

But what can be the most effective method of taking an analog input and decoding back to the closest digital inputs given? Binary operation?

Any help would be great. Thanks.

  • _
  • +5 V ___
  • | _____ ground
  • | \
  • ---------------- /
  • | \ 330
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW1 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW2 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW3 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW4 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW5 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • | SW6 |
  • | \
  • | /
  • | \ 150
  • +5 V /C /
  • input ---B| \
  • \E---------/\/\/\ 330 /\/\/|
  • SW7 |
  • \
  • /
  • \ 150
  • /
  • \
  • |
  • |-----/\/\/\ 330 /\/\/|
  • _____ |
  • ___ ground | analog pin or LM358 OpAmp Follower
  • _