Pfad: Home =>
AVR-Übersicht => Keyboard
Diese Seite zeigt, wie eine handelsübliche 12-er-Tastatur an
einen AVR angeschlossen und per Assembler-Software ausgelesen
werden kann. Die Abschnitte:
- Funktionsweise der Tastatur
- AVR: I/O-Anschlussmatrix einzeln
- AVR: Anschluss an einen ADC mit Widerstands-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..
Um
herauszufinden, ob irgendeine der 12 Tasten gedruuml;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:
| Column | Row | Key |
| Col1 | Col2 | Col3 | Row1 | Row2 |
Row3 | Row4 | Zeichen | Binärcode |
| 0 | 0 | 0 | 1 | 1 | 1 | 1 |
(Keins) | 1111 |
| 0 | 1 | 1 | 0 | 1 | 1 | 1 |
1 | 0001 |
| 1 | 0 | 1 | 0 | 1 | 1 | 1 |
2 | 0010 |
| 1 | 1 | 0 | 0 | 1 | 1 | 1 |
3 | 0011 |
| 0 | 1 | 1 | 1 | 0 | 1 | 1 |
4 | 0100 |
| 1 | 0 | 1 | 1 | 0 | 1 | 1 |
5 | 0101 |
| 1 | 1 | 0 | 1 | 0 | 1 | 1 |
6 | 0110 |
| 0 | 1 | 1 | 1 | 1 | 0 | 1 |
7 | 0111 |
| 1 | 0 | 1 | 1 | 1 | 0 | 1 |
8 | 1000 |
| 1 | 1 | 0 | 1 | 1 | 0 | 1 |
9 | 1001 |
| 0 | 1 | 1 | 1 | 1 | 1 | 0 |
* | 1010 |
| 1 | 0 | 1 | 1 | 1 | 1 | 0 |
0 | 0000 |
| 1 | 1 | 0 | 1 | 1 | 1 | 0 |
# | 1011 |
Um eine solche Tastatur mit diskreten Bauteilen auslesbar zu machen, braucht
es mindestens:
- einen Oszillator mit Schieberegister und Start/Stop zur Erzeugung der
Spaltensignale,
- Feststellung, ob einer der vier Zeilenanschlüsse Null ist,
- Umkodierer für die Auswertung der sieben Signale.
Oder ein fertiges IC, das das alles macht. Oder eben einen Mikrokontroller.
An den Seitenanfang
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,0b00011111 ; 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?
brne 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
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.
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:
* 1 k + 820 Ω = 1,82k nach Masse,
* 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.
| Taste | Spannungen |
8-Bit-AD-Werte | Detektion |
| U(min.) | U(typ.) | U(max.) | min. |
typ. | max. | (ab AD-W.) |
| 1 | 0,225 |
0,248 | 0,272 |
11 | 13 |
14 | 7 |
| 2 | 0,396 |
0,434 | 0,474 |
20 | 22 |
25 | 18 |
| 3 | 0,588 |
0,641 | 0,698 |
29 | 33 |
36 | 28 |
| 4 | 0,930 |
0,969 | 1,048 |
47 | 49 |
54 | 42 |
| 5 | 1,468 |
1,522 | 1,627 |
74 | 78 |
84 | 64 |
| 6 | 1,959 |
2,020 | 2,139 |
99 | 103 |
110 | 91 |
| 7 | 2,563 |
2,688 | 2,809 |
130 | 137 |
144 | 121 |
| 8 | 3,285 |
3,396 | 3,500 |
167 | 173 |
180 | 156 |
| 9 | 3,740 |
3,832 | 3,917 |
190 | 195 |
201 | 185 |
| * | 4,170 |
4,237 | 4,298 |
212 | 216 |
221 | 207 |
| 0 | 4,507 |
4,550 | 4,588 |
229 | 232 |
235 | 225 |
| # | 4,671 |
4,700 | 4,726 |
238 | 240 |
242 | 237 |
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 by http://www.avr-asm-tutorial.net