Amateurfunk verbindet die Welt

Kurz/Lang-Taster

Erstellt: DL6GL, 25.10.2013, letzte Änderung 06.12.2013

« Taster am ADC 
TOP » Taster mit Autorepeat

Kurz/Lang-Taster mit Timer-Interrupt (1)

Soll die Anzahl der Tasten auf einige wenige beschränkt bleiben, obwohl mehr Funktionen damit aufzurufen sind, müssen Sonderfunktionen her. In den nächsten beiden Kapiteln soll zwischen kurzem und langem Tastendruck unterschieden werden. Damit bekommt ein Taster zwei Funktionen.

Da sich die Dannegger-Methode (s. oben) als wirklich kugelsicher erwiesen hat, was die Entprellung angeht, wurde versucht, diese weiterzuentwickeln. Das aber wieder mit BASCOM. Wer sich auf C versteht, findet in [2] womöglich elegantere Methoden.

Der erste Anlauf hierzu ist der, in der Do...Loop eine Warteschleife vorzusehen, die so lange läuft, bis ein Taster wieder losgelassen wird. Übersteigt die Wartezeit einen bestimmten Wert, war es ein langer Tastendruck, sonst ein kurzer.

Die Timer0-isr ist die die gleiche wie in der oben gezeigten Dannegger-Methode.

Eine der Ausgabewerte ist die Statusvariable "bytKey_state.Keyx", die mit "1" zurückgegeben wird, solange der Taster "Keyx", x=0...7, gedrückt ist. Nochmal zur Erläuterung: Der aktuelle Zustand aller (max. 8) Taster am Key-Port wird im Byte " bytKey_state" gezeigt. Jeder einzelne Taster ist einem Bit (0...7) des Bytes zugeordnet. Wurde der Taster losgelassen, wird "0" zurückgegeben. Also fragen wir diese Variable in einer Warteschleife ab, während die Timer0_isr die Taster alle 12,8 msec abfragt. Die Funktion hierzu:

Function KeyPressLong(byVal KeyNum As Byte) As Byte
'Check if Key Key_Port.KeyNum is pressed short or long
'KeyPressLong = 1: pressed long (~ 1 sec), = 0: short

bytTimer0_runs = 0
Do 'Wait for key to be released
Incr bytTimer0_runs
Waitms 10
Loop Until bytKey_state.KeyNum = 0 Or bytTimer0_runs >= KeyCounter

If bytTimer0_runs >= KeyCounter Then
KeyPressLong = 1
bytTimer0_runs = 0 'key was pressed long
Else
KeyPressLong = 0 'key was pressed short
End If
End Function

In der Do...Loop wird nach jeweils 10 msec geprüft, ob entweder der Taster, lfd. Nr. KeyNum, losgelassen wurde (bytKey_state.KeyNum = 0) oder der Zähler bytTimer0_runs den Grenzwert KeyCounter überschritten hat. Wenn letzteres der Fall ist, war es ein langer Tastendruck, sonst ein kurzer.

Im Hauptprogramm sieht es dann so aus:

Do                                                'Start of main program ******

'Disable/enable timer0 may be omitted, test it
If bytKey_state <> bytKey_save Then 'Any key was pressed
'Disable timer0 'Stop Timer0 for display
'Check Key Key0 short or long
If bytKey_state.Key0 = 1 Then 'key0 is pressed
bytTmp0 = KeyPressLong(Key0)
Call ClearLCDLine(1)
If bytTmp0 = 0 Then
LCD "Key 0 short"
Else
LCD "Key 0 long"
bitKey_Long = 0
End If
End If

'Check Key Key1 pressed or not
If bytKey_state.Key1 = 1 Then 'key1 is pressed
Call ClearLCDLine(1)
LCD "Key 1"
End If

....

bytKey_press = 0 'reset Key_press
'Enable Timer0 'Restart Timer0
End If

Loop 'End of main program *******

Die Tasterabfrage erfolgt nur, wenn irgend ein Taster betätigt wurde. Wenn wie hier der Taster0 gedrückt ist (bytKey_state.Key0 = 1), wird die Warteschleife in der Function KeyPressLong durchlaufen.

Das am Key0 gezeigte Beispiel für die Kurz/Lang-Erkennung kann auf jeden verwendeten Taster angewandt werden. Der Timer0 muss nicht angehalten werden (Disable/Enable Timer0).

Wie es besser geht, vor allem ohne die Waits in der Abfrage, wollte ich dann noch rausfinden. Nächster Abschnitt.


Kurz/Lang-Taster mit Timer-Interrupt (2)

Update (V1.04) vom 06.12.2013

Rein aus Neugier wollte ich dann doch mal wissen, ob die Kurz-Lang-Information auch ohne die Warteschleife in der Function KeyPressLong herauszubekommen ist. Die Waits in der Funktion gefielen mir nicht so recht.

Die ersten beiden Versuche (V1.02 mit Tastern an PortC, V1.03 mit Tastern an Port C und D) waren unbefriedigend, da in der Timer0_isr nur ein Taster auf kurz/lang abgefragt werden konnte.

Hier nun eine bessere Lösung (V1.04). Die funktioniert richtig gut, ohne das Programm mit Waits aufzuhalten. Die Taster, die eine kurze oder lange Betätigung erkennen sollen, werden mit einer Bitmaske festgelegt. Damit sind wieder acht Taster ohne großes Umprogrammieren möglich.

Den Kurz-/Langdruck für den zu untersuchenden Taster ermittelt die Function KeyPressedLong außerhalb der Timer0_isr. Sie stellt fest, ob nach einer festzulegenden Anzahl von Durchläufen der Timer0_isr der Taster noch gedrückt ist oder schon losgelassen wurde. Sie wertet das dem Taster zugeordnete Bit "bytKey_Long" (=0...7) im Byte "bytKey_state" aus. Dieses ist solange auf 1 gesetzt, wie der Taster gedrückt ist. Während dieser Zeit wird "bytTimer0_runs" in der Timer0_isr hochgezählt. Wird das Bit=0, wurde der Taster losgelassen. Nun kann "bytTimer0_runs" mit "bytTimer0_long" verglichen werden, um zu einer Kurz-/Lang-Aussage zu kommen.

Function KeyPressedLong(byVal bytKey_Long As Byte) As Byte
   'Check if key bytKey_Long (0...7) in Key_port is pressed short or long
   'Output: KeyPressedLong =  0: evaluating, key is still pressed
   '                          1: key is released, pressed short
   '                          2: key is released, pressed long

   If bytKey_state.bytKey_Long = 1 Then           'Key still pressed
      KeyPressedLong = 0
   Else                                           'Key released
      If bytTimer0_runs < bytTimer0_long Then     'short time elapsed
         KeyPressedLong = 1                       'Pressed short
      Else
         KeyPressedLong = 2                       'Pressed long
      End If
      bytTimer0_runs = 0
      bytKey_press.bytKey_Long = 0                'Reset pressed flag
   End If
End Function

Die Zeitmessung mit "bytTimer0_runs" wird in der Timer0_isr aufgesetzt. Dort wird der aktuelle Tastenzustand (gedrückt/losgelassen)  für festgelegte Tasten ausgewertet. Die Auswahl der Taster, die Kurz-/Lang-Funktion haben sollen, erfolgt mit der Bit-Maske "bytKey_mask", die hier mit dem Bitmuster "00000011" vorbesetzt ist, d.h. die Taster 0 und 1 erkennen kurzen oder langen Druck.

Timer0_isr:
'Interrupt Service Routine for Timer0 (Keys)
'got from Thomas Baer in microcontroller.net
'Original (ASM and C) by Peter Dannegger
'http://www.mikrocontroller.net/topic/tasten-entprellen-bulletproof
'Input: Key_port port connected to the keys
' bytKey_mask Bit pattern to mask the keys enabled for short/long
' Key number (0...7) to be checked for short/long
' e.g. "00000101" Keys 0 and 2 are short/long keys
'Output: bytKey_press bit pattern, key which changed from open to pressed
' Reset after evaluation in the main program
' Bits remain = 1 until resetted in the main program
' bytKey_state bit pattern, Key state, 1:pressed or 0:not pressed
' Bits = 1 while key is pressed, = 0: Key is released
' bytKey_save last state of bytKey_state before entering Timer0_isr
' Bits 0...7: keys 0...7 @ Key_port

Timer0 = Presettimer0

'...

bytIwr0 = bytKey_state AND bytKey_mask 'Mask out short/long keys
If bytIwr0 > 0 Then 'Short/long key still pressed
Incr bytTimer0_runs 'Clock is running...
End If

Return

In den unteren Zeilen werden die ausmaskierten Kurz-/Lang-Tasten beobachtet. Ist eine von ihnen gedrückt, d.h. das zugehörige Bit in bytKey_state ist 1, läuft die Uhr.

Ewig lange kann mit der Byte-Variablen bytTimer0_runs natürlich nicht gezählt werden. Bei einem Timing von z.B. 10 msec, mit dem die Timer0_isr aufgerufen wird, läuft bytTimer0_runs nach 0,01*256=2,56 sec. über und fängt wieder bei Null an. Die Function KeyPressedLong wertet dann den überlangen Tastendruck möglicherweise falsch aus. Mit einer Word-Variablen könnte länger gezählt werden und ggf. unterschieden werden, ob der Taster z.B. 1 sec. oder 5 sec. lang gedrückt wurde. Dann hätte man schon drei Tasterfunktionen: kurz, lang (z.B. 1 sec.) und ganz lang (z.B. 5 sec.).

Die Auswertung in der Do...Loop des Hauptprogramms sieht dann so aus:

Do                                                'Start of main program ******

   'Check Key Key_sto short or long -------------
   If bytKey_press.Key_sto = 1 Then               'Store Key was pressed
      Call ClearLCDLine(1)
      bytPressedLong = KeyPressedLong(Key_sto)
      Select Case bytPressedLong
        Case 1                                    'Key_sto pressed short
             LCD "Key_sto short"
        Case 2                                    'Key_sto pressd long
             LCD "Key_sto long"
      End Select
   End If

'...

'Check Key Key_dwn -------------
   If bytKey_state.Key_dwn = 1 Then               'Key_dwn is still pressed
      Call ClearLCDLine(1)
      LCD "Key_dwn"
   End If

'...

Loop                                              'End of main program *******

Nachfolgend noch einmal die Unterschiede zwischen den Bytes "bytKey_press" und "bytKey_state" am Beispiel der Tasters Key_sto und Key_dwn im obigen Programmausschnitt.

  1. bytKey_press.Key_sto: Ist gesetzt (=1), wenn der Taster "Key_sto" gedrückt wurde, unabhängig davon, ob er im Augenblick der Abfrage "If bytKey_press.Key_sto = 1" immer noch gedrückt ist. Nach der Auswertung im Programm muss dieses Flag mit "bytKey_press.Key_sto = 0" wieder gelöscht werden, sonst würde ein nochmaliges Drücken keine neue Reaktion bewirken.
  2. bytKey_state.Key_dwn: Ist gesetzt (=1), wenn der Taster "Key_dwn" im Augenblick der Abfrage "If bytKey_state.Key_dwn = 1" tatsächlich noch gedrückt ist. Hier gibt es keinen Reset. Den besorgt das Loslassen des Tasters.

Wie im Beispiel "Taster2_103" ist für den Fall, dass die Taster an verschiedenen AVR-Ports hängen, hier PC.0, PC.1, PD.6 und PD.7, die explizite Zuordnung auf den Key_port in der Timer0_isr vorgenommen;

'Keys at different ports
Key_port.Key_sto = PINC.0 'Key store @ PINC.0
Key_port.Key_up = PIND.7 'Key up @ PIND.7
Key_port.Key_dwn = PIND.6 'Key down @ PIND.6
Key_port.Key_enc = PINC.1 'Key encoder @ PINC.1

Etwas übersichtlicher, insbesondere in längeren Programmen, ginge es mit einer Sub. Da muss man für Anpassungen nicht so lange suchen. Die Sub wird dann in der Timer0_isr aufgerufen mit dem möglicherweise nicht unbedeutenden Nachteil, dass der Aufruf der Sub mehr Zeit braucht.

Stehen AVR-Pins eines einzigen Ports zur Verfügung, erfolgt die Zuordnung nicht zur Laufzeit in der Timer0_isr, sondern im Erklärungsteil, z.B. mit "Key_port Alias PINC" für den AVR-Port C.


Referenzen
[2] http://www.mikrocontroller.net/topic/48465


Download

Zu (1): taster2_101.zip

Zu (2): taster2_102.zip
            taster2_103.zip
            taster2_104.zip


« Taster am ADC 
TOP » Taster mit Autorepeat