Very few wires for a numeric keypad for Arduino
Giovanni Carrera, 23/11/2018
The techniques for reading numerical keyboards
with a reduced number of Arduino pins as analog inputs are described.
Introduction
The aim of this study is to drastically reduce
the number of pins required by a numeric keypad. This is because we often need
many I/O pins compared to those available on the Arduino Uno or Nano boards.
With the help of some resistor you can turn the
keypad to use only a few analog inputs, saving several digital pins. In some
cases, as we will see, just one analogue input will suffice. Programs to manage
them are also described.
Matrix keyboards
The ordinary numerical keypads are structured
in matrix because they require less wires and are organized in rows by columns.
Usually the lines are configured as input and the columns as output. The
program scans the array to see if a key has been pressed. A 4x3 keypad, like
the membrane one shown in the figure, requires 7 Arduino digital pins. Numerous
libraries are available on the net to use it with Arduino.
There are also several keyboard applications
with one analogue output, but in my
opinion they are very unreliable for this type of keyboards. To simulate them I
created a template and a spreadsheet program to try out the functionality. The
following figure shows the scheme and the equivalent model of a 4x3 keyboard
with an analogue output.
The biggest problem is that the range of
variation is very limited and, consequently, also the intervals corresponding
to each key. It is also difficult to distinguish the keys because the intervals
may overlap in some cases. Having written the analysis program, I verified that
the values they had chosen in some projects found on the network had these
drawbacks, with minimum intervals of only 10mV.
My solution
The solution I propose is much better even if
it uses three analog inputs, with a circuit like the one shown in the following
figure.
In this case you save only 4 pins against six
of the previous version but it's worth it because the intervals are now only
four, in addition to the rest position that is the zero volts (no key pressed).
In the equivalent scheme (b) the R1e indicates one of the four resistors R1-R4,
while R2e is one of the three resistors R5-R7.
Now let's see how to make the four widest
possible intervals. First of all the resistors R2e = R5 = R6 = R7 can be made
equal, then we can set R1 = 0 to have the outputs corresponding to the first
line at 5V. With the values shown in the table:
R1 =
|
0 W
|
R2 =
|
330 W
|
R3 =
|
1 kW
|
R4 =
|
3 kW
|
R5,R6,R7 =
|
1 kW
|
Vcc =
|
5.000
|
The following intervals are obtained for each
output:
Key
|
Vx [V]
|
DV [V]
|
NADC
|
DN
|
NADC-100
|
NADC+100
|
[1]-[2]-[3]
|
5.00
|
0.000
|
1023
|
0
|
923
|
1023
|
[4]-[5]-[6]
|
3.76
|
1.241
|
769
|
254
|
669
|
869
|
[7]-[8]-[9]
|
2.50
|
1.259
|
512
|
257
|
412
|
612
|
[*]-[0]-[#]
|
1.25
|
1.250
|
256
|
256
|
156
|
356
|
As you can see, the intervals on the three
outputs are the largest possible and you can use six standard resistors with a
tolerance of ±5%. With an extra analogue input and another resistor, a 4x4
keyboard can be used. The following figure shows the connections with Arduino.
In the diagram the resistor R1 is connected to
line 2 because the one on line 1 has drawn it with a wire, so the resistor
references have been scaled by one. Pin assignments can be modified according
to needs, as long as they are pin configurable as analog.
Of course, if Arduino were powered at 3.3V,
nothing would change because the ADC converter as default uses the supply
voltage and the ADC numbers don’t change.
To test the test program, not having a keyboard
of this type, I built it with recycled keys, the figure below shows my
prototype. The 5-pin right connector is used to connect it to the Arduino.
The Analog4x3Keyb.ino program
I reported the lower (NADCm100) and higher
(NADCp100) limits on two numeric arrays and one character array for the 12
keys, arranged in columns. As you can see, in my keypad I used two different
keys because they were more useful for my applications than the standard '*'
and '#' keys that are more suitable for telephone applications.
To scan the three analogue channels, I
initially used an index loop, but it didn’t work, so I used the KeyScan
function. Double conversion is necessary to avoid the bounce of the buttons.
For applications it is better to turn everything into a function.
I consider a button pressed if the value of one
of the three outputs is greater than zero, for safety I put the value 40. The
program first verifies on which column the key was pressed, then identifies the
key itself.
/*
program Analog4x3Keyb
* test
for 4x3 keys keyboard with 3 analog outs
* G.
Carrera - 2/11/2018
*/
int
ledPin = 13; // internal LED
//
limits of keyboard output values:
const
int NADCm100[4] = {923,669,412,156};
const
int NADCp100[4] = {1023,869,612,356};
const
char key[13] = {'1','4','7','C','2','5','8','0','3','6','9','E'};
int
keyval[3];
int
i,colp,val;
void
setup(){
pinMode(ledPin, OUTPUT);
Serial.begin(9600); // used with serial
monitor
digitalWrite(ledPin, LOW);// led off
}
void
loop() {
KeyScan();// read analog keyboard
if (keyval[0]+keyval[1]+keyval[2] > 40){
// a key was pressed
delay(10);// antibounce
KeyScan();// reread analog keyboard
for (i=0; i < 3; i++){//identify which
column it belongs to
if (keyval[i] > 40){
colp= i;
val= keyval[i];// this is the
corresponding value
for (int j=0; j < 4; j++){// identify
which key was pressed on the column
if (val >= NADCm100[j] &&
keyval <= NADCp100[j]){
digitalWrite(ledPin, HIGH);// led on
Serial.print("col= ");
Serial.print(colp);
Serial.print(", val= ");
Serial.print(val);
Serial.print(", key= ");
Serial.println(key[colp*4+j]);
delay(500);
digitalWrite(ledPin, LOW);// led off
break;
}
}
}
}
}
}
void
KeyScan(){// read analog keyboard
keyval[0]= analogRead(A0);
delay(1);
keyval[1]= analogRead(A1);
delay(1);
keyval[2]= analogRead(A2);
delay(1);
}
Keyboards with
single common
I had a strange little keyboard organized as
12x1. After thinking a little bit about it, the simplest solution was to make a
voltage divider and read the output voltage with an analog input.
I realized the circuit illustrated in the
following figure, as you can see, only three wires connect it with Arduino.
Naturally, this system can be easily adapted to even fewer keys, I do not
recommend using too many keys because the intervals become too small. The
keyboards organized in matrix, for example 3x4 or 4x4 have great difficulty for
the analogue output, as already seen.
How to
calculate the voltage ranges
To calculate the limits of the values used to
identify the keys, I wrote a program in Excel.
The following figure shows the equivalent
circuits of the divider (a) and the particular case of the keyboard (b).
In the case of the divider of figure (a), the
output voltage Vout is:
Vout = Vin* R2p/(R1p+R2p)
If in our resistive network we press the key
[2], we can refer to the previous divider formula if we put: R1p = 2*R
R2p = 10*R*Rz/(10*R+Rz)
In fact, the series of resistors of the low
branch is in parallel with Rz. The resistor Rz (R13 in the diagram) serves to
bring the input voltage to zero, ie it acts as an analog pull-down.
Using these formulas for each key,
appropriately adapted, the following table is obtained.
Key
|
Vo [V]
|
DV [V]
|
R2p [kW]
|
R1p [kW]
|
NADC
|
DN
|
NADC-20
|
NADC+20
|
[0]
|
5.00
|
0.000
|
4.98
|
0
|
1023
|
0
|
1003
|
1023
|
[1]
|
4.54
|
0.458
|
4.60
|
0.464
|
929
|
94
|
909
|
949
|
[2]
|
4.10
|
0.443
|
4.22
|
0.928
|
839
|
90
|
819
|
859
|
[3]
|
3.67
|
0.431
|
3.84
|
1.392
|
751
|
88
|
731
|
771
|
[4]
|
3.25
|
0.421
|
3.44
|
1.856
|
665
|
86
|
645
|
685
|
[5]
|
2.84
|
0.413
|
3.04
|
2.32
|
580
|
85
|
560
|
600
|
[6]
|
2.43
|
0.407
|
2.63
|
2.784
|
497
|
83
|
477
|
517
|
[7]
|
2.03
|
0.403
|
2.21
|
3.248
|
414
|
83
|
394
|
434
|
[8]
|
1.62
|
0.401
|
1.79
|
3.712
|
332
|
82
|
312
|
352
|
[9]
|
1.22
|
0.401
|
1.35
|
4.176
|
250
|
82
|
230
|
270
|
[*]
|
0.82
|
0.403
|
0.91
|
4.64
|
168
|
82
|
148
|
188
|
[#]
|
0.41
|
0.407
|
0.46
|
5.104
|
84
|
84
|
64
|
104
|
The NADC number is what can be read from the
Arduino ADC converter (10 bit). The numerical interval DN between one key and the other goes
from 84 to 94, even though all the resistors are equal, in my case R = 464 W, these intervals are slightly
different towards the high values because of R13. For safety I chose the limits
within ± 20, obtaining the NADC-20 and NADC + 20 values.
The experimental measurements on the prototype
gave values very close to those calculated. The following figure shows my
prototype detached from the keyboard for visibility reasons. It connects to the
keyboard with a 13-pin female strip and a second 3-pin connector connects it to
the Arduino.
Components list
·
1
x keypad 12x1 keys or 12 single keys connected to a common
·
12
x resistors R1..R12 = 470 W ±1%, in my case I used 464 W.
·
1
x resistor R13= 47 kW ±5%.
·
1
x 100 nF capacitor.
·
strip
connectors
The Analog12x1Keyb.ino
program
The program required some attention as regards
the bounce phenomena on the keys. The pressed key leads to values higher than
zero + possible noise, even here I have placed 40 as a limit. In the program I
created two numeric arrays with the calculated limits and a third of ascii
characters to assign the key. I only wrote a system test program and tested it
on an Arduino Uno.
/* program Analog12x1Keyb
* test for analog 12x1 keys keyboard
* G. Carrera - 27/10/2018
*/
int keybPin = A0;// keyboard analog input
int ledPin = 13; // internal LED
// limits of keyboard output values:
const int NADCm20[12] =
{1003,909,819,731,645,560,477,394,312,230,148,64};
const int NADCp20[12] =
{1023,949,859,771,685,600,517,434,352,270,188,104};
const char key[13] =
{'0','1','2','3','4','5','6','7','8','9','*','#'};
void setup(){
pinMode(ledPin,
OUTPUT);
Serial.begin(9600); // used with serial
monitor
digitalWrite(ledPin, LOW);// led off
}
void loop() {
int val =
analogRead(keybPin);// read analog keyboard
if (val >
40){ // a key was pressed
delay(10);//
antibounce
val =
analogRead(keybPin);// re-read
for (int
i=0; i < 12; i++){
if (val
>= NADCm20[i] && val <= NADCp20[i]){// the key[i] was pressed
digitalWrite(ledPin, HIGH);// led on
Serial.print(val);
Serial.print(" , ");
Serial.println(key[i]);
delay(500);
digitalWrite(ledPin, LOW);// led off
break;
}
}
}
}
With a few modifications you can create a function
that, if called, returns the character and if no key has been pressed returns a
thirteenth character to indicate this state, for example 'N'.
This solution is particularly suitable in cases
where you want to make a small custom keyboard, so we can organize it as we see
fit. Another useful application is for 8-pin microcontrollers like ATtiny that
have very few I/O pins.
Keyboards with a few keys
With a few buttons, such as 4x1, you can also
use a different configuration, as shown in the following figure. The table is
the result of optimization with Excel with the standard values: R1 = 470W, R2 = 1 kW, R3 = 3 kW and R4 = 1.5 kW.
With this circuit a resistor is saved, but the
values of the upper branch of the divider are not all the same as in the
circuit discussed above. To have equal intervals must be:
R3 = 2 * R4, R2 = R3 / 3 and R1 = R2 / 2
But it is difficult to use all standard values.
The ranges are wide and the resistors can have a tolerance of ±5%. The standard
3 kW resistor can be obtained with two
1.5 kW resistors in series or a 3.3 kW resistor in parallel with a 33 kW one.
The Analog4x1Keyb.ino
program
/* program Analog4x1Keyb
* test for 4x1 keys keyboard with 3 analog outs
* G. Carrera - 8/11/2018
*/
int ledPin = 13; // internal LED
int keybPin = A0;// keyboard analog input
// limits of keyboard output values:
const int NADC_L[4] = {907,646,393,118};
const int NADC_H[4] = {1023,897,636,383};
const char key[5] = {'1','2','3','4'};
int i,keyval;
void setup(){
pinMode(ledPin,
OUTPUT);
Serial.begin(9600); // used with serial
monitor
digitalWrite(ledPin, LOW);// led off
}
void loop() {
if
(analogRead(keybPin) > 40){ // a key was pressed
delay(10);//
antibounce
keyval= analogRead(keybPin);// reread analog
keyboard
if (keyval
> 40){
for (int
i=0; i < 4; i++){// identify which key was pressed on the column
if
(keyval >= NADC_L[i] && keyval <= NADC_H[i]){
digitalWrite(ledPin, HIGH);// led on
Serial.print("val= "); Serial.print(keyval);
Serial.print(", key= "); Serial.println(key[i]);
delay(500);
digitalWrite(ledPin, LOW);// led off
break;
}
}
}
}
}