Go Down

Topic: example of using one analog pin to read 5 switches (Read 25828 times) previous topic - next topic

Dougl

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:

Code: [Select]

// 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

salsaman

Thanks for the details-- just when I needed two extra inputs!  Quite clever...:)
My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

salsaman

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:
[font=Courier]
/*
*
*   analogPin                 +5 V
*      |                         |
*      |                         \
*      ----------------          /  
*                     |          \  1K
*                     |          /
*                     |          \
*                     |____ \____|
*                     |   SW1    |
*                     |          \
*                     |          /  
*                     |          \  1K
*                     |          /
*                     |          \
*                     |____ \____|
*                     |   SW2    |
*                     |          |
*                     |          \
*                     |          /  
*                     |          \  1K
*                     |          /
*                     |          \
*                     |____ \____|
*                     |   SW3    |
*                     |          |
*                     |          \
*                     |          /  
*                     |          \  1K
*                     |          /
*                     |          \
*                     |____ \____|
*                     |   SW4    |
*                     |          |
*                     |          \
*                     |          /  
*                     |          \  1K
*                     |          /
*                     |          \
*                     |          |
*                     |__/\/\/\__|
*                         4.7K   |
*                                |
*                                |
*                              _____  
*                               ___     ground
*                                _
*
*/
[/font]
My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

Dougl

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.

Code: [Select]

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.

salsaman

My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

TalkingJazz

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!

Dougl

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.

TalkingJazz

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).

Grumpy_Mike

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.

Chewie Baker

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

jamieriddles

#10
Aug 02, 2009, 11:23 pm Last Edit: Aug 02, 2009, 11:47 pm by jamieriddles Reason: 1
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?

marser

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.

Grumpy_Mike

Quote
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.

marser

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...

Grumpy_Mike

Quote
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.

Go Up