Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

K (Funktionen)
 
(27 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
[[Kategorie:Baustelle]]
+
Die Abfrage von Tastern, die an einen [[Mikrocontroller]] angeschlossen sind, wird sich üblicherweise in zwei Phasen aufteilen:
 +
# Abfrage der Port-Werte, an denen die Taster angeschlossen sind
 +
# 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 <u>nicht</u> 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 <tt>get_taster</tt> übermittelt, welche in der globalen Variablen <tt>taster</tt> 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 <tt>taster</tt> abgefragt werden und abhängig davon Aktionen ausgeführt/ausgelöst werden. Nachdem <tt>taster</tt> gelesen wurde wird die Variable geleert und es kann ein weiterer Tastendruck empfangen werden.
 +
 
 
=Interface=
 
=Interface=
 
==Funktionen==
 
==Funktionen==
Zeile 5: Zeile 32:
  
 
:Als Richtwert für die Dauer eines Ticks kann man sich an 10ms orientieren.
 
:Als Richtwert für die Dauer eines Ticks kann man sich an 10ms orientieren.
 +
:{|
 +
;<tt>num</tt>: Dies ist die Nummer des Tasters: <tt>0 <= num < NUM_TASTER</tt>.
 +
|-
 +
;<tt>tast</tt>: 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.&nbsp;0 an Port B1:
 +
:<pre>get_taster (0, PINB & (1 << PB1));</pre>
 +
|}
  
 
==Datenstrukturen==
 
==Datenstrukturen==
Zeile 19: Zeile 52:
  
 
===<tt>taste_t</tt>===
 
===<tt>taste_t</tt>===
<pre>
+
typedef struct  
typedef struct  
+
{
{
+
    ...
    ...
+
 
+
    {{comment|Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT}}
    /* Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT */
+
    unsigned char mode;
    unsigned char mode;
+
} taste_t;
} taste_t;
+
</pre>
+
  
 
Jeder Taster wird im Array <tt>tasten</tt> durch einen Eintrag repräsentiert. Die einzige interessante Komponente in dieser Struktur ist <tt>mode</tt>. Indem man dieser Komponente einen der Werte <tt>TM_SHORT</tt>, <tt>TM_LONG</tt> oder <tt>TM_REPEAT</tt> zuordnet, kann man das Verhalten der einzelnen Taster steuern:
 
Jeder Taster wird im Array <tt>tasten</tt> durch einen Eintrag repräsentiert. Die einzige interessante Komponente in dieser Struktur ist <tt>mode</tt>. Indem man dieser Komponente einen der Werte <tt>TM_SHORT</tt>, <tt>TM_LONG</tt> oder <tt>TM_REPEAT</tt> zuordnet, kann man das Verhalten der einzelnen Taster steuern:
;<tt>tasten[n].mode = KM_SHORT;</tt>: Taster&nbsp;<tt>n</tt> reagiert auf Druck. Bevor er erneut reagiert, muss er losgelassen werden. <tt>taster</tt> wird auf&nbsp;n gesetzt.
+
;<tt>tasten[n].mode = TM_SHORT</tt>: Taster&nbsp;<tt>n</tt> reagiert auf Druck. Bevor er erneut reagiert, muss er losgelassen werden. <tt>taster</tt> wird auf&nbsp;n gesetzt.
;<tt>tasten[n].mode = KM_LONG;</tt>: Der Taster reagiert auf kurzen/langen Druck. Wird er nur kurz gedrückt, wird <tt>taster=n</tt> gesetzt; wird er lange gedrückt, wird <tt>taster=n+TASTER_LONG</tt> gesetzt. Die Zeitdauer, ab der ein Druck als lange angesehen wird, kann mir <tt>TASTER_DELAY_LONG</tt> eingestellt werden.
+
;<tt>tasten[n].mode = TM_LONG</tt>: Der Taster reagiert auf kurzen/langen Druck. Wird er nur kurz gedrückt, wird <tt>taster=n</tt> gesetzt; wird er lange gedrückt, wird <tt>taster=n+TASTER_LONG</tt> gesetzt. Die Zeitdauer, ab der ein Druck als lange angesehen wird, kann mir <tt>TASTER_DELAY_LONG</tt> eingestellt werden.
;<tt>tasten[n].mode = KM_REPEAT;</tt>: Der Taster hat eine auto-repeate Funktion, d.h. bei dauernd gedrücktem Taster wird in bestimmten Zeitabständen immer wieder <tt>taster=n</tt> gesetzt. Das Wiederholdauer und Erstverzögerung können mit <tt>TASTER_REPEAT</tt> resp. <tt>TASTER_REPEAT_DELAY</tt> angepasst werden.
+
;<tt>tasten[n].mode = TM_REPEAT</tt>: Der Taster hat eine auto-repeat Funktion, d.h. bei dauernd gedrücktem Taster wird in bestimmten Zeitabständen immer wieder <tt>taster=n</tt> gesetzt.
 +
Wiederholdauer und Erstverzögerung können mit <tt>TASTER_REPEAT</tt> resp. <tt>TASTER_REPEAT_DELAY</tt> angepasst werden.
  
 
===<tt>tasten</tt>===
 
===<tt>tasten</tt>===
Zeile 39: Zeile 71:
 
</pre>
 
</pre>
 
<tt>tasten</tt> ist ein Array der Länge <tt>NUM_TASTEN</tt>. Jedes Array-Element ist vom Typ <tt>taste_t</tt>. Dadurch kann wie oben gezeigt der Betriebsmodus eines Tasters eingestellt werden, z.B. für Taster&nbsp;2:
 
<tt>tasten</tt> ist ein Array der Länge <tt>NUM_TASTEN</tt>. Jedes Array-Element ist vom Typ <tt>taste_t</tt>. Dadurch kann wie oben gezeigt der Betriebsmodus eines Tasters eingestellt werden, z.B. für Taster&nbsp;2:
  tasten[2].mode = KM_REPEAT;
+
  tasten[2].mode = TM_REPEAT;
  
 
==Defines==
 
==Defines==
Zeile 45: Zeile 77:
 
Input Defines dienen dazu, den Code  bzw. die Funktionalität den eigenen Bedürfnissen anzupassen. Nach ihrer Änderung muss der Code neu übersetzt werden.
 
Input Defines dienen dazu, den Code  bzw. die Funktionalität den eigenen Bedürfnissen anzupassen. Nach ihrer Änderung muss der Code neu übersetzt werden.
 
;<tt>NUM_TASTER</tt>: Die Anzahl der Taster, die man behandeln möchte. Das Array <tt>tasten</tt> hat <tt>NUM_TASTEN</tt> Elemente (jedes vom Typ <tt>taste_t</tt>).
 
;<tt>NUM_TASTER</tt>: Die Anzahl der Taster, die man behandeln möchte. Das Array <tt>tasten</tt> hat <tt>NUM_TASTEN</tt> Elemente (jedes vom Typ <tt>taste_t</tt>).
 +
;<tt>TASTER_LEVEL</tt>: Muss den Wert&nbsp;0 oder&nbsp;1 haben. Mit Wert&nbsp;0 wird ein Taster als aktiv (gedrückt) betrachtet, wenn der Übergabewert <tt>tast</tt> von <tt>get_taster</tt> gleich Null ist. Ist er ungleich Null, wird ein Taster als losgelassen angesehen. Dies entspricht low-aktiven Tastern. 0&nbsp;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&nbsp;1, dann werden die Taster als high-aktiv behandelt (gedrückt &rarr; <tt>tast</tt> ungleich&nbsp;0, ungedrückt &rarr; <tt>tast</tt> gleich&nbsp;0).
 +
:Dieses Define wirkt auf alle Taster.
 
;<tt>TASTER_LONG</tt>: Wird eine Taste kurz gedrückt, dann wird deren Nummer in <tt>taster</tt> gespeichert. Wird die Taste lange gedrückt, dann wird deren Nummer plus <tt>TASTER_LONG</tt> in <tt>taster</tt> gespeichert. Voreinstellung ist&nbsp;16. Wird z.B. Taste No.&nbsp;1 kurz gedrückt, wird <tt>taster</tt> auf&nbsp;1 gesetzt. Wird sie lange gedrückt und ist <tt>tasten[1].mode = TM_LONG</tt>, dann wird in <tt>taster</tt> eine &nbsp;17 gespeichert.
 
;<tt>TASTER_LONG</tt>: Wird eine Taste kurz gedrückt, dann wird deren Nummer in <tt>taster</tt> gespeichert. Wird die Taste lange gedrückt, dann wird deren Nummer plus <tt>TASTER_LONG</tt> in <tt>taster</tt> gespeichert. Voreinstellung ist&nbsp;16. Wird z.B. Taste No.&nbsp;1 kurz gedrückt, wird <tt>taster</tt> auf&nbsp;1 gesetzt. Wird sie lange gedrückt und ist <tt>tasten[1].mode = TM_LONG</tt>, dann wird in <tt>taster</tt> eine &nbsp;17 gespeichert.
;<tt>TASTER_REPEAT_DELAY</tt>: Für <tt>.mode=TM_REPEAT</tt>: Erstverzögerung in Ticks, nach der bei Dauerdruck eines Tasters der auto-repeate einsetzt.
+
;<tt>TASTER_REPEAT_DELAY</tt>: Für <tt>.mode=TM_REPEAT</tt>: Erstverzögerung in Ticks, nach der bei Dauerdruck eines Tasters der auto-repeat einsetzt.
 
;<tt>TASTER_REPEAT</tt>: Für <tt>.mode=TM_REPEAT</tt>: Zeitspanne in Ticks zwischen zwei auto-repeats.
 
;<tt>TASTER_REPEAT</tt>: Für <tt>.mode=TM_REPEAT</tt>: Zeitspanne in Ticks zwischen zwei auto-repeats.
 
;<tt>TASTER_DELAY_LONG</tt>: Für <tt>.mode=TM_LONG</tt>: Wird ein Taster gedrückt und erst wieder losgelassen, nachdem mindestens <tt>TASTER_DELAY_LONG</tt> Ticks verstrichen sind, ist der Tastendruck "lang". Ansonsten ist er "kurz".
 
;<tt>TASTER_DELAY_LONG</tt>: Für <tt>.mode=TM_LONG</tt>: Wird ein Taster gedrückt und erst wieder losgelassen, nachdem mindestens <tt>TASTER_DELAY_LONG</tt> Ticks verstrichen sind, ist der Tastendruck "lang". Ansonsten ist er "kurz".
Zeile 59: Zeile 94:
 
Werte für Komponente <tt>.mode</tt>:
 
Werte für Komponente <tt>.mode</tt>:
 
;<tt>TM_SHORT</tt>: Taster reagiert auf Tastendruck.
 
;<tt>TM_SHORT</tt>: Taster reagiert auf Tastendruck.
;<tt>TM_LONG</tt>: Taster unterscheidet zwischen auf kurzem und langem Tastendruck.
+
;<tt>TM_LONG</tt>: Taster unterscheidet zwischen kurzem und langem Tastendruck.
;<tt>TM_REPEAT</tt>: Taster reagiert auf kurzen Tastendruck. Bei langem Tastendruck auto-repeate.
+
;<tt>TM_REPEAT</tt>: Taster reagiert auf kurzen Tastendruck. Bei langem Tastendruck auto-repeat.
  
 
=Quellcode=
 
=Quellcode=
 
==taster.c==
 
==taster.c==
<pre>
+
#include "taster.h"
#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
+
* tast:  =0 falls der Taster gerade nicht gedrückt wird
+
* tast: !=0 falls der Taster gerade gedrückt 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 (tast)
+
        tast = 1;
+
 
   
 
   
    /* Was wurde gedrückt/losgelassen...? */
+
volatile signed char taster = NO_TASTER;
 
   
 
   
    /* Taster bleibt gedrückt */
+
  taste_t tasten[NUM_TASTER];
    pressed = taster_old &  tast;
+
    /* Taster neu gedrückt */
+
    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 ändern (private). */
+
    *((unsigned char *) & ptast->old) = tast;
+
 
   
 
   
    tast = 0;
+
<font color="#008000">/*
 +
  * 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
 +
  */</font>
 +
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;
 
   
 
   
    mode = ptast->mode;
+
  #if TASTER_LEVEL
    delay = ptast->delay;
+
    tast = !!tast;
 +
#else
 +
    tast = !tast;
 +
#endif
 +
 
 +
    {{comment|Was wurde gedrueckt/losgelassen...?}}
 +
 
 +
    {{comment|Taster bleibt gedrueckt}}
 +
    pressed =  taster_old &  tast;
 +
    {{comment|Taster neu gedrueckt}}
 +
    press  = ~taster_old &  tast;
 +
    {{comment|Taster losgelassen}}
 +
    release =  taster_old & ~tast;
 
   
 
   
    if (press)
+
    <font color="#008000">/* ptast->old = tast;
     {
+
      * Der Cast dient zum Wegwerfen des 'const' Qualifiers.
        if (mode != TM_LONG)
+
      * Die Komponente wurde als 'const' qualifiziert,
            tast = num;
+
      * damit es einen Fehler gibt, wenn versucht wird,
 +
      * ihren Wert von aussen zu aendern (private). */</font>
 +
    *((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;
 
    
 
    
        delay = 0;
+
    {{comment|siehe oben}}
    }
+
    *((unsigned char *) & ptast->delay) = delay;
    else if (pressed)
+
}
    {
+
 
        if (delay < 0xfe)
+
==taster.h==
            delay++;
+
#ifndef _TASTER_H_
    }
+
#define _TASTER_H_
    else if (release)
+
    {
+
        if (mode == TM_LONG && delay != 0xff)
+
            tast = num;
+
    }
+
 
   
 
   
    if (mode == TM_LONG)
+
{{comment|Wert fuer taster, wenn nichts gedrückt wurde}}
    {
+
#define NO_TASTER (-1)
        if (delay == TASTER_DELAY_LONG)
+
        {
+
{{comment|Maximale Anzahl der Taster}}
            tast = TASTER_LONG + num;
+
#define NUM_TASTER 4
            delay = 0xff;
+
        }
+
{{comment|0 --> Taster sind low-aktiv}}
    }
+
{{comment|1 --> Taster sind high-aktiv}}
    else if (mode == TM_REPEAT)
+
#define TASTER_LEVEL 0
     {
+
         if (delay == TASTER_REPEAT_DELAY)
+
{{comment|Dieser Offset wird zur Tasten-Nummer addiert,}}
         {
+
{{comment|wenn eine Taste lange gedrückt wurde}}
            tast = num;
+
#define TASTER_LONG 16
            delay = TASTER_REPEAT_DELAY - TASTER_REPEAT;
+
        }
+
{{comment|Zeitverzögerung (in Ticks), bis zum Beginn von auto-repeat}}
    }
+
#define TASTER_REPEAT_DELAY (60)
 +
 +
{{comment|Zeitverzögerung (in Ticks), bis zum nächsten auto-repeat}}
 +
#define TASTER_REPEAT      (15)
 +
 +
{{comment|Ab dieser Dauer wird der Tastendruck 'lange'}}
 +
#define TASTER_DELAY_LONG   (80)
 +
 +
typedef struct
 +
{
 +
    {{comment|private}}
 +
    const unsigned char delay, old;
 +
 +
    {{comment|Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT}}
 +
    unsigned char mode;
 +
} taste_t;
 +
 +
extern taste_t tasten[];
 +
 +
<font color="#008000">/* 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
 +
*/</font> 
 +
extern volatile signed char taster;
 +
 +
extern void get_taster (const unsigned char num, unsigned char tast);
 +
 +
enum
 +
{
 +
    TM_SHORT,
 +
    TM_LONG,
 +
    TM_REPEAT
 +
};
 +
 +
#endif {{comment|_TASTER_H_}}
  
    if (taster == NO_TASTER)
+
=Anwendungsbeispiel=
        taster = tast;
+
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 [[AVR]]s sieht's möglicherweise etwas anders aus.
 +
#include <avr/io.h>
 +
#include <avr/interrupt.h>
 
    
 
    
    /* siehe oben */
+
<font color="#008000">/* Wir haben 3 Taster, in taster.h wird also angepasst zu
    *((unsigned char *) & ptast->delay) = delay;
+
#define NUM_TASTER 3
}
+
*/</font>
</pre>
+
#include "taster.h"
 
+
==taster.h==
+
<font color="#008000">/*  
<pre>
+
  * Die Taster-Ports sind hartcodiert:
#ifndef _TASTER_H_
+
  * #0 --> PortB.4
#define _TASTER_H_
+
  * #1 --> PortC.1
 
+
  * #2 --> PortD.2
/* Wert für taster, wenn nichts gedrückt wurde */
+
  */</font>
#define NO_TASTER (-1)
+
 
+
<font color="#008000">/* Bei 1MHz Grundtakt läuft Timer0 alle 256µs über.
/* Maximale Anzzhl der Taster */
+
  * Um auf rund 10ms zu kommen, rufen wir get_taster nur
#define NUM_TASTER 4
+
  * jedes 39. mal auf. */</font>
 
+
SIGNAL (SIG_OVERFLOW0)
/* Dieser Offset wird zur Tasten-Nummer addiert,
+
{
* wenn eine Taste lange gedrückt wurde */
+
    static unsigned char count_ovl0;
#define TASTER_LONG 16
+
    unsigned char ovl0 = count_ovl0+1;
 
+
/* Zeitverzögerung (in ticks), bis zum Beginn von auto-repeate */
+
    if (ovl0 >= 39)
#define TASTER_REPEAT_DELAY (60)
+
    {
 
+
        get_taster (0, PINB & (1<<PB4));
/* Zeitverzögerung (in ticks), bis zum nächsten auto-repeate */
+
        get_taster (1, PINC & (1<<PC1));
#define TASTER_REPEAT      (15)
+
        get_taster (2, PIND & (1<<PD2));
 
+
       
/* Ab dieser Dauer wird der Tastendruck 'lange' */
+
        ovl0 = 0;
#define TASTER_DELAY_LONG  (80)
+
    }
 
+
typedef struct
+
    count_ovl0 = ovl0;
{
+
}
    /* private */
+
    const unsigned char delay, old;
+
void ioinit()
 
+
{
    /* Mode des Tasters aus: TM_SHORT, TM_LONG, TM_REPEAT */
+
    {{comment|Taster sind Input (default nach RESET)}}
    unsigned char mode;
+
    {{comment|Bei LOW-aktiven an den Tastern die PullUps aktivieren, bei HIGH-aktiven nicht}}
} taste_t;
+
    #if TASTER_LEVEL
 
+
        ;
extern taste_t tasten[];
+
    #else
 
+
        PORTB |= 1 << PB4;
/* In dieser Variable kann abgefragt werden, welche Taste gedrückt wurde.
+
        PORTC |= 1 << PC1;
  --> NO_TASTER:
+
        PORTD |= 1 << PD2;
      es wurde nichts gedrückt
+
    #endif
  --> 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_ */
+
</pre>
+
  
 +
    {{comment|Timer0 ohne Prescaler starten}}
 +
    TCCR0 = 1 << CS00;
 +
 
 +
    {{comment|Timer0-Overflow-Interrupt aktivieren}}
 +
    TIMSK |= (1 << TOIE0);
 +
}
 +
 +
int main()
 +
{
 +
    ioinit();
 +
 
 +
    {{comment|Taster konfigurieren (#define NUM_TASTER 3 in taster.h)}}
 +
    tasten[0].mode = TM_SHORT;
 +
    tasten[1].mode = TM_LONG;
 +
    tasten[2].mode = TM_REPEAT;
 +
 
 +
    {{comment|Interrupts global aktivieren}}
 +
    sei();
 +
 +
    {{comment|Hauptschleife}}
 +
    while (1)
 +
    {
 +
        signed char tast = taster;
 +
 
 +
        switch (tast)
 +
        {
 +
            default:
 +
            case NO_TASTER:
 +
                break;
 +
   
 +
            case 0:
 +
                {{comment|Taster 0}}
 +
                break;
 +
   
 +
            case 1:
 +
                {{comment|Taster 1 kurz gedrueckt}}
 +
                break;
 +
   
 +
            case 1+TASTER_LONG:
 +
                {{comment|Taster 1 lange gedrueckt}}
 +
                break;
 +
   
 +
            case 2:
 +
                {{comment|Taster 2}}
 +
                break;
 +
        }
 +
 
 +
        if (tast != NO_TASTER)
 +
            taster = NO_TASTER;
 +
 +
        {{comment|**********************************}}
 +
        {{comment|Weiterer Code in der Hauptschleife}}
 +
    }
 +
}
  
 
=Siehe auch=
 
=Siehe auch=
 +
* [[Tastsensoren]]
 
* [[C-Tutorial]]
 
* [[C-Tutorial]]
 +
* [[avr-gcc]]
  
 
[[Kategorie:Software]]
 
[[Kategorie:Software]]
 
[[Kategorie:Quellcode C]]
 
[[Kategorie:Quellcode C]]

Aktuelle Version vom 27. Juli 2012, 14:09 Uhr

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


LiFePO4 Speicher Test