Pfad: Home => AVR-Übersicht => Keyboard

Anschluss einer 12-er-Tastatur an einen AVR

Diese Seite zeigt, wie eine handelsübliche 12-er-Tastatur an einen AVR angeschlossen und per Assembler-Software ausgelesen werden kann. Die Abschnitte:
  1. Funktionsweise der Tastatur
  2. AVR: I/O-Anschlussmatrix einzeln
  3. AVR: Anschluss an einen ADC mit Widerstands-Matrix

1. Funktionsweise der Tastatur

12-er-Tastatur-Matrix 12-er-Tastaturen sind Schalter, die über eine Matrix von Zeilen (Rows) und Spalten (Columns) miteinander verbunden sind. Wird die Taste "1" gedrückt, dann ist die Spalte 1 mit der Zeile 1 verbunden, ist "2" gedrückt, dann Spalte 2 mit Reihe 1, usw..
Irgend ein KeyUm herauszufinden, ob irgendeine der 12 Tasten gedrückt ist, würde es reichen, die drei Spalten mit Null Volt zu verbinden und die vier Zeilen zu verbinden und über einen Pull-Up-Widerstand von z.B. 10 kΩ mit Plus zu verbinden. Der Output hat ohne gedrückte Taste Plus-Potential. Jede gedrückte Taste bewirkt dann, dass der Output auf Null Volt gezogen wird.
Um auch noch fest zu stellen, welche der 12 Tasten gedrückt ist, wären z.B. nacheinander die drei Spaltenanschlüsse auf Null Volt zu bringen (die beiden anderen jeweils auf Plus) und das Ergebnis an den vier Zeilenanschlüssen abzulesen. Ist einer der vier Zeilenanschlüsse auf Null, muss die Maschinerie anhalten und den aktuellen Spaltenanschluss sowie das Ergebnis der Zeilenanchlüsse in den Code einer gedrückten Taste umwandeln. Etwa so:
ColumnRowKey
Col1Col2Col3Row1Row2 Row3Row4ZeichenBinärcode
0001111 (Keins)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
Um eine solche Tastatur mit diskreten Bauteilen auslesbar zu machen, braucht es mindestens: Oder ein fertiges IC, das das alles macht. Oder eben einen Mikrokontroller.

An den Seitenanfang

2. AVR: I/O-Anschlüssmatrix einzeln

Tastaturmatrix an I/O-Port Eine Tastaturmatrix kann direkt und ohne weitere Bauteile an einen Mikrokontroller angeschaltet werden.
Im Beispiel sind dies die unteren sieben I/O-Pins des Ports B. Andere Ports lassen sich ebenso verwenden.
Die Ports PB4..PB6 werden als Ausgänge definiert und liefern die Spalten-Nullen. Die Ports PB0..PB3 dienen zum Einlesen der Zeilenergebnisse. Die Pull-Up-Widerstände der Ports PB0..PB3 werden per Software zuschaltet, externe Widerstände sind unnötig.

Das folgende Software-Beispiel zeigt zunächst das Initiieren der Ports. Sie wird nur ein Mal zu Beginn des AVR-Programms ausgeführt.

Init-Routine


;
; Init Keypad-I/O
;
.DEF rmp = R16 ; ein Hilfsregister definieren
; Ports definieren
.EQU pKeyOut = PORTB ; Ausgabe und Pull-Ups
.EQU pKeyInp = PINB  ; Tastatur lesen 
.EQU pKeyDdr = DDRB  ; Datenrichtungsregister
; Init-Routine
InitKey:
	ldi rmp,0b01110000 ; Datenrichtungsregister
	out pKeyDdr,rmp    ; des Keyports setzen
	ldi rmp,0b00001111 ; Pull-Up-Widerstände
	out pKeyOut,rmp    ; an den Eingängen 

Tastendruck feststellen

Die folgende Routine stellt zunächst fest, ob irgendeine Taste gedrückt ist. Sie wird im Programmverlauf regelmäßig wiederholt, z.B. in einer Verzögerungsschleife oder Timer-gesteuert.

;
; Check any key pressed
;
AnyKey:
	ldi rmp,0b00001111 ; PB4..PB6=Null, pull-Up-Widerstände
	out pKeyOut,rmp    ; an den Eingängen PB0..PB3
	in rmp,pKeyInp     ; Tastaturport lesen
	ori rmp,0b11110000 ; alle oberen Bits auf Eins
	cpi rmp,0b11111111 ; alle Bits = Eins?
	breq NoKey         ; ja, keine Taste gedrückt

Gedrückte Taste feststellen

Jetzt ist der Auslesevorgang dran. Nacheinander werden PB6, PB5 und PB4 Null gesetzt und PB0..PB3 auf Nullen geprüft. Das Registerpaar Z (ZH:ZL) zeigt dabei auf eine Tabelle mit den Tastencodes. Es zeigt am Ende auf den identifizierten Tastencode, der mit der Instruktion LPM aus dem Flash-Memory in das Register R0 gelesen wird.

;
; Identifiziere gedrueckte Taste
;
ReadKey:
	ldi ZH,HIGH(2*KeyTable) ; Z ist Zeiger auf Tastencode
	ldi ZL,LOW(2*KeyTable)
	; read column 1
	ldi rmp,0b00111111 ; PB6 = 0
	out pKeyOut,rmp
	in rmp,pKeyInp ; lese Zeile
	ori rmp,0b11110000 ; obere Bits maskieren
	cpi rmp,0b11111111 ; ein Key in dieser Spalte?
	brne KeyRowFound ; Spalte gefunden
	adiw ZL,4 ; Spalte nicht gefunden, Z vier Keys weiter
	ldi rmp,0b01011111 ; PB5 = 0
	out pKeyOut,rmp
	in rmp,pKeyInp ; wieder Zeile lesen
	ori rmp,0b11110000 ; obere Bits maskieren
	cpi rmp,0b11111111 ; ein Key in dieser Spalte?
	brne KeyRowFound ; Spalte gefunden
	adiw ZL,4 ; Spalte nicht gefunden, Z vier Keys weiter
	ldi rmp,0b01101111 ; PB4 = 0
	out pKeyOut,rmp
	in rmp,pKeyInp ; letzte Zeile lesen
	ori rmp,0b11110000 ; obere Bits maskieren
	cpi rmp,0b11111111 ; ein Key in dieser Spalte?
	breq NoKey ; wider Erwarten auch hier nicht gefunden
KeyRowFound: ; Spalte ist gefunden, identifiziere Zeile
	lsr rmp ; schiebe Bit 0 in das Carry-Flag
	brcc KeyFound
	adiw ZL,1 ; zeige auf naechsten Tastencode
	rjmp KeyRowFound ; weiter schieben
KeyFound:
	lpm ; lese keycode nach R0
	rjmp KeyProc ; hier weiter mit Key-Verarbeitung
NoKey:
	rjmp NoKeyPressed ; keine Taste gedrueckt
;
; Tabelle fuer Code Umwandlung
;
KeyTable:
.DB 0x0A,0x07,0x04,0x01 ; Erste Spalte, Tasten *, 7, 4 und 1
.DB 0x00,0x08,0x05,0x02 ; Zweite Spalte, Tasten 0, 8, 5 und 2
.DB 0x0B,0x09,0x06,0x03 ; Dritte Spalte, Tasten #, 9, 6 und 3

Entprellen

In den Routinen KeyProc und NoKeyPressed muss natürlich noch ein Entprellen der Tasten erfolgen. Also z.B. muss in der KeyProc-Routine eine Tastenoperation erst dann ausgeführt werden, wenn die gleiche Taste 50 Millisekunden lang gedrückt ist. In der NoKeyPressed-Routine kann der dazu verwendete Zähler zurück gesetzt werden. Da das Timing der Entprellung auch noch von anderen Bedürfnissen abhängig sein kann, ist es hier nicht eingearbeitet.

Hinweise, Nachteile

In den Software-Beispielen ist zwischen der Ausgabe der Column-Adresse und dem Einlesen der Row-Information nur ein Takt Zeit gelassen. Bei hohen Taktfrequenzen und/oder langen Leitungen zwischen Tastatur und Prozessor ist es notwendig, zwischen den Out- und In-Instruktionen mehr Zeit zu lassen (z.B. durch Einfügen von NOP-Instruktionen).
Die internen Pull-Ups liegen bei Werten um 50 kΩ. Bei langen Leitungen und in hoch feldverseuchter Umgebung kann es unter Umständen zum Fehlansprechen der Tastatur kommen. Wer es weniger sensibel haben will, kann noch externe Pull-Ups dazu schalten.

Der Nachteil der Schaltung ist, dass sie sieben Port-Leitungen exklusiv benötigt. Die Lösung über einen AD-Wandler-Kanal und ein Widerstands-Netzwerk (Abschnitt 3) ist da viel sparsamer.

An den Seitenanfang

3. Anschluss an einen ADC mit Widerstands-Matrix

Die meisten Tiny- und Mega-AVR-Typen haben heutzutage AD-Wandler an Bord. Sie sind daher ohne größere Klimmzüge dazu in der Lage, Analogspannungen zu messen und mit 10 Bits Genauigkeit aufzulösen. Wer also I/O-Ports sparen will, muss die Tastatur nur dazu bringen, ein Analogsignal zu liefern. Das macht z.B. eine Widerstandsmatrix.

Eine erheblich verbesserte Version dieses Kapitels mit neuer Software und vielem anderem mehr gibt es
hier.

Widerstandsmatrix

Widerstandsmatrix Hier ist eine solche Widerstandsmatrix abgebildet. Die Spalten sind über drei Widerstände auf Masse geführt, die Zeilen über vier Widerstände auf die Betriebsspannung (z.B. 5V). Der AD-Wandler-Eingang ist noch mit einem Folienkondensator von 1 nF abgeblockt, da der ADC absolut keine Hochfrequenz mag, die da über die Tasten, die Widerstände und die Zuleitungen eingestreut werden könnte.
Wird jetzt z.B. die Taste "5" gedrückt, dann entsteht ein Spannungsteiler:

  a) 1 k + 820 Ω = 1,82k nach Masse,
  b) 3,3 k + 680 Ω + 180 Ω = 4,16k nach Plus.

Bei 5 Volt Betriebsspannung gelangen dann

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

an den AD-Wandler-Eingang. Rechnen wir noch 5% Toleranz der Widerstände mit ein, dann liegt die Spannung irgendwo zwischen 1,468 und 1,627 Volt. Der AD-Wandler macht daraus bei 5 V Referenzspannung einen Wert zwischen 300 bis 333. Verwenden wir nur die oberen 8 Bit des AD-Wandler-Ergebnisses (Teilen durch vier oder Linksjustieren des Wandlers), gibt das 74 bis 78.

Spannungswerte und Auswertung

Die anderen Kombinationen von Widerständen ergeben die in der Tabelle gegebenen Werte für die Spannungen, die 8-Bit-AD-Wandler-Werte und die optimale Erkennung, ob der Spannungswert der Taste erreicht wurde.
TasteSpannungen 8-Bit-AD-WerteDetektion
U(min.)U(typ.)U(max.)min. typ.max.(ab AD-W.)
10,225 0,2480,272 1113 147
20,396 0,4340,474 2022 2518
30,588 0,6410,698 2933 3628
40,930 0,9691,048 4749 5442
51,468 1,5221,627 7478 8464
61,959 2,0202,139 99103 11091
72,563 2,6882,809 130137 144121
83,285 3,3963,500 167173 180156
93,740 3,8323,917 190195 201185
*4,170 4,2374,298 212216 221207
04,507 4,5504,588 229232 235225
#4,671 4,7004,726 238240 242237
Wie zu erkennen ist, gibt es bei der Verwendung von 5%-Widerständen und der dargestellten Widerstandskombination keine Überlappungen der Spannungsbereiche der einzelnen Tasten.

Wer andere Widerstandskombinationen ausprobieren möchte, kann mit der zugehöigen Tabelle herumspielen (im Open-Office-Format, im Excel-XP-Format).

Hinweise zur AD-Wandler-Hardware

ATtiny-Typen bieten meist nur die Möglichkeit, eine intern erzeugte Konstantspannung oder die Betriebsspannung als Referenzspannung des AD-Wandlers zu wählen. Für die Tastaturschaltung kommt nur die Betriebsspannung als Referenzspannung infrage. Diese Option ist beim Initiieren des AD-Wandlers einzustellen.

Bei vielen ATmega-Typen kann auch eine extern erzeugte Referenzspannung verwendet werden, die am Pin AREF zugeführt wird. Verwendet man diese Möglichkeit, wäre auch die Tastaturschaltung aus dieser Referenz zu speisen. Verwendet man keine externe Referenzspannung, dann kommt für den Betrieb der Tastatur nur die Möglichkeit der Verwendung der Betriebsspannung als Referenzspannung infrage. In diesem Fall wird die Betriebsspannung per Software-Option intern an den AREF-Pin geführt, der externe AREF-Pin wird mit einem Folienkondensator von ca.10 nF abgeblockt.

ATmega-Typen bieten zur Erhöhung der Stabilität des AD-Wandlers ferner die Möglichkeit, diesen über den AVCC-Pin separat zu bespeisen. Für die Tastaturschaltung alleine kann dieser Pin direkt an die Betriebsspannung angeschlossen werden. Sollen auch noch andere Messungen veranstaltet werden, bei denen genauer gemessen werden soll, wird der AVCC-Pin über eine Drossel von 22 µH an die Betriebsspannung geführt und mit einem Keramikkondensator von 100 nF gegen Masse abgeblockt.

Initiieren und Lesen des AD-Wandlers

Zum Auslesen der Tastatur wird ein AD-Wandler-Kanal gebraucht. Der AD-Wandler wird zu Beginn eingestellt. Die beiden Beispiele zeigen den manuellen Einzelstart eines ATmega8 und den interrupt-gesteuerten Dauerstart bei einem ATtiny13.

ATmega8: manuell starten

Als erstes Beispiel ein ATmega8, ohne Interrupts, mit manuellem Start und Stop des AD-Wandlers. Tastatursignal am AD-Kanal ADC0.

.DEF rKey = R15 ; Register für AD-Wert
.DEF rmp = R16 ; Vielzweck-Register
	; setze MUX auf Kanal 0, Linksjustieren, AREF auf AVCC
	ldi rmp,(1<<REFS0)|(1<<ADLAR) ; ADMUX Kanal 0, AREF auf AVCC
	out ADMUX,rmp
	; AD-Wandler einschalten, Wandlung starten, Teilerrate = 128
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp
	; warten bis AD-Wandler fertig mit Wandlung ist
WaitAdc1:
	; ADSC-Bit abfragen, wenn Null ist Wandlung fertig
	sbic ADCSRA,ADSC ; conversion ready?
	rjmp WaitAdc1 ; not yet
	; AD-Wandler-Wert MSB lesen
	in rKey,ADCH
	; AD-Wandler wieder abschalten
	ldi rmp,(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; switch ADC off
	out ADCSRA,rmp
Man beachte, dass diese eine Wandlung 25 * 128 Takte dauert, also bei 1 MHz Prozessortakt 3,2 Millisekunden. Das macht man nur, wenn sonst nichts Wichtiges anderes zu tun ist als im Kreis herum zu laufen.

ATtiny13: Autostart AD-Wandlung, Interrupt-gesteuert

Ja, auch ein ATtiny13 kann unsere Tastaturmatrix einlesen (das wäre mangels Pins bei der Einzelbedrahtung gar nicht möglich).

Eine typische Routine hierfür wäre für den ATtiny13 beispielsweise die folgende Sequenz, bei der ADC3 an Pin 2 des tiny13 im Dauerlauf (der AD-Wandler beginnt nach der Umwandlung von selbst wieder) gestartet wird.

;
; AD-Wandler starten
;
	; PB3=ADC3 wird nur für den AD-Wandler verwendet
	ldi rmp,0b00001000 ; PB3 Digitaltreiber ausschalten, spart Strom
	out DIDR0,rmp
	; Referenz = Betriebsspannung, Links-Justieren Ergebnis,
	;   ADMUX auf ADC3 stellen
	ldi rmp,0b00100011 ; Referenzspannung UB, ADC3 waehlen
	out ADMUX,rmp
	; Autostart-Option wählen
	ldi rmp,0b00000000 ; Freilauf waehlen (startet selbst)
	out ADCSRB,rmp
	; ADC starten, Interrupt ermöglichen, Teilerrate einstellen
	ldi rmp,0b11101111 ; ADC starten, Autostart,
	out ADCSRA,rmp ;  Int Enable, Teiler auf 128
; fertig initiiert
Der Betrieb per Interrupt setzt voraus, dass der entsprechende Sprungvektor vorhanden ist, also z.B.

;
; Sprungvektoren fuer Reset und Interrupts, ATtiny13
;
.CSEG ; Assembliere in das Code Segment
.ORG $0000 ; an den Anfang des Code Segments
	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
;
Natürlich muss auch der Stapel initiiert sein und das Interrupt- Statusflag gesetzt sein (SEI).

Die Service-Routine intadc liest den AD-Wandler aus. Da Links-Justieren gesetzt ist, reicht es, das MSB des Ergebnisses zu lesen:

;
; Interrupt Service Routine AD-Wandler
;
.DEF rKey = R15 ; Ergebnisspeicher AD-Wandler-Wert
intadc:
	in rKey,ADCH ; Lese AD-Wandler MSB
	reti ; Rückkehr vom Interrupt
;
Im Register rKey steht jetzt laufend aktuell der Wandlerwert des Keyboards.

Umwandeln des AD-Wandler-Werts in einen Tastencode

Die Spannung alleine ist noch nicht sehr verwendungsfähig. Da die Spannungen aufgrund der eigenwilligen Gestaltung von Standard-Widerstandswerten (wer hat sich die Reihe 4,7 - 5,6 - 6,8 - 8,2) bloss ausgedacht? Muss entweder ziemlich sturzbetrunken oder ein Mathematiker gewesen sein!) und der arg krummen Formel U = R1 / (R1 + R2) kommt hierfür nur eine Tabelle in Betracht. Die Tabelle kann nicht primitiv sein, da wir ja 256 mögliche ADC-Zustände haben und keine unnötige Platzverschwendung betreiben wollen.

Wir hangeln uns mit dem ADC-Wert in rKey durch folgende Tabelle:

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
Das niedrige erste Byte jedes Worts sind jeweils die AD-Werte: von 0 bis <7: keine Taste gedrückt (Tastencode=255), von 7 bis <18: Tastencode 1, etc..

Oder wer lieber gleich ASCII mag:

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, '#'
Der Code zur Auswertung sieht dann so aus:

;
; Umwandlung des AD-Werts in einen Keycode
;
GetKeyCode:
	; falls der AD-Wert zwischendurch wechselt, vorher kopieren!
	mov R1,rKey ; kopiere AD-Wandler-Wert nach R1
	ldi ZH,HIGH(2*KeyTable) ; Z zeigt auf Tabelle
	ldi ZL,LOW(2*KeyTable)
GetKeyCode1:
	lpm ; Lese Wert aus Tabelle
	cp R1,R0 ; vergleiche AD-Wert mit Tabellenwert
	brcs GetKeyCode2 ; kleiner als Tabellenwert, Taste gefunden
	inc R0 ; teste, ob am Tabellenende
	breq GetKeyCode2 ; Tabellenende erreicht
	adiw ZL,2 ; huepfe ein Wort weiter in der Tabelle
	rjmp GetKeyCode1
GetKeyCode2:
	adiw ZL,1 ; zeige auf MSB = Tastencode
	lpm ; lese Tastencode aus Tabelle in R0
;
Natürlich ist jetzt noch zu prüfen, ob keine Taste gedrückt ist (R0 = 0xFF bzw. bei ASCII R0 = 0) und es sind Anti-Prell-Aktionen zu basteln (wenn 20 mal hintereinander die gleiche Taste herauskommt, nehme ich sie ernst, etc.).

Erfahrungen

Die Schaltung und die Software sind sehr stabil. In der ersten Version waren die Widerstände 10 mal so groß. Das hatte höhere Störanfälligkeit zur Folge, z.B. wenn in der Nähe mit einer 2 W-Handfunke gerade gesendet wurde.



An den Seitenanfang

©2006-2009 by http://www.avr-asm-tutorial.net