# Connecting a keypad to an AVR

## 1. How a keypad works

Keypads are switches, that are connected to rows and columns. If key "1" is pressed, the column 1 is electrically connected to row 1, if key "2" is pressed column 2 with row 1, and so on ...
To find out, if any one of the 12 keys is pressed, it would only be necessary to tie the three column lines to ground, connect all four row lines and, via a resistor of 10 kΩ, pull them to the operating voltage. If no key is pressed, the row lines are at plus. Any pressed key pulls down the row lines to zero volts.
To detect, which one of the 12 keys is pressed, pull down the three column lines one by one to ground (the other two column lines to plus) and read the resulting four row-lines. If one of the four row-lines is low, stop further reading and identify the key code from the column and row info. Like that:
ColumnRowKey
Col1Col2Col3Row1Row2 Row3Row4CharacterBinary code
0001111 (none)1111
0110111 10001
1010111 20010
1100111 30011
0111011 40100
1011011 50101
1101011 60110
0111101 70111
1011101 81000
1101101 91001
0111110 *1010
1011110 00000
1101110 #1011

Reading such a keypad using digital logic components, you need at least:
• an oscillator, a shift register and a start/stop gate for generating the column signals,
• detection if one of the four row signals is zero,
• a recoder for conversion of the seven signals to the keycode.
Or a complete IC doing all that (probably you won't get such an IC at your local electronic parts dealer). Or you better use a micro.

To top of page

## 2. AVR: I/O-connection of the matrix

A keypad matrix can be connected directly to an I/O port of an AVR, with no additional components.
The example shows a connection to the lower seven I/O pins of Port B. Other ports can be used similiarly.
The port pins PB4..PB6 are defined as outputs, they provide the column signals. The port pins PB0..PB3 are used to read in the row results. Pull-up resistors on these inputs are enabled by software, external resistors are not necessary.

The following software example first demonstrates initialization of the ports. This software part has to be excecuted only once at the AVR's program start.

### Init-routine

``````
;
;
.DEF rmp = R16 ; define a multipurpose register
; define ports
.EQU pKeyOut = PORTB ; Output and Pull-Up-Port
.EQU pKeyDdr = DDRB  ; data direction register of the port
; Init-routine
InitKey:
ldi rmp,0b01110000 ; data direction register column lines output
out pKeyDdr,rmp    ; set direction register
ldi rmp,0b00001111 ; Pull-Up-Resistors to lower four port pins
out pKeyOut,rmp    ; to output port
``````

### Check for any key pressed

The following routine detects if any one of the 12 keys is pressed. This routine is called in intervals, e.g. in a delay loop or by use of a timer.
``````
;
; Check any key pressed
;
AnyKey:
ldi rmp,0b00001111 ; PB4..PB6=Null, pull-Up-resistors to input lines
out pKeyOut,rmp    ; of port pins PB0..PB3
in rmp,pKeyInp     ; read key results
ori rmp,0b11110000 ; mask all upper bits with a one
cpi rmp,0b11111111 ; all bits = One?
breq NoKey         ; yes, no key is pressed
``````

### Identify the key pressed

Now the keypad is read out. One after the other the port bits PB6, PB5 and PB4 are set to low, and PB0..PB3 are checked for zeros. The register pair Z (ZH:ZL) points to a table with the key codes. When leaving the routine, this pair points to the key code of the pressed key. By using the LPM instruction, the key code is read from the table in the flash memory to the register R0.
``````
;
; Identify the key pressed
;
ldi ZH,HIGH(2*KeyTable) ; Z is pointer to key code table
ldi ZL,LOW(2*KeyTable)
ldi rmp,0b00111111 ; PB6 = 0
out pKeyOut,rmp
in rmp,pKeyInp ; read input line
ori rmp,0b11110000 ; mask upper bits
cpi rmp,0b11111111 ; a key in this column pressed?
brne KeyRowFound ; key found
ldi rmp,0b01011111 ; PB5 = 0
out pKeyOut,rmp
in rmp,pKeyInp ; read again input line
ori rmp,0b11110000 ; mask upper bits
cpi rmp,0b11111111 ; a key in this column?
brne KeyRowFound ; column found
ldi rmp,0b01101111 ; PB4 = 0
out pKeyOut,rmp
in rmp,pKeyInp ; read last line
ori rmp,0b11110000 ; mask upper bits
cpi rmp,0b11111111 ; a key in this column?
breq NoKey ; unexpected: no key in this column pressed
KeyRowFound: ; column identified, now identify row
lsr rmp ; shift a logic 0 in left, bit 0 to carry
brcc KeyFound ; a zero rolled out, key is found
adiw ZL,1 ; point to next key code of that column
rjmp KeyRowFound ; repeat shift
KeyFound: ; pressed key is found
lpm ; read key code to R0
rjmp KeyProc ; countinue key processing
NoKey:
rjmp NoKeyPressed ; no key pressed
;
; Table for code conversion
;
KeyTable:
.DB 0x0A,0x07,0x04,0x01 ; First column, keys *, 7, 4 und 1
.DB 0x00,0x08,0x05,0x02 ; second column, keys 0, 8, 5 und 2
.DB 0x0B,0x09,0x06,0x03 ; third column, keys #, 9, 6 und 3
``````

### Debouncing

The routines KeyProc and NoKeyPressed have to debounce the pressed key. For example by counting a counter up whenever the same key is identified. Repeat this for e.g. 50 milliseconds. The NoKeyPressed routine clears the counter and the key pressed. Because the timing depends on other necessary timing requirements of the AVR program, it is not shown here.

The routines shown above do not leave more time between the setting of the column address and the reading of the row information. At high clock frequencies and/or longer connections between keypad and processor it is necessary to leave more time between write and read (e.g. by inserting NOP instructions).
The internal pull-ups resistors have values around 50 kΩ. Long lines or a noisy environment might interfere and produce glitches. If you like it less sensitive, add four external pull-ups.

A disadvantage of the circuit is that it requires seven port lines exclusively. The modification with an AD converter and a resistor network (see chapter 3) is more economic and saves port lines.

To the top of that page

## 3. Connection to an ADC with a resistor matrix

Most of the Tiny and Mega-AVR devices nowadays have an AD converter on board. Without additional external hardware these are capable of measuring analog voltages and resolve these with 10 bit resolution. Those who want to save I/O ports only have to get the keypad to produce an analog voltage. That's the task for a resistor matrix.

Hint: An improved version of this text with more examples, a commandline software tool etc. is available
here! A graphical software tool for studying different scheme versions and sizes can be found here.

### Resistor matrix

This is such a resistor matrix. The columns are connected to ground, in between the column connections are three stacked resistors. The rows are connected via four such stacked resistors to the operating voltage (e.g. 5 V). The AD converter input is blocked by a condensator of 1 nF because the ADC doesn't like high frequencies, that could be caught by the keys, the resistors and the more or less long lines in between all this.
If the key "5" is pressed, a voltage divider gets active:

* 1 k + 820 Ω = 1,82k to ground,
* 3,3 k + 680 Ω + 180 Ω = 4,16k to plus.

At an operating voltage of 5 Volt a divided voltage of

5 * 1,82 / (1,82 + 4,16) = 1,522 Volt

is seen on the AD converter input. If we consider 5% tolerance of the resistors, the resulting voltage is somewhere between 1,468 and 1,627 Volts. The 10-bit AD converter converts this (at 5 V reference voltage) to a value between 300 and 333. If we ignore the lower two bits of the result (e.g. divide the AD result by four or left-adjusting the result - if the ADC provides that function) this yields an 8-bit-value between 74 and 78.
Each key pressed produces a typical voltage range, to be converted to the key code.

### Key encoding table

The matrix uses resistors of the E12 row, which are available at every electronic store. That is why the resistor values (e.g. 820 or 3300) are a little bit funny numbers.

The different resistors are necessary to achieve a smooth curve of analog voltages from those 12 different keys. If you think that it is more practical to just use one value for all, try to calculate the resulting voltages. You will soon learn that all these resistors have to have different values. The reason for that is in part that they add up in the two rows, another reason is the voltage divider formula, which is not linear. If you are able to solve that in an optimal way with a few formulas, try that. I gave up very soon.

The following is a table for the above displayed matrix that calculates the resulting resistors for each key pressed. Of course, if you assume that two keys can be pressed at once, you have to move to a PC keyboard instead. But not many people press two keys at once.

The second row of the table displays the resistor values towards ground, the third towards the operating voltage. The voltage that results from that voltage dividers is calculated with the formula

U = Uref * RGND / (RGND + RVCC)

Because available resistors have a tolerance of +/-5% the minimum and maximum has to be calculated. To calculate the minimum, all resistors to ground are assumed to have -5%, all towards VCC are assumed to have +5%. That is not very realistic, but we do not want to hand-select resistors and test them in summer and winter.
KeyResistor
to GND
Ohm
Resistor
to VCC
Ohm
U Nominal
@5V
U Min
@5V
U Max
@5V
N Nom
@8Bit
N Min
@8Bit
N Max
@8Bit
11000191600.2480.2250.273131114
21820191600.4340.3960.475222025
32820191600.6410.5880.700333036
4100041600.9690.8931.050504554
5182041601.5221.4181.630787284
6282041602.0201.9012.14210397110
710008602.6882.5632.812138131144
818208603.3963.2853.503174168180
928208603.8323.7403.919196191201
*10001804.2374.1704.300217213221
018201804.5504.5074.589233230235
#28201804.7004.6714.727241239243
The The voltages that are associated with the keys, and their variation through tolerance is displayed in the following graph.

As can be seen, the tolerance influences only the middle keys.

In the table, the values N are added that would result from an AD conversion with 8-Bit. This resolution is fine as all the values have enough distance from the previous and the next value.

### Voltages and key recognition

As can be seen from the table, there is no overlapping of the different detection values for the keys, taking 5% tolerance of the resistors into account.

Those who like to play around with other resistor combinations, can download the calculation sheet (as Open-Office-Format, as Excel-XP-Format).

### Hints for the AD converter hardware

ATtiny devices in the most cases provide only the opportunity to use an internally generated voltage or the supply voltage of the AVR as reference for the AD converter. For the keypad conversion only the supply voltage is suitable as reference. This option has to be selected when initiating the AD converter hardware at program start.

Many ATmega types can connect the reference voltage to an external pin, AREF. This pin can either be input or output. It is an output if either the supply voltage or the internal reference are selected as AD converter reference. In this case the AREF pin should have a condensator to ground to further reduce noise on the reference voltage. The AREF pin is an input, if an external reference source is selected as option. In this case an external source provides the reference voltage. If an external source provides the reference voltage, the keypad matrix should also be supplied by this source. Note in that case that the keypad consumes up to 10 mA, to improve noise sensitivity.

ATmega devices allow to supply the AD converter from an extra pin (AVCC) to further reduce noise. If only the keypad uses AD conversion the low necessary resolution of 8 bits does not require a separate supply for the AVCC pin, it can be tied to the normal supply voltage. If other measuring tasks have to be performed on other channels, the AVCC pin should be connected to the supply voltage via a coil of 22 µH and should be blocked by a condensator of 100 nF to ground.

For reading the keypad matrix voltage one AD converter channel is required. The AD converter is initiated once during program start. The two example codes show a start sequence for single conversion, here for an ATmega8, and one for an interrupt driven automatic start of the ADC, here for an ATtiny13.

#### ATmega8: manual start of the ADC

The first example shows a routine for an ATmega8, without interrupts, with a manual start and stop of the the AD converter. The keypad signal is connected to AD channel ADC0.
``````
.DEF rKey = R15 ; Register for AD value
.DEF rmp = R16 ; Multi purpose register
; set MUX to channel 0, left adjust the result, AREF taken from AVCC
; switch AD conversion on, start conversion, divider rate = 128
; wait until the AD conversion is complete
; check ADSC bit, conversion complete if bit is zero
``````
Please note that this single conversion requires 25 * 128 clock cycles, at 1 Mcs/s clock 3.2 milli-seconds. Only do this that way, running around in circles, if you don't need to care for other things in between this time delay (except if these other things are done inside interrupts).

#### ATtiny13: Autostart AD conversion, interrupt-driven

Yes, an ATtiny13 with its 8 pins can read our keypad matrix (we cannot connect the keypad matrix itself due to the limited number of I/O pins).

A typical routine for this task would be the following sequence, that converts the voltage on ADC3 (pin 2 of the ATtiny13) continously (after conversion complete, the next conversion is started automatically).
``````
;
;
; PB3=ADC3 is used for the conversion
ldi rmp,0b00001000 ; disconnect the PB3 digital driver, saves supply current
out DIDR0,rmp
; Reference = supply voltage, Left-adjust the result
ldi rmp,0b00100011 ; reference voltage = supply voltage, chose ADC3
; select autostart option
ldi rmp,0b00000000 ; free-running conversion (autostart)
; start ADC, allow interrupt, select clock divider
ldi rmp,0b11101111 ; start ADC, autostart,
out ADCSRA,rmp ;  Int Enable, clock divider to 128
; initiation complete
``````
Running in interrupt mode requires defining the respective int jump vector, e.g.
``````
;
; Reset and int vectors, ATtiny13
;
.CSEG ; assemble into code segment
.ORG \$0000 ; at the beginning of the code segment
rjmp main ; Reset vector
reti ; Int0 interrupt vector
reti ; PCINT0 vector
reti ; TC0 overflow vector
reti ; Analog comparator int vector
reti ; TC0 CompA vector
reti ; TC0 CompB vector
reti ; WDT vector
;
``````
Of course, the stack must be initiated to use interrupts, and the interrupt status flag must be set (SEI).

``````
;
; Interrupt Service Routine AD conversion
;
.DEF rKey = R15 ; result register for the conversion result
reti ; return from interrupt
;
``````
The register rKey continously provides the current value of the resistor matrix.

### Converting the AD result to the key code

The conversion result is, as such, not very useful. The voltages and the conversion result do not follow easy mathematical laws (the resistor values 4.7 - 5.6 - 6.8 - 8.2 must have been designed by a drunken math professor, the formula V = R1 / (R1 + R2) is not very easy to handle), so that we better use a table to resolve our key codes. The table cannot be a primitive look-up table, because we have 256 different possible results of the conversion, and we like more slim tables.

Like a monkey, we climb the matrix tree by going step by step through the following table:
``````
KeyTable:
.DB 7, 255, 18, 1, 28, 2, 42, 3, 64, 4, 91, 5
.DB 121, 6, 156, 7, 185, 8, 207, 9, 225, 10, 237, 0, 255, 11
``````
The first byte is the compare value for our conversion result, the second byte is the key code, if this compare value is greater than our result. If the result is between 0 and <7: no key is pressed (key code is 255), if it is between 7 and <18 the key code is 1, etc.

Or, if you prefer ASCII for the key codes:
``````
KeyTable:
.DB 7, 0 , 18, '1', 28, '2', 42, '3', 64, '4', 91, '5'
.DB 121, '6', 156, '7', 185, '8', 207, '9', 225, '*', 237, '0', 255, '#'
``````
The code for key translation goes like this:
``````
;
; Converting a AD result to a key code
;
GetKeyCode:
; if the AD result can change in between, the result must be copied first
mov R1,rKey ; copy AD result to R1
ldi ZH,HIGH(2*KeyTable) ; Z points to conversion table
ldi ZL,LOW(2*KeyTable)
GetKeyCode1:
lpm ; read one table value
cp R1,R0 ; compare AD result with table value
brcs GetKeyCode2 ; less than table value, key identified
inc R0 ; test, if table end is reached
breq GetKeyCode2 ; reached end of table
adiw ZL,2 ; point to next table entry
rjmp GetKeyCode1 ; go on comparing next entry
GetKeyCode2:
adiw ZL,1 ; point to MSB = key code
lpm ; read key code to R0
;
``````
Of course we have to check, if no key is pressed (R0 = 0xFF resp. if ASCII: R0 = 0) and we have to check for glitches (if the same key code comes 20 or more times, I take it for serious ...).

### Experiences

The hard- and software work very reliable. In the first version the resistor values of the matrix were ten times higher. This version was more vulnerable to HF noise, e.g. when transmitting with a 2 W VHF transmitter nearby.

To the top of the page