Aus RN-Wissen.de
Wechseln zu: Navigation, Suche


Die Abfrage von Tastern, die an einen Mikrocontroller angeschlossen sind, wird sich üblicherweise in zwei Phasen aufteilen:

  1. Abfrage der Port-Werte, an denen die Taster angeschlossen sind
  2. Verarbeitung dieser Werte

Während Punkt 1 nur abhängig von der Hardware gelöst werden kann, ist Punkt 2 hardware-unabhängig formulierbar. Dieser Artikel legt seinen Fokus auf Punkt 2.

Überblick

Die Haupt-Nutzen, welche die hier vorgestellte Taster-Auswertung bietet, sind:

  • Abfrage, ob eine Taste gedrückt wurde und welche Taste dies war
  • Entprellung der Taster
  • Die Abfrage soll "nebenher" geschehen, um die Hauptanwendung nicht zu blockieren oder unnötig Zeit zu verschwenden.
  • Das Verhalten der einzelnen Taster soll zur Laufzeit des Programmes änderbar sein.
  • Evtl. unterscheiden, ob eine Taste kurz oder lange gedrückt wurde
  • Evtl. automatische Wiederholfunktion bei längerem Tastendruck (auto-repeat)
  • Da die Taster-Auswertung unabhängig von der Hardware ist, soll der C-Code dies auch sein: Er ist Standard-C (ANSI-C, ISO C90).

Was die Taster-Auswertung nicht unterstützt:

  • Erkennung gleichzeitigen Drückens mehrerer Taster (Shift-Funktion)
  • Doppeldrucke (vergleichbar dem Doppel-Click bei einer PC-Maus). Dieses Feature kann relativ leicht hinzu implementiert werden. Damit hätte man bis zu drei verschiedene Funktionen je Taster : kurz, lang und Doppel-Click.

Funktionsweise

Zunächst werden die Betriebsmodi für alle Taster eingestellt. In regelmässigen Zeitabständen werden dann die Port-Zustände der auszuwertenden Taster an die Funktion get_taster übermittelt, welche in der globalen Variablen taster das Ergebnis der Auswertung speichert.

Dadurch, daß diese Funktion nur etwa alle 10ms aufgerufen wird, wird automatisch eine Entprellung der Taster erreicht.

Im Hauptprogramm kann der Wert der Variablen taster abgefragt werden und abhängig davon Aktionen ausgeführt/ausgelöst werden. Nachdem taster gelesen wurde wird die Variable geleert und es kann ein weiterer Tastendruck empfangen werden.

Interface

Funktionen

void get_taster (const unsigned char num, unsigned char tast)
Diese Funktion muss in regelmässigen Abständen aufgerufen werden; sinnvoller Weise durch eine Interrupt Service Routine, die Funktion ist kurz genug dafür. Die Zeit zwischen zwei aufeinanderfolgenden Aufrufen von get_taster für die gleiche Tastennummer num wird unten auch als Tick bezeichnet.
Als Richtwert für die Dauer eines Ticks kann man sich an 10ms orientieren.
num
Dies ist die Nummer des Tasters: 0 <= num < NUM_TASTER.
tast
Der Wert dieses Taster, wie vom Input-Port gelesen. Die Behandlung kann für low-aktive oder high-aktive Taster erfolgen. Beispiel für avr-gcc: Taster No. 0 an Port B1:
get_taster (0, PINB & (1 << PB1));

Datenstrukturen

taster

volatile signed char taster;

Über diese Variable wird ein erfolgter Tastendruck mitgeteilt.

  • Gab es keinen Tastendruck, dann hat taster den Wert NO_TASTER.
  • Gab es einen (kurzen) Tastendruck, dann wird hier die Nummer der gedrückten Taste gespeichert.
  • Gab es einen langen Tastendruck – was nur sein kann, wenn der Mode des entsprechenden Tasters TM_LONG ist –, dann wird hier die Nummer der gedrückten Taste plus TASTER_LONG gespeichert.
  • Solange taster ungleich NO_TASTER ist, wird kein neuer Tastendruck entgegen genommen. Nach Verarbeitung eines Taster-Wertes muss daher taster wieder auf NO_TASTER zurückgesetzt werden! taster funktioniert also wie ein Tastaturpuffer, in welchen nur ein einziges Zeichen hineinpasst.

taste_t

typedef struct 
{
    ...

    /* Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT */
    unsigned char mode;
} taste_t;

Jeder Taster wird im Array tasten durch einen Eintrag repräsentiert. Die einzige interessante Komponente in dieser Struktur ist mode. Indem man dieser Komponente einen der Werte TM_SHORT, TM_LONG oder TM_REPEAT zuordnet, kann man das Verhalten der einzelnen Taster steuern:

tasten[n].mode = TM_SHORT
Taster n reagiert auf Druck. Bevor er erneut reagiert, muss er losgelassen werden. taster wird auf n gesetzt.
tasten[n].mode = TM_LONG
Der Taster reagiert auf kurzen/langen Druck. Wird er nur kurz gedrückt, wird taster=n gesetzt; wird er lange gedrückt, wird taster=n+TASTER_LONG gesetzt. Die Zeitdauer, ab der ein Druck als lange angesehen wird, kann mir TASTER_DELAY_LONG eingestellt werden.
tasten[n].mode = TM_REPEAT
Der Taster hat eine auto-repeat Funktion, d.h. bei dauernd gedrücktem Taster wird in bestimmten Zeitabständen immer wieder taster=n gesetzt.

Wiederholdauer und Erstverzögerung können mit TASTER_REPEAT resp. TASTER_REPEAT_DELAY angepasst werden.

tasten

taste_t tasten[NUM_TASTER];

tasten ist ein Array der Länge NUM_TASTEN. Jedes Array-Element ist vom Typ taste_t. Dadurch kann wie oben gezeigt der Betriebsmodus eines Tasters eingestellt werden, z.B. für Taster 2:

tasten[2].mode = TM_REPEAT;

Defines

Defines (Input)

Input Defines dienen dazu, den Code bzw. die Funktionalität den eigenen Bedürfnissen anzupassen. Nach ihrer Änderung muss der Code neu übersetzt werden.

NUM_TASTER
Die Anzahl der Taster, die man behandeln möchte. Das Array tasten hat NUM_TASTEN Elemente (jedes vom Typ taste_t).
TASTER_LEVEL
Muss den Wert 0 oder 1 haben. Mit Wert 0 wird ein Taster als aktiv (gedrückt) betrachtet, wenn der Übergabewert tast von get_taster gleich Null ist. Ist er ungleich Null, wird ein Taster als losgelassen angesehen. Dies entspricht low-aktiven Tastern. 0 ist der default für dieses Macro, denn es entspricht Tastern, die von einem Port mit Pullup nach GND verschaltet sind.
Ist dieses Define auf 1, dann werden die Taster als high-aktiv behandelt (gedrückt → tast ungleich 0, ungedrückt → tast gleich 0).
Dieses Define wirkt auf alle Taster.
TASTER_LONG
Wird eine Taste kurz gedrückt, dann wird deren Nummer in taster gespeichert. Wird die Taste lange gedrückt, dann wird deren Nummer plus TASTER_LONG in taster gespeichert. Voreinstellung ist 16. Wird z.B. Taste No. 1 kurz gedrückt, wird taster auf 1 gesetzt. Wird sie lange gedrückt und ist tasten[1].mode = TM_LONG, dann wird in taster eine  17 gespeichert.
TASTER_REPEAT_DELAY
Für .mode=TM_REPEAT: Erstverzögerung in Ticks, nach der bei Dauerdruck eines Tasters der auto-repeat einsetzt.
TASTER_REPEAT
Für .mode=TM_REPEAT: Zeitspanne in Ticks zwischen zwei auto-repeats.
TASTER_DELAY_LONG
Für .mode=TM_LONG: Wird ein Taster gedrückt und erst wieder losgelassen, nachdem mindestens TASTER_DELAY_LONG Ticks verstrichen sind, ist der Tastendruck "lang". Ansonsten ist er "kurz".

Defines (Output)

Output Defines dienen dazu, "magische Zahlen" aus dem Code heraus zu halten und ihn lesbarer zu machen. Output Defines müssen so bleiben wie sie sind und dürfen nicht verändert werden.

NO_TASTER
Wert, den taster hat, wenn keine Taste gedrückt wurde.

Werte für Komponente .mode:

TM_SHORT
Taster reagiert auf Tastendruck.
TM_LONG
Taster unterscheidet zwischen kurzem und langem Tastendruck.
TM_REPEAT
Taster reagiert auf kurzen Tastendruck. Bei langem Tastendruck auto-repeat.

Quellcode

taster.c

#include "taster.h"

volatile signed char taster = NO_TASTER;

taste_t tasten[NUM_TASTER];

/*
 * Aktualisiert 'taster', falls taster==NO_TASTER
 * num:  Nummer des Tasters von 0...NUM_TASTER-1
 * TASTER_LEVEL=1:
 *    tast: ==0 falls der Taster gerade nicht gedrueckt wird
 *    tast: !=0 falls der Taster gerade gedrueckt wird
 * TASTER_LEVEL=0:
 *    tast: !=0 falls der Taster gerade nicht gedrueckt wird
 *    tast: ==0 falls der Taster gerade gedrueckt wird
 */
void get_taster (const unsigned char num, unsigned char tast) 
{
    const taste_t * const ptast = & tasten[num];
    const unsigned char taster_old = ptast->old;
    unsigned char pressed, press, release, mode, delay;

#if TASTER_LEVEL
    tast = !!tast;
#else
    tast = !tast;
#endif
 
    /* Was wurde gedrueckt/losgelassen...? */
 
    /* Taster bleibt gedrueckt */
    pressed =  taster_old &  tast;
    /* Taster neu gedrueckt */
    press   = ~taster_old &  tast;
    /* Taster losgelassen */
    release =  taster_old & ~tast;

    /* ptast->old = tast;
     * Der Cast dient zum Wegwerfen des 'const' Qualifiers. 
     * Die Komponente wurde als 'const' qualifiziert, 
     * damit es einen Fehler gibt, wenn versucht wird,
     * ihren Wert von aussen zu aendern (private). */
    *((unsigned char *) & ptast->old) = tast;
 
    tast = NO_TASTER;
 
    mode  = ptast->mode;
    delay = ptast->delay;
 
    if (press)
    {
        if (mode != TM_LONG)
            tast = num;
   
        delay = 0;
    }
    else if (pressed)
    {
        if (delay < 0xfe)
            delay++;
    }
    else if (release)
    {
        if (mode == TM_LONG && delay != 0xff)
            tast = num;
    }
 
    if (mode == TM_LONG)
    {
        if (delay == TASTER_DELAY_LONG)
        {
            tast = TASTER_LONG + num;
            delay = 0xff;
        }
    }
    else if (mode == TM_REPEAT)
    {
        if (delay == TASTER_REPEAT_DELAY)
        {
            tast = num;
            delay = TASTER_REPEAT_DELAY - TASTER_REPEAT;
        }
    }

    if (taster == NO_TASTER)
        taster = tast;
  
    /* siehe oben */
    *((unsigned char *) & ptast->delay) = delay;
}

taster.h

#ifndef _TASTER_H_
#define _TASTER_H_

/* Wert fuer taster, wenn nichts gedrückt wurde */
#define NO_TASTER (-1)

/* Maximale Anzahl der Taster */
#define NUM_TASTER 4

/* 0 --> Taster sind low-aktiv */
/* 1 --> Taster sind high-aktiv */
#define TASTER_LEVEL 0

/* Dieser Offset wird zur Tasten-Nummer addiert, */
/* wenn eine Taste lange gedrückt wurde */
#define TASTER_LONG 16

/* Zeitverzögerung (in Ticks), bis zum Beginn von auto-repeat */
#define TASTER_REPEAT_DELAY (60)

/* Zeitverzögerung (in Ticks), bis zum nächsten auto-repeat */
#define TASTER_REPEAT       (15)

/* Ab dieser Dauer wird der Tastendruck 'lange' */
#define TASTER_DELAY_LONG   (80)

typedef struct 
{
    /* private */
    const unsigned char delay, old;

    /* Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT */
    unsigned char mode;
} taste_t;

extern taste_t tasten[];

/* In dieser Variable kann abgefragt werden, welche Taste gedrückt wurde.
   --> NO_TASTER:
       es wurde nichts gedrückt
   --> 0..NUM_TASTER-1:
       Taster Numero 'taster' wurde (kurz) gedrückt
   --> TASTER_LONG ... TASTER_LONG + NUM_TASTER-1:
       Taster Numero 'taster-TASTER_LONG' wurde lange gedrückt
*/   
extern volatile signed char taster;

extern void get_taster (const unsigned char num, unsigned char tast);

enum
{
    TM_SHORT,
    TM_LONG,
    TM_REPEAT
};

#endif /* _TASTER_H_ */

Anwendungsbeispiel

Da ein Anwendungsbeispiel auch die Abfrage der Ports beinhalten muss, ist dieses natürlich nicht mehr hardwareunabhängig.

ATmega8 und avr-gcc

Dies nur als Anregung. Auf anderen AVRs sieht's möglicherweise etwas anders aus.

#include <avr/io.h>
#include <avr/interrupt.h>
 
/* Wir haben 3 Taster, in taster.h wird also angepasst zu
#define NUM_TASTER 3
*/
#include "taster.h"

/* 
 * Die Taster-Ports sind hartcodiert:
 * #0 --> PortB.4
 * #1 --> PortC.1
 * #2 --> PortD.2
 */

/* Bei 1MHz Grundtakt läuft Timer0 alle 256µs über.
 * Um auf rund 10ms zu kommen, rufen wir get_taster nur
 * jedes 39. mal auf. */
SIGNAL (SIG_OVERFLOW0)
{
    static unsigned char count_ovl0;
    unsigned char ovl0 = count_ovl0+1;

    if (ovl0 >= 39)
    {
        get_taster (0, PINB & (1<<PB4));
        get_taster (1, PINC & (1<<PC1));
        get_taster (2, PIND & (1<<PD2));
        
        ovl0 = 0;
    }

    count_ovl0 = ovl0;
}

void ioinit()
{
    /* Taster sind Input (default nach RESET) */
    /* Bei LOW-aktiven an den Tastern die PullUps aktivieren, bei HIGH-aktiven nicht */
    #if TASTER_LEVEL
        ;
    #else
        PORTB |= 1 << PB4;
        PORTC |= 1 << PC1;
        PORTD |= 1 << PD2;
    #endif
    /* Timer0 ohne Prescaler starten */
    TCCR0 = 1 << CS00;
 
    /* Timer0-Overflow-Interrupt aktivieren */
    TIMSK |= (1 << TOIE0);
}

int main()
{
    ioinit();
 
    /* Taster konfigurieren (#define NUM_TASTER 3 in taster.h) */
    tasten[0].mode = TM_SHORT;
    tasten[1].mode = TM_LONG;
    tasten[2].mode = TM_REPEAT;
 
    /* Interrupts global aktivieren */
    sei();

    /* Hauptschleife */
    while (1)
    {
        signed char tast = taster;
  
        switch (tast)
        {
            default:
            case NO_TASTER:
                break;
    
            case 0:
                /* Taster 0 */
                break;
    
            case 1:
                /* Taster 1 kurz gedrueckt */
                break;
    
            case 1+TASTER_LONG:
                /* Taster 1 lange gedrueckt */
                break;
    
            case 2:
                /* Taster 2 */
                break;
        }
  
        if (tast != NO_TASTER)
            taster = NO_TASTER;

        /* ********************************** */
        /* Weiterer Code in der Hauptschleife */
    }
}

Siehe auch