sinnvolle Tastenentprellung mal anders

HalloMoin, nach langer Zeit mal wieder hier.

Auf der Suche nach einer vernünftigen Software-Taster-Entprellung habe ich ich eine sehr gut funktionierende Alternative gefunden und erfolgreich getestet.

Als Grundlage dient diese Komfortroutine (C für AVR)
Link: http://www.mikrocontroller.net/articles/Entprellung

Obwohl auf dem ersten Blick der Code sehr verwirrend aussieht, liegen seine Vorteile auf der Hand.

Für Arduino hab ich die Routine angepasst und kann fehlerfrei kompiliert werden.
Da die Routine sich auf Timer 0 bezieht, steht diese bei Arduino ( millis usw… ) im Konflikt; somit wurde Timer 2 konfiguriert.
Der Speicherbedarf dieser Routine ist nicht erwähnenswert.
Der Tastendruck ( kann auch länger sein!) ist HIGH Aktiv( pulldown-Widerstand z.B. 4,7 kohm auf GND)

Folgendes Beispiel mit OLED 16x2 Zeichen Display und einer LED :

Display zeigt den gezählten Tastendruck;
Led Zustand wechselt bei jeden Tastendruck;

// Tasterentprellung: Komfortroutine (C für AVR µC) nach Timer-Verfahren 
// Quelle:   -->http://www.mikrocontroller.net/articles/Entprellung
// Angepasst für ARDUINO
// Beispielcode mit Zählerstand auf OLED 16x2 Display und LED  auf UNO
// Speicher nackter Code  mit  Routine wenn nix weiteres Programmiert wurde:  ca 652 Bytes

#include <Adafruit_CharacterOLED.h>   // OLED 16x2 LIB

#define KEY_DDR         DDRD
#define KEY_PORT        PORTD
#define KEY_PIN         PIND
// Tasters haben  4,7k PULLdown nach GND

#define KEY0            PORTD2               // UNO: Taster an  PIN2 
#define KEY1            PORTD3              // UNO: Taster an  PIN3
#define KEY2            PORTD4               // UNO: Taster an  PIN4
#define ALL_KEYS        (1<<KEY0 | 1<<KEY1 )
#define REPEAT_MASK     (1<<KEY1 | 1<<KEY2)       // repeat: key1, key2
#define REPEAT_START    50                        // after 500ms
#define REPEAT_NEXT     20                        // every 200ms

volatile uint8_t key_state;                                // debounced and inverted key state:
volatile uint8_t key_press;                                // key press detect
volatile uint8_t key_rpt;                                  // key long press and repeat

ISR( TIMER2_OVF_vect )                            // every 10ms
{
  static uint8_t ct0, ct1, rpt;
  uint8_t i;
  TCNT2 = (uint8_t)(int16_t)-(16000000 / 1024 * 10e-3 + 0.5);  // preload for 10ms
  i = key_state ^ KEY_PIN;                        // Taster HIGH AKTIV!
  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;
  }
}
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;
}
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_state( uint8_t key_mask )
{
  key_mask &= key_state;
  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 ));
}


Adafruit_CharacterOLED lcd(OLED_V1, A0, A1, A2, 9, 10, 11, 12);  // OLED POWER

int cnt=0;

void setup() 
{

  lcd.begin(16, 2);

  DDRB|= (1<<PORTB0);          // UNO: LED ist an PIN 8 geschaltet
  PORTB &= ~(1<<PORTB0);      // LED aus
  
  
  // Configure debouncing routines
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
  TCCR2B = (1<<CS22)|(1<<CS20);         // divide by 1024
  TCNT2 = (uint8_t)(int16_t)-(16000000 / 1024 * 10e-3 + 0.5);  // preload for 10ms
  TIMSK2 |= 1<<TOIE2;                   // enable timer interrupt
  sei();
}

void loop() 
{  
  if( get_key_press( 1<<KEY0 ))
  {
    cnt++;                // Push zählen
    PORTB^=(1<<PORTB0);   //UNO: LED toogeln
  }

  lcd.setCursor(1,0);
  lcd.print(cnt);
 
}

Timer2 ist nicht zwingend notwendig. Das könnte man zusätzlich in Timer1 mit integrieren. zB. Abänderung der Funktions die beim Interupt Overflow genutzt wird.

Des weiteren solltes du lieber den #define für F_CPU drinlassen. Denn bei deinem Beispiel muss man somit an 2 Stellen die 16.000.000 durch 8.000.000 ersetzen, wenn mal ein Pro Mini mit 3,3V zum einsatz kommt.

Nutze die Variante selber, jedoch direkt ohne die Arduino IDE. Was hier störend ist, dass du nicht die Funktion des PinModes nutzt (bei Arduino halte ich das vom Aufbau für sinnvoll).

Sicherlich kann man noch viele Veränderung vornehmen und auf seine eigene Bedürfnisse anpassen...
Den Timer 1 brauch ich noch für was anderes, da mein nächstes Projekt ( UV-Belichter) u.a. auch diese Routine beinhaltet....

Hab mich auch vertan, :drooling_face:
Nicht Timer1, sondern Timer0. Wenn du die Arduino IDE nutzt, wird dieser Timer immer bereits für millis() belegt sein (sofern nicht deaktiviert oder überschrieben). Hier kann man diesen aber entsprechend erweitern.

Die ISR( TIMER0_OVF_vect ) müsstest du nur über eine weitere if-Abfrage erweitern.

if(i % 10 == 0) // Inhalt aus der Komfortvariante.