Introduction: 5 WAY Switch Interfacing

This instructable shows how to interface a 5 way directional switch to a single ADC pin on a microcontroller (e.g. arduino, attiny85, esp8266, esp32).

The idea of using an ADC pin to replace multiple digital inputs has been covered before but most of these only allow one switch to be active. The 5 way switch I am dealing with here has one common input and 5 outputs corresponding to left, right, up, down and centre press. On their own these movements would connect the input to one output. However, it is also possible to move to the corners which then connects the input to 2 of the outputs, e.g. top/left. This can be useful in allowing diagonal movements indications.

The basic technique is to use carefully selected resistor values so that the possible combinations of resistance remain well separated and can be measured and decoded with a reasonable ADC.

Step 1: Scheme and Values

The schematic shows the hook up where a potential divider is formed of a common 1K resistor and either single or potential pairs of resistors when the switch is activated. Not all pairs are possible, only diagonal combinations which simplifies it somewhat. Also the centre push down switch is normally used as an action switch independent of any direction so is arranged to give a unique value independent of any other switch movement.

The table show the 10 possible switch combinations (4 sides, 4 corners, centre down, and all off). For the resistor values shown this is then used to compute the equivalent resistance and an ADC value assuming a 10 bit converter and a reference that is the same as the voltage feeding the divider. This would be the default case where say a 5V arduino or ATTiny is used.

The resistor values chosen give a minimum difference between ADC values of 34 allowing for reliable discrimination.

Note that for ESP type controllers the analog reference is internal not supply referenced which makes it a bit trickier. The common top resistor can be increased to allow for this. As the reference is not necessarily well controlled the overall division might need to be calibrated either by using a potentiometer for the common resistance or by adjusting values in the software.

Keen observers will note that the physical resistors used in the image do not match the values in the schematic. I have used SMD resistors where I only have a limited range of values and I have used 2 resistors in series or parallel to closely approximate the values needed (8k2 ~ 10K || 47K, 3.9K ~ 4k7 ||22K, 1K2 ~ 1K + 220R).

Step 2: Software

The following simple sketch illustrates how to decode the ADC values into a representation of the switch.

switches is a byte value where the 5 LSB bits represent the 5 switch inputs.

/*
	R. J. Tidey 2020/07/27
	5 way switch input on one ADC pin
	Feed resistor 1K
	Pull down resistors Left 8k2, Right 3K9, Up 2k2, Down 1k2, Centre 0K
	Centre ADC values CE 0, BR 490, LD 524, BC 559, TR 598, LU 650, TC 704, RC 815, LC 912, Off 1023
 */

#define SWITCH_ADC A0
#define ADC_STABLE 5
#define SWITCH_CENTRE 0x10
#define SWITCH_LEFT   0x8
#define SWITCH_RIGHT  0x4
#define SWITCH_UP     0x2
#define SWITCH_LEFT   0x1
int thresholds[10] = {968,864,760,677,624,579,541,507,409,-2000};
uint8_t switchmask[10] = {0x0,0x8,0x4,0x2,0xa,0x6,0x1,0x9,0x5,0x10};
uint8_t switches;
int lastADC = 2000;


void getSwitches() {
	int i;
	uint8_t switchVal;
	int s = analogRead(SWITCH_ADC);
	int diff = lastADC - s;
	if (diff < 0) diff = -diff;
	lastADC = s;//only process ADC stable values
	if(diff < ADC_STABLE) {
		for(i = 0; i < 9; i++) {
			if(s>thresholds[i]) {
				break;
			}
		}
		switches = switchmask[i];
	}
}

void setLeds(uint8_t leds) {
	PORTB = leds;
}

void setup(){
	int i;
	for(i = 0; i < 5; i++) {
		pinMode(i, OUTPUT);
	}
	analogReference(DEFAULT);
}

void loop(){
	getSwitches();
	setLeds(switches);
	delay(200);
}<br>

The getSwitches function performs the basic decoding using the thresholds array to separate the ADC value into locating which switches are pressed. The switchmask then produces the equivalent bits indicating the switches pressed.

The getSwitches only does a decode if two successive ADC values have a similar value. This avoids potential spurious decodes when the analog value is changing.

The example sketch just sends the decoded mask out to port B which can be used to drive 5 LEDS and indicate the switch functionality.