Dieses Modul wird verwendet bei der immer wiederkehrenden Aufgabe der Taster-Abfrage. Der C-Code ist projekt- und hardwareunabhängig – zumindest was die Tasterauswertung selbst angeht. Die Erzeugung einer Zeitbasis ist natürlich systemabhängig.
Ebenso wie beim Countdown-Zähler und der DCF77-Software wird die Arbeitsroutine für die zu erledigende Aufgabe im 10 Millisekunden-Rhythmus ausgeführt. Diese langsame Abtastrate liefert die Entprellung der Taster frei Haus.
In taster.h wird die Struktur taste_t definiert. Im Hauptprogramm legt man von diesem Typ so viele Variablen an, wie man Taster abzufragen hat. Ein Beispiel dazu findet sich ganz unten.
Die Struktur taste_t hat folgende, für den Anwender interessante Komponenten:
- .mode
- Dieser Eintrag legt das Verhalten des jeweiligen Tasters fest. Soll jedes Mal gemeldet werden, wenn der Taster gedrückt wird, schreibt man KM_SHORT in dieses Feld. Soll unterschieden werden zwischen langen und kurzen Tastendrucken, schreibt man KM_LONG hinein. Soll der Taster eine Auto-Repeate-Funktion haben, ist der Wert KM_REPEAT.
- .key
- Der Wert dieser Komponente wird nach taster kopiert, wenn diese Taste gedrückt wurde. Bei einem langen Tastendruck und falls .mode=KM_LONG ist, wird der Wert .key+KEY_LONG nach taster geschrieben.
Die Routine, die sich um die Tasterabfrage kümmert, heisst get_taster. Sie hat zwei Parameter. Der erste Parameter ist die Adresse des Tasters, der abgefragt werden soll. Der zweite Parameter informiert die Funktion über den Zustand des Ports, an den der Taster angeschlossen ist. Ist der Taster gedrückt, wird als zweiter Parameter 0 übergeben. Ist er nicht gedrückt, wird ein Wert ungleich 0 übergeben. get_taster hat keinen Rückgabewert, stattdessen wird gegebenenfalls die globale Variable taster auf einen neuen Wert gesetzt.
Verwendet man zwei Taster, dann könnte eine Deklaration so aussehen:
taste_t tasten[] = { { .key = 1, .mode = KM_SHORT }, // erster Taster { .key = 2, .mode = KM_REPEAT } // zweiter Taster (auto-widerholen) };
Es wird also ein Arrey mit zwei Taster-Strukturen angelegt und initialisiert. Der Aufruf von get_taster sieht damit folgendermassen aus (falls der erste Taster an Port B1 und der zweite an Port D3 angeschlossen ist):
get_taster (& tasten[0], PINB & (1 << PB1)); get_taster (& tasten[1], PIND & (1 << PD3));
Der Codeverbrauch des Moduls liegt bei ca. 130 Bytes. Werden einfach nur Tastendrucke ohne Sonderfunktion empfangen, also keine langen Drucke oder Auto-Repeate (TASTER_SAME_MODE=KM_SHORT), dann sinkt der Codeverbrauch auf ca. 40 Bytes.
taster.h
Hier muss eigentlich nichts angepasst werden, es sei denn die Zeiten ab wann ein Tastendruck als "lang" gilt oder die Wiederholrate für den Auto-Repeate. Die Stellen, die gegebenenfalls anzupassen sind, sind dokumentiert und leicht zu finden.
#ifndef TASTER_H #define TASTER_H #include <stdint.h> // 'taster' bekannt machen // Wurde keine Taste gedrückt, dann enthält diese Variable den Wert 0. // Wurde eine Taste gedrückt, enthält diese Variable die Komponente // .key des gedrückten Tasters (bzw. den um KEY_LONG vergrößerten // von .key, wenn es sich um einen langen Tastendruck handelt). // Es werden nur dann neue Tastendrucke angenommen, wenn taster=0 ist. // Hat man in der Anwandung alse ein Taster-Ereignis verarbeitet, // muss man taster=0 setzen. 'taster' berhält sich also wie ein // Tastaturpuffer der Länge 1. extern volatile uint8_t taster; // Die möglichen Betriebsmodi eines Tasters enum { KM_SHORT, KM_LONG, KM_REPEAT, KM_SKIP }; // Ein Taster typedef struct { // Betriebsmodus // KM_SHORT einfach nur Tastendruck melden // KM_LONG Tastendruck mit Unterscheidung lang/kurz // KM_REPEAT bleibt die Taste gedrückt, so wird immer wieder der gleiche // Taster-Wert geliefert, ähnlich wie bei einer PC-Tastatur // KM_SKIP Verwendet in get_taster() den vormaligen Wert von 'tast' für // den Fall, daß momentan kein Wert verfügbar ist uint8_t mode; // Wird der Taster gedrückt, wird dieser Wert in 'taster' geschrieben. // bzw. bei einem langen Tastendruck .key + KEY_LONG // Dieser Wert muss ungleich Null sein. uint8_t key; // Wird intern gebraucht uint8_t delay; uint8_t old; } taste_t; // Alle 10ms (oder so) aufrufen extern void get_taster (taste_t * const, uint8_t); // Zeiten (in Einheiten von 10 Millisekunden) für: // Anfangsverzögerung bis Auto-Repeate loslegt (.mode = KM_REPEAT) #define KEYREPEAT_DELAY 50 // Zeitverzögerung zwischen zwei Auto-Repeates (.mode = KM_REPEAT) #define KEYREPEAT 30 // Ab dieser Dauer ist ein Tastendruck nicht mehr "kurz", // sondern "lang" (.mode = KM_LONG) #define KEYDELAY_LONG 50 // Wird auf .key draufaddiert, wenn es sich bei (.mode = KM_LONG) // um einen langen Tastendruck handelt. // Bei mehr als 4 Tastern muss dieser Wert vergrößert werden! #define KEY_LONG 4 #endif /* TASTER_H */
taster.c
Diese Datei braucht nicht angepasst zu werden.
#include "taster.h" volatile uint8_t taster; #define PRESSED (2|1) #define PRESS (0|1) #define RELEASE (2|0) // Alle 10ms aufrufen. Auf diese Zeitbasis von 10ms beziehen sich die // Defines in taster.h. // 'ptast' ist die Adresse des auszuwertenden taste-Objekts. // 'tast' ist der (Port-)Zustand des Tasters (low-aktiv): // == 0 wenn der Taster gerade gedrückt (aktiv) ist. // != 0 wenn der Taster gerade nicht gedrückt (inaktiv) ist. // Ausgabe ist die Variable 'taster'. void get_taster (taste_t * const ptast, uint8_t tast) { // Nichts tun, falls die Anwendung den Wert aus 'taster' noch nicht // abgehohlt hatden if (taster) return; // ist Taster gedrückt? (low aktiv) tast = !tast; // alten Taster-Zustand lesen uint8_t taster_old = ptast->old; uint8_t mode = ptast->mode; #ifdef TASTER_SAME_MODE // Let GCC throw away superfluous code if there is some // extra knowledge. mode = TASTER_SAME_MODE; #endif // TASTER_SAME_MODE // skip? // Für Sonderanwendung: Wenn kurzzeitig kein sinnvoller Wert // am Taster-Port anliegt, wird so kein Phanton-Tastendruck // registriert und die Zeit (delay) läuft dennoch weiter. if (KM_SKIP == mode) tast = taster_old; // Taster-Zustand merken ptast->old = tast; // state = 1: eben gedrückt // state = 2: eben losgelassen // state = 3: bleibt gedrückt uint8_t state = taster_old << 1; state |= tast; tast = 0; uint8_t delay = ptast->delay; uint8_t key = ptast->key; if (state == PRESS) { // Taste wurde eben gedrückt if (mode != KM_LONG) tast = key; delay = 0; } else if (state == PRESSED) { // Taste bleibt gedrückt if (delay < 0xfe) delay++; } else if (state == RELEASE) { // Taste wurde losgelassen if (mode == KM_LONG && delay != 0xff) tast = key; } if (mode == KM_LONG) { // Langer Tastendruck if (delay == KEYDELAY_LONG) { tast = KEY_LONG + key; delay = 0xff; } ptast->delay = delay; } else if (mode == KM_REPEAT) { // Auto-Repeate if (delay == KEYREPEAT_DELAY) { tast = key; delay = KEYREPEAT_DELAY - KEYREPEAT; } ptast->delay = delay; } // Tasten-Code speichern taster = tast; }
main.c
Ein Beispiel wie es aussehen könnte auf einem ATtiny2313. Es wird Timer1 so grogrammiert, daß er 5000 IRQs pro Sekunde auslöst. In der Interrupt-Routine wird die Anzahl der IRQs mitgezählt, und wenn 10 Millisekunden voll sind, wird get_taster() aufgerufen. Da zwei Taster angeschlossen sind, wird die Funktion auch zweimal aufgerufen – einmal für jeden Taster. Dabei werden die Aufrufe nicht in ein und derselben IRQ erledigt, sondern auf zwei aufeinanderfolgende IRQs verteilt.
Die Hauptschleife deutet an, wie man die Tasterabfrage macht und die Tastenwerte aus taster abhohlt.
Im Beispiel geschieht die Verarztung der Taster-Routine(n) aus der Timer-ISR heraus. Ebenso ist vorstellbar, den Aufruf in die Hauptschleife auszulagern und die ISR so zu entlasten. In diesem Falle würde man in der ISR nur einen Merker setzen (oder hochzählen), daß 10 Millisekunden voll sind, und dies in der Hauptschleife abhandeln.
Dies ist ein Ausschnitt aus dem Quellcode zu meiner Eieruhr.
#include <avr/io.h> #include <avr/interrupt.h> #include "taster.h" #define IRQS_PER_SECOND 5000 #define IRQS_PER_10MS (IRQS_PER_SECOND / 100) static void timer1_init (void); // Ein paar Werte festlegen um "magische Zahlen" aus dem Code rauszuhalten. enum { NO_KEY = 0, KEY_EINER = 1, KEY_ZEHNER = 2, KEY_EINER_L = KEY_EINER + KEY_LONG, KEY_ZEHNER_L = KEY_ZEHNER + KEY_LONG }; // Zwei Tasten, .mode ist KM_LONG taste_t tasten[] = { [0] = { .key = KEY_EINER, .mode = KM_LONG } , [1] = { .key = KEY_ZEHNER, .mode = KM_LONG } }; ISR (SIG_OUTPUT_COMPARE1A) { static uint8_t interrupt_num_10ms; uint8_t irq_num = interrupt_num_10ms; // interrupt_num_10ms erhöhen und mit Maximalwert vergleichen if (++irq_num == IRQS_PER_10MS) { // 10 Millisekunden sind vorbei // interrupt_num_10ms zurücksetzen irq_num = 0; } // Alle 10ms die Taster abfragen (ginge auch zusammen, mach ich aber net ;-) if (irq_num == 0) get_taster (& tasten[0], IS_SET (PORT_EINER)); if (irq_num == 1) get_taster (& tasten[1], IS_SET (PORT_ZEHNER)); interrupt_num_10ms = irq_num; } void timer1_init() { // Timer1 ist Zähler: Clear Timer on Compare Match (CTC, Mode #4) // Timer1 läuft @ F_CPU: prescale = 1 TCCR1A = 0; TCCR1B = (1 << WGM12) | (1 << CS10); // OutputCompareA für gewünschte Timer1 IRQ-Rate OCR1A = (unsigned short) ((unsigned long) F_CPU / IRQS_PER_SECOND-1); // OutputCompare-Interrupt A für Timer1 TIMSK = (1 << OCIE1A); } int main() { // Taster sind Input mit PullUp (low active) SET (PORT_EINER); SET (PORT_ZEHNER); // Timer1 macht IRQs timer1_init(); // IRQs zulassen sei(); // Hauptschleife while (1) { uint8_t tast = taster; if (tast) taster = NO_KEY; if (tast == KEY_EINER) { // Taste "Einer" (kurz) } if (tast == KEY_EINER_L) { // Taste "Einer" (lang) } if (tast == KEY_ZEHNER) { // Taste "Zehner" (kurz) } if (tast == KEY_ZEHNER_L) { // Taste "Zehner" (lang) } ... } // main Loop }