Mechanische Schalter "prellen" beim Ein- und Ausschalten. Vereinfacht dargestellt sieht eine von einem Schalter oder Taster geschaltete Spannung beim Schalten wie folgt aus:
Für die Auswertung dieses unsauberen Signals gibt es verschiedene Ansätze:
[bearbeiten] Hardwareentprellung
Tastenentprellung lässt sich in Hardware durch einen Tiefpassfilter mit nachgeschaltetem Schmitt-Trigger realisieren.
Eine andere Hardware-Lösung ist die Verwendung eines Umschaltkontaktes und eines RS-Flipflops, das aus zwei NAND-Gattern gebildet wird. Der Schalter sollte vom Typ "nicht kurzschließend" sein.
Taster entprellen mit NAND-RS FlipFlop
Bei Verwendung eines Mikrocontrollers kann man sich die zusätzliche Hardware sparen, da die Entprellung genau so gut in Software funktioniert.
[bearbeiten] Softwareentprellung
Bei einem Taster gibt es insgesamt 4 Zustände:
- 1. war nicht gedrückt und ist nicht gedrückt
- 2. war nicht gedrückt und ist gedrückt (steigende Flanke)
- 3. war gedrückt und ist immer noch gedrückt
- 4. war gedrückt und ist nicht mehr gedrückt (fallende Flanke)
Diese einzelnen Zustände lassen sich jetzt bequem abfragen/durchlaufen. Die Entprellung geschieht dabei durch die ganze Laufzeit des Programms.
Die Taster werden hierbei als Active-Low angeschlossen um die interen Pull-Ups zu nutzen.
Diese Routine gibt für den Zustand "steigende Flanke" den Wert "1" zurück, sonst "0"
#define TASTERPORT PINC
#define TASTERBIT PINC1
char taster(void)
{
static unsigned char zustand;
char rw = 0;
if(zustand == 0 && !(TASTERPORT & (1<<TASTERBIT))) //Taster wird gedrueckt (steigende Flanke)
{
zustand = 1;
rw = 1;
}
else if (zustand == 1 && !(TASTERPORT & (1<<TASTERBIT))) //Taster wird gehalten
{
zustand = 2;
rw = 0;
}
else if (zustand == 2 && (TASTERPORT & (1<<TASTERBIT))) //Taster wird losgelassen (fallende Flanke)
{
zustand = 3;
rw = 0;
}
else if (zustand == 3 && (TASTERPORT & (1<<TASTERBIT))) //Taster losgelassen
{
zustand = 0;
rw = 0;
}
return rw;
} |
[bearbeiten] Warteschleifen-Verfahren
Siehe Abschnitt (Tasten-)Entprellung im AVR-GCC-Tutorial.
Der DEBOUNCE Befehl in dem BASIC-Dialekt BASCOM für AVR ist ebenfalls nach dem Warteschleifen-Verfahren programmiert. Die Wartezeit beträgt defaultmässig 25 ms, kann aber vom Anwender überschrieben werden. Vgl. BASCOM Online-Manual zu DEBOUNCE.
Der Nachteil dieses Verfahrens ist, dass der Controller durch die Warteschleife blockiert wird. Günstiger ist die Implementierung mit einem Timer-Interrupt.
[bearbeiten] Interrupt-Verfahren (nach Peter Dannegger)
[bearbeiten] Grundroutine (AVR Assembler)
Siehe dazu: Forum
Vorteile
- besonders kurzer Code
- schnell
- sicher (4-fach-Abtastung)
Außerdem können 8 Tasten gleichzeitig bearbeitet werden, es dürfen also
alle exakt zur selben Zeit gedrückt werden. Andere Routinen können z.B. nur eine Taste verarbeiten, d.h. die zuerst oder zuletzt gedrückte gewinnt oder es kommt Unsinn heraus.
Die eigentliche Einlese- und Entprellroutine ist nur 8 Instruktionen
kurz. Der entprellte Tastenzustand ist im Register "key_state". Mit nur 2 weiteren Instruktionen wird dann der Wechsel von Taste offen zu
Taste gedrückt erkannt und im Register "key_press" abgelegt. Im Beispielcode werden dann damit 8 LEDs ein- und ausgeschaltet. Jede Taste entspricht einem Bit in den Registern, d.h. die Verarbeitung erfolgt bitweise mit logischen Operationen. Zum Verständnis empfiehlt es sich daher, die Logikgleichungen mit Gattern für ein Bit = eine Taste aufzumalen. Die Register kann man sich als Flip-Flops denken, die mit der Entprellzeit als Takt arbeiten. D.h. man kann das auch so z.B. in einem GAL22V10 realisieren.
Als Kommentar sind neben den einzelnen Instruktionen alle 8 möglichen
Kombinationen der 3 Signale dargestellt.
Beispielcode für AVR (Assembler):
.nolist
.include "c:\avr\inc\1200def.inc"
.list
.def save_sreg = r0
.def iwr0 = r1
.def iwr1 = r2
.def key_old = r3
.def key_state = r4
.def key_press = r5
.def leds = r16
.def wr0 = r17
.equ key_port = pind
.equ led_port = portb
rjmp init
.org OVF0addr ;timer interrupt 24ms
in save_sreg, SREG
get8key: ;/old state iwr1 iwr0
mov iwr0, key_old ;00110011 10101010 00110011
in key_old, key_port ;11110000
eor iwr0, key_old ; 11000011
com key_old ;00001111
mov iwr1, key_state ; 10101010
or key_state, iwr0 ; 11101011
and iwr0, key_old ; 00000011
eor key_state, iwr0 ; 11101000
and iwr1, iwr0 ; 00000010
or key_press, iwr1 ;store key press detect
;
; insert other timer functions here
;
out SREG, save_sreg
reti
;-------------------------------------------------------------------------
init:
ldi wr0, 0xFF
out ddrb, wr0
ldi wr0, 1<<CS02 | 1<<CS00 ;divide by 1024 * 256
out TCCR0, wr0
ldi wr0, 1<<TOIE0 ;enable timer interrupt
out TIMSK, wr0
clr key_old
clr key_state
clr key_press
ldi leds, 0xFF
main: cli
eor leds, key_press ;toggle LEDs
clr key_press ;clear, if key press action done
sei
out led_port, leds
rjmp main
;------------------------------------------------------------- |
[bearbeiten] Komfortroutine (C für AVR)
Siehe dazu: Forum
Funktionsprinzip wie oben plus zusätzliche Features:
- Kann Tasten sparen durch unterschiedliche Aktionen bei kurzem oder langem Drücken
- Wiederholfunktion, z.B. für die Eingabe von Werten
Das Programm ist für avr-gcc/avr-libc geschrieben, kann aber mit ein paar Anpassungen auch mit anderen Compilern und Mikrocontrollern verwendet werden. Eine Portierung für den AT91SAM7 findet man hier (aus dem Projekt ARM MP3/AAC Player).
/************************************************************************/
/* */
/* Debouncing 8 Keys */
/* Sampling 4 Times */
/* With Repeat Function */
/* */
/* Author: Peter Dannegger */
/* danni@specs.de */
/* */
/************************************************************************/
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#ifndef F_CPU
#define F_CPU 1000000 // processor clock frequency
#endif
#define KEY_DDR DDRB
#define KEY_PORT PORTB
#define KEY_PIN PINB
#define KEY0 0
#define KEY1 1
#define KEY2 2
#define ALL_KEYS (1<<KEY0 | 1<<KEY1 | 1<<KEY2)
#define REPEAT_MASK (1<<KEY1 | 1<<KEY2) // repeat: key1, key2
#define REPEAT_START 50 // after 500ms
#define REPEAT_NEXT 20 // every 200ms
#define LED_DDR DDRA
#define LED_PORT PORTA
#define LED0 0
#define LED1 1
#define LED2 2
uint8_t key_state; // debounced and inverted key state:
// bit = 1: key pressed
uint8_t key_press; // key press detect
uint8_t key_rpt; // key long press and repeat
ISR( TIMER0_OVF_vect ) // every 10ms
{
static uint8_t ct0, ct1, rpt;
uint8_t i;
TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5); // preload for 10ms
i = key_state ^ ~KEY_PIN; // key changed ?
ct0 = ~( ct0 & i ); // reset or count ct0
ct1 = ct0 ^ (ct1 & i); // reset or count ct1
i &= ct0 & ct1; // count until roll over ?
key_state ^= i; // then toggle debounced state
key_press |= key_state & i; // 0->1: key press detect
if( (key_state & REPEAT_MASK) == 0 ) // check repeat function
rpt = REPEAT_START; // start delay
if( --rpt == 0 ){
rpt = REPEAT_NEXT; // repeat delay
key_rpt |= key_state & REPEAT_MASK;
}
}
///////////////////////////////////////////////////////////////////
//
// check if a key has been pressed. Each pressed key is reported
// only once
//
uint8_t get_key_press( uint8_t key_mask )
{
cli(); // read and clear atomic !
key_mask &= key_press; // read key(s)
key_press ^= key_mask; // clear key(s)
sei();
return key_mask;
}
///////////////////////////////////////////////////////////////////
//
// check if a key has been pressed long enough such that the
// key repeat functionality kicks in. After a small setup delay
// the key is reported beeing pressed in subsequent calls
// to this function. This simulates the user repeatedly
// pressing and releasing the key.
//
uint8_t get_key_rpt( uint8_t key_mask )
{
cli(); // read and clear atomic !
key_mask &= key_rpt; // read key(s)
key_rpt ^= key_mask; // clear key(s)
sei();
return key_mask;
}
///////////////////////////////////////////////////////////////////
//
uint8_t get_key_short( uint8_t key_mask )
{
cli(); // read key state and key press atomic !
return get_key_press( ~key_state & key_mask );
}
///////////////////////////////////////////////////////////////////
//
uint8_t get_key_long( uint8_t key_mask )
{
return get_key_press( get_key_rpt( key_mask ));
}
int main( void )
{
KEY_DDR &= ~ALL_KEYS; // konfigure key port for input
KEY_PORT |= ALL_KEYS; // and turn on pull up resistors
TCCR0 = (1<<CS02)|(1<<CS00); // divide by 1024
TIMSK = 1<<TOIE0; // enable timer interrupt
LED_PORT = 0xFF;
LED_DDR = 0xFF;
sei();
for(;;) { // main loop
// single press
if( get_key_press( 1<<KEY0 ))
LED_PORT ^= 1<<LED0;
// release after short press: task 1
// long press: task 2
if( get_key_short( 1<<KEY1 ))
LED_PORT ^= 1<<LED1;
if( get_key_long( 1<<KEY1 ))
LED_PORT ^= 1<<LED2;
// single press and repeat
if( get_key_press( 1<<KEY2 ) || get_key_rpt( 1<<KEY2 )){
uint8_t i = LED_PORT;
i = (i & 0x07) | ((i << 1) & 0xF0);
if( i < 0xF0 )
i |= 0x08;
LED_PORT = i;
}
}
} |