www.mikrocontroller.net

Entprellung

Mechanische Schalter "prellen" beim Ein- und Ausschalten. Vereinfacht dargestellt sieht eine von einem Schalter oder Taster geschaltete Spannung beim Schalten wie folgt aus:

Bild:Entprellen.png

Für die Auswertung dieses unsauberen Signals gibt es verschiedene Ansätze:

Inhaltsverzeichnis

[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
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

[bearbeiten] Flankenerkennung

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;      
    }
  }
}

[bearbeiten] Links

webmaster@mikrocontroller.netImpressumWerbung auf Mikrocontroller.net