Pfad: Home => AVR-Overview => keypad

Connecting a keypad to an AVR

This page shows how to connect a common 12-key-keypad to an AVR and read it by assembler software. The chapters are:
  1. How a keypad works
  2. AVR: I/O-connection of the matrix
  3. AVR: Connecting to an ADC with a resistor network

1. How a keypad works

12-key-keypad-matrix 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 ...
Press any keyTo 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:
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: 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

keypad matrix on I/O-Port 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 keypad-I/O
.DEF rmp = R16 ; define a multipurpose register
; define ports
.EQU pKeyOut = PORTB ; Output and Pull-Up-Port
.EQU pKeyInp = PINB  ; read keypad input
.EQU pKeyDdr = DDRB  ; data direction register of the port
; Init-routine
	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
	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)
	; read column 1
	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
	adiw ZL,4 ; column not found, point Z one row down
	; read column 2
	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
	adiw ZL,4 ; column not found, another four keys down
	; read column 3
	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
	rjmp NoKeyPressed ; no key pressed
; Table for code conversion
.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


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.

Hints, Disadvantages

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

resistor matrixThis 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.
Resistor dividerVoltagesADC values
to GND
to VCC
U Nominal
U Min
U Max
N Nom
N Min
N Max
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.

Initiation and reading the AD converter result

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
	ldi rmp,(1<<REFS0)|(1<<ADLAR) ; ADMUX channel 0, AREF from AVCC
	out ADMUX,rmp
	; switch AD conversion on, start conversion, divider rate = 128
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp
	; wait until the AD conversion is complete
	; check ADSC bit, conversion complete if bit is zero
	sbic ADCSRA,ADSC ; conversion ready?
	rjmp WaitAdc1 ; not yet
	; read MSB of the AD conversion result
	in rKey,ADCH
	; switch AD converter off
	ldi rmp,(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; switch ADC off
	out ADCSRA,rmp
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).

; Start AD converter
	; 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
	;   ADMUX to ADC3
	ldi rmp,0b00100011 ; reference voltage = supply voltage, chose ADC3
	out ADMUX,rmp
	; select autostart option
	ldi rmp,0b00000000 ; free-running conversion (autostart)
	out ADCSRB,rmp
	; 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 ; Eeprom ready vector
	reti ; Analog comparator int vector
	reti ; TC0 CompA vector
	reti ; TC0 CompB vector
	reti ; WDT vector
	rjmp intadc ; ADC conversion complete vector
Of course, the stack must be initiated to use interrupts, and the interrupt status flag must be set (SEI).

The service routine intadc reads the AD conversion result. Because Left-Adjust has been selected, it is sufficient to read the MSB of the result:

; Interrupt Service Routine AD conversion
.DEF rKey = R15 ; result register for the conversion result
	in rKey,ADCH ; read AD converter MSB
	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:

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

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


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

©2006-2017 by