Tasten auswerten in Assembler
Tasten sind wahrscheinlich immer noch das Eingabemedium Nr.1
in
der Mikrocontrollerwelt. An einen AVR kann man einen Taster an einen
beliebigen Portpin gegen GND anschließen, den internen Pullup
Widerstand einschalten und schon können wir mit einem Tastendruck
unserem AVR sagen das wir
eine bestimmte Funktion ausführen wollen. Normale Taster haben jedoch
einen Nachteil.
Taster prellen. Wird ein Taster betätigt, federt der der mechanische
Kontakt noch einige male zurück bis er dann zum Stillstand kommt und
unseren Portpin sauber auf GND zieht. Während des prellens, hängt der
Kontakt Zeitweise in der Luft und unser Pullup zieht unser Portpin auf
Highpegel. Daraus folgt, dass unser
Mikrocontroller mehrere Tastendrücke registriert, obwohl der Anwender
nur einmal drückt. Da der MC durchaus schnell genug ist um einzelne
Preller einzufangen müssen wir die Tasten entprellen. Einen guten
Artikel zu diesem Thema gibt es auf http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten.
Hier findet man auch eine ausgeklügelte Entprellroutine u.a. in
Assembler welche bis zu 8 Tasten
gleichzeitig, durch geschickte logische Verknüpfungen entprellt. Der
Grund, warum ich mich da noch selbst mit der Tastenentprellung
beschäftigt habe, liegt darin, dass die Routine immerhin 5 (untere)
Register belegt und ich noch nie mehr als 3 Tasten in einer meiner
Schaltungen benötigt habe. Wenn ich also jemals mehr als 4 Tasten in
einer Schaltung brauche, werde ich sicher auf dieser Routine aufbauen.
Natürlich kann man Tasten auch über Hardware mit R-C Gliedern,
Logikgatter usw. entprellen. Darum soll
es aber hier nicht gehen.
Im Folgenden geht es um Tastenentprellung über Software in AVR-Assembler.
Das Prinzip ist einfach. In den meisten Programmen wird dazu
die
Taste in Intervallen ausgelesen. Dazu wird ein Zähler benutzt, der bei
nicht gedrückter Taste
zurückgesetzt wird und bei Überlauf die gedrückte Taste meldet.
Ich
habe eine für mich einfachere Art für das Entpellen entdeckt. Anstatt
einen Zähler hochzuzählen, schiebe ich. So kann ich, zumindest in ASM
leichter einen Überlauf feststellen und muss keinen Zähler
zurücksetzen. Die ältesten Bits werden einfach herausgeschubst.
Darüber hinaus, werde ich zeigen, wie ich
kurze, lange und Doppelklicks unterscheide und auswerte. Außerdem zeige
ich, wie man einen Tastendruck auf 2 Tasten gleichzeitig auswertet und
stelle eine Autorepeat Funktion vor, die den Artikel abschließt. Die
folgenden Routinen sind durchaus Praxistauglich. In allen Projekten, in denen ich
Tasten eingesetzt habe, kommen die Entprellroutinen ( evtl. mit
leichten Anpassungen) vor.
Die Testschaltung
Alle folgenden Beispiele, lassen sich mit dieser Schaltung
nachvollziehen.
Ich habe die Schaltung auf einem Steckboard aufgebaut. Die Fuses des
ATTiny2313 sind im Originalzustand außer dass CKDIV8 deaktiviert wurde.
Der Tiny läuft also mit 8 MHz. Bitte sucht
eure ältesten und ausgeleiertesten Tasten aus der Bastelkiste, damit
man sieht, ob die Entprellung über Software Funktioniert.
Einfaches Entprellen..
.. geschieht am einfachsten und besten durch Polling in einem
Timerinterrupt. Man liest den
Portpin, an dem der Taster angeschlossen ist in Intervallen von etwa
2..10 Millisekunden
aus und überprüft, ob der Taster über längere Zeit, den gleichen
logischen Pegel liefert. Da in fast jedem Programm schon ein
Timerinterrupt läuft, baut man da am besten eine kleine Routine mit
ein, die den Pin ausliest und die Daten für das Hauptprogramm
bereithält. Sollte ein Timerinterrupt im Programm nicht sowieso schon
vorhanden sein, ist eine Tastenentprellung ein guter Grund, einen
solchen hinzuzufügen .
Beispiel1:
Im ersten Beispielprogramm, wird der Timer (0) so
programmiert, das alle 8 Millisekunden ein Interrupt erzeugt wird.
;Beispielroutine 1 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def taste
= r3
.def temp1
= r16
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt
;------------------------------------------------------
in
stat,SREG
;Status sichern
;----------------------------------------------
;Taste Entprellen
;----------------------------------------------
lsl
taste ;Eine
Null in Taste schieben
sbic
pind,0 ;Wenn die
Taste gedrückt ist, überspringen
inc
taste
;Ansonsten die Null in eine 1 wandeln
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
out
SREG,stat
;str wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi
temp1, RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
sbi
DDRD,6
;LEDanschluss auf Ausgang
sbi
PortD,0
;Pullup für Taste einschalten
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256, ISR ca. alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Taste pollen
;----------------------------------------------
main: tst
taste
;
brne main
;----------------------------------------------
;Reaktion wenn Taste gedrückt
;----------------------------------------------
sbi
pind,6
;In diesem
Fall, LED toggeln
;----------------------------------------------
;Warten, bis die Taste Losgelassen wird
;----------------------------------------------
main1: tst
taste
;
breq main1
rjmp main
;------------------------------------------------------
;Ende
;------------------------------------------------------
Nach dem üblichen sichern des Statusregister sind Folgende 3 Befehle für die Entprellung zuständig.
lsl
taste
sbic
pind,0
inc
taste
Jedes mal, wenn diese Befehlsfolge Durchlaufen wird (alle 8
mS), wird
der Zustand der Taste an PinD0 in das Register taste geschoben.
Da das Register taste
mit dem
unteren Register r3
definiert wurde, kann der Befehl sbr
oder ori
nicht
benutzt werden um Bit0 zu setzten. Deshalb wird Bit0 mit inc
gesetzt.
Das kann man so programmieren, wenn man weiß, dass Bit0 eine 0 enthält.
Das Hauptprogramm kann nun durch Testen des Registers taste,
abhängig
vom Zustand der Letzten 8 ISRs (der letzten 8*8=64 mS) verzweigen. Ist
der Inhalt von taste
=0, war die Taste vor 64mS gedrückt und für diese
Zeit gehalten. Jeder Tastenpreller wird eine 1 in taste
schieben und so
das Ergebnis zu <>0 (Taste nicht gedrückt) machen. Ebenso
kann
getestet werden, ob die Taste losgelassen wurde. Dann enthält taste
-1
(255, $ff,0b11111111). Die Verzögerungszeit von 64mS ist in der Paxis
nicht bemerkbar, deshalb können ruhig alle 8 Bit auf Null getestet
werden. Ist es einmal nötig einen Timerinterrupt mit 20mS oder mehr zu
programmieren, kommt man auf 160 Millisekunden Verzögerung . Hier
könnte man noch das obere Nibble durch eine AND
Verknüpfung ausmaskieren. Die unteren 4 Bit würden für eine saubere
Entprellung auch ausreichen.
Diese Entprellung Funktioniert eigentlich sehr zuverlässig. Wenn man sich jedoch das Hauptprogramm ansieht, ist das ganze etwas unkomfortabel, da man am Ende warten muss, bis der Anwender die Taste wieder loslässt. Außerdem würde ein zweiter Tastendruck, der während des ausführen der eigentlichen Routine (hier nur sbi pind,6) verloren gehen. Besser währe es, nur die negative Flanke auszuwerten. Das ist der Moment, wenn der Zustand am Portpin von high (Taster offen) zu low (Taster geschlossen) wechselt.
Entprellung
mit Flankenerkennung
Hinzugekommen ist das Flagregister flags (r23) welches die Flanke key1 in einem beliebigen Bit (hier Bit 0) speichert. Die restlichen 7 Bits, können für weitere Programmflags oder Tasten hergenommen werden.
Beispiel 2
;Beispielroutine
2 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def taste
= r3
.def temp1
= r16
.def flags
= r23
.equ key1
= 0
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt, alla 8 mS
;------------------------------------------------------
in
stat,SREG
;Status sichern
;----------------------------------------------
;Taste Entprellen
;----------------------------------------------
lsl
taste ;Eine
Null in Taste schieben
sbic
pind,0 ;Wenn die
Taste gedrückt ist, überspringen
inc
taste
;Ansonsten die Null in eine 1 wandeln
brne
is_end ;Wenn nicht
die letzten 8 ISRs gedrückt war
brcc
is_end ;Wenn vor 9
ISRs gedrückt war, Sprung
sbr
flags,1<<key1 ;Tastenflag
(Flanke) setzen
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
is_end: out
SREG,stat
;Status wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi temp1,
RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
sbi
DDRD,6
;LEDanschluss auf Ausgang
sbi
PortD,0
;Pullup für Taste einschalten
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256, ISR ca. alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Tastenflag abfragen
;----------------------------------------------
main: sbrc
flags,key1
;Solange überspringen bis Tastenflag gesetzt ist
rcall toggle_LED
;Weitere Tasten, oder was anderes
...
rjmp main
;----------------------------------------------
;Reaktion wenn Taste gedrückt
;----------------------------------------------
Toggle_LED:
cbr
flags,1<<key1
;Tastenflag
löschen
sbi
pind,6
;LED toggeln
ret
;------------------------------------------------------
;Ende
;------------------------------------------------------
Die Entprellung sieht nun so aus:
lsl
taste
sbic pind,0
inc taste
brne is_end
brcc is_end
sbr flags,1<<key1
Für die Flankenerkennung sind die letzten 3 Befehle zuständig. Brne prüft erstmal, ob die Taste für 64mS gehalten wurde und springt zum Interruptende wenn dem nicht so ist. Da das Flankenbit nur einmal pro Tastendruck gesetzt werden soll, wird das älteste (herausgeschobene) Bit mit brcc geprüft. Ist dieses gesetzt (high), war vor 9 Interrupts die Taste noch nicht gedrückt und wir haben eine negative Flanke die wir mit sbr flags,1<<key1 speichern.
Würde man PD0 an ein DSO anschließen, bekämen wir in etwa ein
Bild wie unten (PIND) angezeigt.

So etwa, könnte die Bitfolge aussehen, wie sie die
Entprellroutine
sieht. Die Bits die alle 8 mS an PinD0 abgetastet werden, wandern
im Gänsemarsch durch das Register taste
und zuletzt ins Carrybit. Der
blaue Rahmen, können wir in Gedanken nach rechts und links
verschieben.
Links in der Bitfolge ist der Taster noch offen und PIND0 liefert
einsen. Danach folgt die Prellphase mit einem zufälligen 0/1 Muster
welches es ja hier zu eliminieren gilt. Der blaue Rahmen zeigt die
Stelle,
an welcher die Entprellroutine greift und das Flankenbit setzt. Schiebt
man den Rahmen durch die Bitfolge und spielt die Routine im
Kopf durch, wird man feststellen, daß das Flag nur an dieser Stelle
gesetzt werden kann. Da das key1
Flag erst gesetzt wird, nachdem die Taste 8 mal hintereinander 0
liefert, ensteht eine kurze Verzögerungszeit von 64 Millisekunden,
welche aber vernachlässigt werden kann.
Im Hauptprogramm (siehe Beispiel 2
unter
Main) wird nun nur noch das Flankenbit key1
getestet und entsprechend
verzweigt. Das Unterprogramm löscht als erstes das Flankenbit. Die ISR
ist danach sofort wieder bereit, neue Tastendrücke aufzunehmen
Auch diese Entprellroutine könnte man durch eine logische AND
Verknüpfung des Register taste mit $f, auf 4 Bit reduzieren. Brcc würde
man dann durch brhc (Branch if Half Carry Flag is Cleared)
ersetzen. In der Skizze oben, würde das bedeuten, das Taste nur noch 4
Bit breit ist und C dur H ersetzt würde. Die Verzögerungszeit würde
sich in diesem Fall auf die hälfte reduzieren und somit schon nach 32
mS liefern. Das ist aber eigentlich nur von Nutzen, wenn die Intervalle
in der ISR so groß werden, das die Verzögerung vom Anwender bemerkt
wird. Für den langsamen Menschen ist sowohl 32 wie auch 64
Millisekunden, sofort .
Es liegt nahe, 2 Tasten mit 4 Bit, in nur einem Register, auf diese Art zu Entprellen. Dazu ist aber mehr Code und ein hohes Register (r16 - r31) für taste notwendig, so das es kürzer ist, für jede Taste ein eigenes Register zu definieren. Trotzdem hier der Code.
Beispiel 3
Im Programm wurde zusätzlich key2 definiert.
;Beispielroutine
3 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def tasten
= r22
.def temp1
= r16
.def flags
= r23
.equ key1
= 0
.equ key2
= 1
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt, alle 8 mS
;------------------------------------------------------
in
stat,SREG
;Status sichern
push temp1
;----------------------------------------------
;2 Tasten (4 Bit) in einem Register Entprellen
;----------------------------------------------
rol
tasten
;Bit3 ins Halfcarry, 7 ins Carry
andi
tasten,~(1<<0|1<<4) ;Null ins unterste Bit
jedes
Nibbles
sbic
pind,0
;Überspringen, wenn Taste gedrückt
ori
tasten,1<<0 ;In
1 Wandeln, wenn Taste nicht gedrückt
sbic pind,1
ori tasten,1<<4
t1:
brhc
t2
;Taste1 im unteren Nibble testen
mov
temp1,tasten
;Nibble auf Null
testen
andi
temp1,$0f
;Dazu oberes Nibble ausmaskieren
brne
t2
;Sprung, wenn ein Bit gesetzt ist
sbr
flags,1<<key1
;Ansonsten
Flankenbit setzen
t2:
brcc
is_end
;Taste2 ist im oberen Nibble
mov
temp1,tasten
;dito
andi temp1,$f0
brne is_end
sbr flags,1<<key2
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
is_end: pop temp1
out
SREG,stat
;str wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi
temp1, RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
ldi
temp1,1<<pd6|1<<pd5|1<<pd4
out
DDRD,temp1
;LED Anschlüsse auf Ausgang
ori
temp1,1<<pd0|1<<pd1
;Pullups
dazuodern
out
PortD,temp1
;Alle LEDs erstmal aus, Pullups ein
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256,
ISR ca. alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Tastenflags abfragen
;----------------------------------------------
main: sbrc flags,key1
rcall Tast1
sbrc flags,key2
rcall Tast2
rjmp main
Tast1: cbr
flags,1<<key1
sbi pind,5
ret
Tast2: cbr
flags,1<<key2
sbi pind,6
ret
;------------------------------------------------------
;Ende
;------------------------------------------------------
Man könnte die Routine noch verkürzen wenn man beim Label t1:
die
beiden Befehle mov
temp1,tasten und andi temp1,$0f
durch cpi
tasten,$f0
(und bei t2: tasten,$0f) ersetzen würde, könnte dann aber keine 2
Tasten gleichzeitig detektieren. Auch bräuchte das Temp1 Register in
diesem
Fall nicht gepusht zu werden.
Die Routine oben benötigt zum Entprellen von 2 Taster 16 (oder eben 14)
Befehle,
ein (hohes, teures) Register und 2 Bits im Flagregister. Dem
gegenüber steht der Code aus Beispiel 2 mit 6 Befehlen für eine
Taste, also 12 Befehle für 2 Tasten und 2 (niedrige)
Register.
Hier kann man also abwägen, welche Variante für das jeweilige Programm
besser passt.
Für sehr langsame Timerinterrupts kann der Entprellcode nochmals
verkürzt werden, indem nur 3 Bits auf null getestet werden.
;Beispielroutine 3.1 zur
Tastenentprellung by juergen@avr-projekte.de
;----------------------------------------------
;2 Tasten (3 Bit) in einem Register Entprellen
;----------------------------------------------
rol
tasten
;Bit3 ins Halfcarry, 7 ins Carry
andi
tasten,~(1<<0|1<<4) ;Jeweils eine Null ins
unterste Bit der
Nibbles
sbic
pind,0
;Überspringen, wenn Taste gedrückt
ori
tasten,1<<0
;In 1 Wandeln, wenn Taste nicht gedrückt
sbic pind,1
ori tasten,1<<4
t1:
cpi tasten,0b11111000
brne t2
sbr flags,1<<key1
t2:
cpi tasten,0b10001111
brne is_end
sbr flags,1<<key2
Hier wird auf die Tests der Carry Flags verzichtet. Man
erkennt die
negative Flanke, wenn man die Nibbles bei den CPI Befehlen separat
betrachtet. Bei dieser Variante reichen wieder
12 Befehle, außerdem braucht temp1 nicht gepusht werden. Die Routine
geht davon aus, das der Anwender nur eine Taste gleichzeitig drückt.
Das muss nicht unbedingt schlecht sein. Drückt man also beide Tasten
gleichzeitig, passiert gar nichts.
Die 3 und 4 Bit Varianten Funktionieren zwar auch sehr sicher, jedoch
habe ich in meinen Projekten immer die 8 Bit Variante mit separatem
Register pro Taste bevorzugt. Einer der Gründe ist die sehr einfache
Auswertung von
kurzen, langen und doppelten Tastendrücken, die ich gerne für spezielle
Programmfunktionen benutze. Beispiel: Drehgebertaster wird durch langen
Tastendruck zum Menüaufruf Uhr stellen benutzt.
Kurzer, langer, doppelter Tastendruck
Einen Nachteil, beim auswerten eines Doppelclicks möchte ich nicht verschweigen. Der kurze Tastendruck wird um die Delayzeit des doppelten Tastendrucks verzögert. Egal, ob man die Auswertung in der ISR vornimmt und ein Flag setzt, oder (wie ich) im Hauptprogramm. Will ich sofort auf einen kurzen Tastendruck reagieren, kann ich keinen Doppelclick detektieren. Zumindest nicht ohne vorher die Routine für den kurzen Tastendruck aufzurufen. Deshalb ist die Delayzeit immer ein Kompromiss zwischen der Verzögerung des kurzen Tastendrucks und der bequemen Bedienbarkeit des Doppelclick.
Neu hinzugekommen ist das Register tick, welches in der ISR heruntergezählt wird. Dieses wird für das Delay der langen Tastendrücke benötigt.
Beispiel 4
;Beispielroutine
4 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def taste
= r3
.def tick
= r4
.def temp1
= r16
.def flags
= r23
.equ key1
= 0
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt, alla 8 mS
;------------------------------------------------------
in
stat,SREG
;Status sichern
dec
tick
;Jede ISR herunterzählen
;----------------------------------------------
;Taste Entprellen
;----------------------------------------------
lsl
taste
;Eine Null in Taste schieben
sbic
pind,0
;Wenn die Taste gedrückt ist, überspringen
inc
taste
;Ansonsten die Null in eine 1 wandeln
brne is_end
brcc is_end
sbr flags,1<<key1
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
is_end: out
SREG,stat
;str
wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi
temp1, RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
ldi
temp1,1<<pd6|1<<pd5|1<<pd4
out
DDRD,temp1
;LED Anschlüsse auf Ausgang
out
PortD,temp1
;Alle LEDs erstmal aus
sbi
PortD,0
;Pullup für Taste einschalten
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256,
ISR ca. alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Tastenflag abfragen
;----------------------------------------------
main: sbrc
flags,key1
;Überspringen, wenn nicht gedrückt
rjmp
kld
;Sprung zu kurz lang doppel Auswertung
rjmp main
;----------------------------------------------
;Test auf kurz, lang oder Doppelklick
;----------------------------------------------
kld:
cbr
flags,1<<key1
;Tastenflag löschen
rcall
wait
;300mS warten
sbrc
flags,key1
;Wenn innerhalb 300mS das Flag gesetzt ist..
rjmp
doppel
;.. war es ein Doppelklick
tst
taste
;Wenn kein Doppelclick und Taste = 0, wurde die Taste gehalten
breq lang
rjmp
kurz
;Ansonsten war kurz gedrückt
;----------------------------------------------
;Reaktion wenn Taste 2* kurz gedrückt
;----------------------------------------------
doppel: cbr
flags,1<<key1
;Tastenflag löschen
sbi
pind,4
;LED an pd4 toggeln
rjmp main
;----------------------------------------------
;Reaktion wenn Taste lang gedrückt
;----------------------------------------------
lang: sbi
pind,5
;LED an pd5 toggeln
rjmp main
;----------------------------------------------
;Reaktion wenn Taste 1* kurz gedrückt
;----------------------------------------------
kurz: sbi
pind,6
;LED an pd6 toggeln
rjmp main
;----------------------------------------------
;Delay
;----------------------------------------------
wait: ldi
temp1,300/8
;300 mS
warten
mov
tick,temp1
;Tick
laden und in der ISR herunterzählen lassen
w1:;
tst
taste
;Beim loslassen der Taste, Delay verlassen
;
breq
w2
;Bei Doppelklickauswertung nicht verwenden
;
ret
w2:
tst
tick
brne
w1
;Warten bis Tick =0
ret
;------------------------------------------------------
;Ende
;------------------------------------------------------
Ich habe oben in Beispiel 4 eine Verzögerung von 300 Millisekunden angesetzt. In dieser Zeit kann ich noch ohne Anstrengung einen Doppelklick ausführen. 300 mS sind zwar recht kurz, aber bei einem kurzen Tastendruck doch noch bemerkbar. Außerdem kommt es darauf an, wie die Taste später montiert werden soll. An einer Frontplatte die Vertikal bedient wird, kann ein Doppelklick in 300 mS nur schwer ausgeführt werden. Hier hilft nur Ausprobieren.
Im Hauptprogramm.
Zuerst wird wie üblich auf einen kurzen Tastendruck geprüft.
Ist dieser erfolgt, springt das Programm zu Label kld
wo nach löschen des Flankenbit key1
300 Millisekunden gewartet wird. Danach wird key1
erneut geprüft. Ist dieses gesetzt, wurde die Taste innerhalb der 300
mS erneut
gedrückt und es erfolgt ein Sprung zum Label Doppel, in welcher zur
Kontrolle, die LED an PD4 getoggelt wird.
Wurde der Sprung nicht ausgeführt, wird das Register Taste auf Null
getestet. War das Flag also Null UND die 8 Bit im Register taste
sind
alle 0 handelt es sich um einen langen Tastendruck und es wird
entsprechend verzweigt.
Trifft beides nicht zu, war es ein einzelner, kurzer Tastendruck und
wir haben die 300 mS umsonst vertrödelt .
Nur kurzer und langer Tastendruck
Benötigt man nur einen kurzen und langen Tastendruck, kann in
der
Delayroutine durch hinzufügen der 3 Auskommentierten Befehle,
die Verzögerung für den kurzen Tastendruck minimiert werden. Hier wird
einfach auf das Loslassen der Taste geprüft und bei einem kurzen
Tastendruck die Delayroutine vorzeitig verlassen. In diesem Fall, kann
der lange Tastendruck auch ruhig mit einer Sekunde oder mehr definiert
werden. Hierzu einfach ldi
temp1,1000/8 für eine Sekunde eintragen.
2 Tasten gleichzeitig drücken
Eines vorweg. Für den µC gibt es kein gleichzeitig. Für den
Anwender
unserer Hardware schon. Um dem Tiny beizubringen, was für uns das
gleichzeitige drücken von 2 Tasten bedeutet, soll es hier gehen.
So können mit 2 Tasten, 3 Programmfunktionen aufgerufen werden, wie Z.B
eine Uhr stellen mit Plus und Minustaste und beide Tasten gleichzeitig
zum übernehmen
Beispiel 5
Im Interrupt werden die schon bekannten Entprellroutinen,
hintereinander für jede Taste separat ausgeführt.
;Beispielroutine
5 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def taste1
= r3
.def taste2
= r4
.def tick
= r5
.def temp1
= r16
.def flags
= r23
.equ key1
= 0
.equ key2
= 1
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt, alle 8 mS
;------------------------------------------------------
in
stat,SREG
;Status
sichern
dec
tick
;Zeit - 8mS
;----------------------------------------------
;2 Tasten Entprellen
;----------------------------------------------
lsl
taste1 ;Eine Null in Taste schieben
sbic
pind,0 ;Wenn die Taste gedrückt ist,
überspringen
inc
taste1 ;Ansonsten die Null in eine 1
wandeln
brne
t2 ;Wenn nicht die letzten 8 ISRs gedrückt war,
Sprung
brcc t2 ;Wenn vor 9
ISRs gedrückt war, Sprung
sbr flags,1<<key1
;Tastenflag (Flanke) setzen
t2:
lsl taste2
sbic pind,1
inc taste2
brne is_end
brcc is_end
sbr flags,1<<key2
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
is_end: out
SREG,stat
;Status wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi temp1,
RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
ldi
temp1,1<<pd6|1<<pd5|1<<pd4
out
DDRD,temp1
;LEDs auf Ausgang
ldi
temp1,1<<pd6|1<<pd5|1<<pd4|1<<pd1|1<<pd0
out
PortD,temp1 ;LEDs
aus, Pullups
ein
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256, ISR ca. alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
clr flags
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Tastenflags abfragen
;----------------------------------------------
main: mov
temp1,flags
;Alle
Flags nach temp1 holen
andi
temp1,1<<key1|1<<key2;Nur Tasten stehen
lassen
breq
main
;Sprung, wenn keine Taste gedrückt wurde
rcall
wait
;Dem User kurz Zeit lassen, gleichzeitig
2 Tasten zu drücken ;-)
mov
temp1,flags
;Flags updaten
andi
temp1,1<<key1|1<<key2 ;Nur Tasten stehen
lassen
cbr
flags,1<<key1|1<<key2 ;Flags löschen
cpi temp1,1<<key1
breq
toggle_LED1
;Sprung wenn Taste 1 gedrückt
cpi temp1,1<<key2
breq
toggle_LED2
;Sprung wenn Taste 2 gedrückt
rjmp
toggle_LED3
;Können
nur noch beide Tasten sein
;----------------------------------------------
;Reaktion wenn Taste gedrückt
;----------------------------------------------
Toggle_LED1:
sbi
pind,4
;LED toggeln
rjmp main
Toggle_LED2:
sbi
pind,5
;LED toggeln
rjmp main
Toggle_LED3:
sbi
pind,6
;LED toggeln
rjmp main
;----------------------------------------------
;Delay
;----------------------------------------------
wait: ldi
temp1,100/8
;100 mS vertrödeln
mov
tick,temp1
;In
der ISR herunterzählen lassen
w1:
tst tick
brne
w1
;
ret
;------------------------------------------------------
;Ende
;------------------------------------------------------
Am Ende des Codes befindet sich das Unterprogramm wait,
welches beim Aufruf 100mS wartet. Damit ist auch gleich festgelegt das
2 Tasten innerhalb 100mS gedrückt, als gleichzeitig gelten sollen. In
der main
wird zunächst gewartet, bis irgendeine Taste gedrückt ist.
Ist das geschehen, werden nochmals 100mS gewartet und die Tastenflags
erneut gelesen. Nach dem Ausmaskieren der unrelevanten Flags, stehen
nun die Tastenflags in temp1 bereit . Sind beide Flags gesetzt (Inhalt
von Temp1=3) wird zur Kontrolle die LED an PORTD3 getoggelt.
Tasten
mit Autorepeat
Die Repeatfunktion einer PC-Tastatur kennt wohl jeder. Man
drückt in
einem Texteditor z.B. die Leertaste und ein Leerzeichen wird sofort
eingefügt.
Hält man die Taste für eine gewisse Zeit gedrückt, erscheinen in kurzen
Intervallen solange Leerzeichen, bis man die Taste wieder loslässt.
Sowas wollen wir für unsere Hardwarebasteleien natürlich auch haben .
Bestes Beispiel ist das Stellen einer Uhr mit einer Plus und Minustaste
über LCD oder LED- Display.
Im Gegensatz zu der Abfrage von langen Tastendrücken, läuft
der
Autorepeat vollständig in der ISR. Die Routine benötigt 2
Zeitvorgaben die am Programmanfang mit .equ zugewiesen werden. Dabei
handelt es sich um rep_lang und rep_kurz. Rep_lang ist die Zeit die bei
niedergedrückten Taste vergeht, bis der Autorepeat aktiv wird. Hier
habe ich 600 Millisekunden eingetragen. Bei rep_kurz, wird die Zeit der
Intervalle festgelegt, in der nach aktivierter Repeatfuktion, das
Flankenbit gesetzt wird.
Beispiel 6
Neu hinzugekommen ist der Zähler repeat
den ich hier mit r17 definiert habe. Man könnte auch ein niederes
Register hernehmen, müsste dann aber den ldi Befehl umgehen indem man
temp1 pusht oder die Zeitwerte als Konstante in der Init in andere
niedere Register schreibt und diese dann in der ISR mit mov überträgt.
Der Zähler repeat
wird in der ISR bei gedrückter Taste heruntergezählt. Ist er bei Null
angekommen wird er neu geladen und ein Tastendruck generiert. Ist keine
Taste gedrückt wird er mit der langen Zeit geladen.
;Beispielroutine
6 zur Tastenentprellung by juergen@avr-projekte.de
.include "tn2313def.inc"
;------------------------------------------------------
;Register
;------------------------------------------------------
.def stat
= r2
.def taste
= r3
.def temp1
= r16
.def repeat
= r17
;------------------------------------------------------
;Flagregister
;------------------------------------------------------
.def flags
= r23
.equ key1
= 0
.equ
rep = 7
;------------------------------------------------------
;Repeatzeiten bei einer ISR alle 8 mS
;------------------------------------------------------
.equ
rep_lang =
600/8 ;600mS
.equ
rep_kurz =
120/8 ;120mS
;------------------------------------------------------
;Sprungvektoren
;------------------------------------------------------
.org 0
;Reset
rjmp init
.org
OVF0addr
;Timer0 Overflow Interrupt
;------------------------------------------------------
;Timer Interrupt, alle 8 mS
;------------------------------------------------------
in
stat,SREG
;Status sichern
;----------------------------------------------
;Tastenabfrage mit Repeat
;----------------------------------------------
lsl
taste ;Eine Null in
Taste schieben
sbic
pind,0
;Wenn Taste gedrückt, die Null stehen lassen
inc
taste
;Ansonsten die Null in eine 1 wandeln
brne
taste2
;Sprung, wenn Taste nicht gedrückt
sbr
flags,1<<rep ;Repeat am Ende der ISR nicht zurücksetzen
dec
repeat ;Repeat herunterzählen
brne
rp1 ;Wenn Repeatzeit <0, auf neuen
Tastendruck
testen
ldi
repeat,rep_kurz ;Repeatzeit abgelaufen, kurz
laden
sec
;und Taste durch Repeat
drücken
rp1: brcc
Taste2
sbr
flags,1<<key1
;Taste
gedrückt, Flag setzen
;----------------------------------------------
;Weitere Tasten (oder anderes)
;----------------------------------------------
Taste2: nop
;----------------------------------------------
;Interrupt Ende
;----------------------------------------------
is_end: sbrs
flags,rep
;Wurde Repeat runtergezählt?
ldi
repeat,rep_lang ;Wenn nicht,
Repeatzeit lang laden..
cbr
flags,1<<rep ;Repeatflag für die
nächste Runde löschen
out
SREG,stat
;Statusregister wieder zurück
reti
;------------------------------------------------------
;Init
;------------------------------------------------------
init: ldi temp1,
RAMEND
;Stack
out
SPL, temp1
;----------------------------------------------
;Ports init
;----------------------------------------------
ldi
temp1,1<<pd6|1<<pd5|1<<pd4
out
DDRD,temp1
;LEDs auf Ausgang
ldi
temp1,1<<pd5|1<<pd4|1<<pd0
out
PortD,temp1 ;Pullup für Taster, LED an PD6 ein.
;----------------------------------------------
;Timer init
;----------------------------------------------
ldi
temp1,1<<cs02
;TC0/256, ISR ca.
alle 8mS
out
TCCR0,temp1
ldi
temp1,1<<TOIE0
;Timer0 overflow interrupt
out
TIMSK,temp1
sei
;------------------------------------------------------
;Main
;------------------------------------------------------
;----------------------------------------------
;Tastenflag abfragen
;----------------------------------------------
main: sbrs
flags,key1
;Wenn Taste gedrückt, überspringen
rjmp main
;----------------------------------------------
;Reaktion wenn Taste gedrückt
;----------------------------------------------
cbr
flags,1<<key1
;Tastenflag löschen
sbi
pind,6
;LED an pd6 und 5 toggeln
sbi
pind,5
;Da PD6 in der Init gesetzt wird, blinken die LEDs abwechselnd
rjmp
main
;Neue Runde
;------------------------------------------------------
;Ende
;------------------------------------------------------
In der ISR sind in der Tastenabfrage die ersten 4 Befehle
schon aus Beispiel 2
bekannt. Ist der Tiny an sbr
flags,1<<rep angelangt,
ist die Taste gedrückt und es wird der Repeat aktiviert. Das setzten
des
rep
Flags bewirkt am Ende der ISR, das der Repeatzähler nicht mit der
langen Reapeatzeit (600mS) geladen wird.
Durch dec
repeat,
werden 8 mS vom Zähler abgezogen. Ist der Zähler abgelaufen, wird
dieser gleich mit rep_kurz (120 mS) neu geladen. Danach wird mit sec
das Carry gesetzt um den nachfolgenden brcc
Befehl auszuhebeln. Natürlich könnte hier auch ein rjmp stehen um das
Flankenbit zu setzten. Am Ende der ISR, wird durch das rep Flag
geprüft, ob irgend eine Taste, das rep Flag gesetzt hat. Danach wird
das Repeatflag für die nächste ISR zurückgesetzt. Das könnte auch
am den Anfang der ISR passieren.
Im Hauptprogramm wird die Autorepeat-Entprellroutine genauso behandelt,
wie die Routinen mit Flankenerkennung (ab Beispiel 2).
Tipp am Schluss
Alle Entprelloutinen in den bisherigen Beispielen, fangen mit den ersten 3 Befehlen so an:
lsl
taste
sbic pind,0
inc taste
Mit dieser Befehlsfolge können mehrere Taster sowohl an
beliebigen
Pins als auch an verschiedenen Ports abgefragt werden. Hat man jedoch
mehrere Taster in einer Reihenfolge
am selben Port, so bietet sich an, den Port in ein Register zu lesen
und
direkt über das Carryflag in die Tastenregister zu schieben. Das würde
pro Taste nochmals einen Befehl sparen. Temp1 muss natürlich gepusht
werden, es sei den man hat ein Arbeitsregister für die ISR reserviert.
Beispiel mit 3 Taster an Portd 0..2
in temp1,pind
lsr
temp1
;Taste an PinD0 ins Carry
rol
Taste1 ;und in Bit0
von Taste1 schieben
brne
t2
;Wenn nicht die
letzten 8 ISRs gedrückt
war, Sprung
brcc
t2
;Wenn vor 9 ISRs gedrückt war, Sprung
sbr
flags,1<<key1;Tastenflag (Flanke) setzen
t2: lsr temp1
;Taste an
PinD1 ins Carry
rol Taste2
brne t3
brcc t3
sbr
flags,1<<key2
t3: lsr
temp1
;Taste an PinD2 ins Carry
rol Taste3
...
...
Im Beispiel oben, wird PIND
nur noch einmal gelesen und temp1
verteilt dann die gelesenen Bits, in die richtigen Tastenregister.
Nach dem übertragen von PIND
nach temp1,
wird PIND0
durch den lsr
Befehl ins Carry transportiert. Der nachfolgende rol
Befehl
schiebt die Taste weiter in Bit 0 von Taste1.
Gleichzeitig wird Bit 7 (das älteste Bit) ins Carry geschoben, so das
wir wie in den Beispielen oben, weiter verfahren können.
Das ganze würde auch anders herum Funktionieren, wenn z.B. 3 Tasten an
PortD 5..7 angeschlossen währen. Anstatt lsr temp1
würde man dann lsl
oder
rol temp1 verwenden und zunächst Taste3
bedienen.
Wichtig ist im Endeffekt nur, das die Pins an denen die Taster
angeschlossen sind, später ins richtige Register geschoben werden.
Ich wünsche euch eine prellfreie Zeit,
Jürgen