Prellen im Interrupt

Hallo,
Ich beschäftige mich gerade mit Interupts.
Ich habe mich an ein Youtube Tutorial gehalten.
https://www.youtube.com/watch?v=Fh5jFS4h1PQ&list=PLtEjuZQyAkqHb_rOnDZFuAT4vydFC1bH8&index=16
Folgender Schaltplan (ich habe ea auf einem Mega aufgebaut, sollte aber egal sein)


Hier der Sketch:

/**
 * Arduino Sketch für SmartHome yourself - Arduino Lehrgang
 * 
 * Tag 16: Interrupts
 * In diesem Sketch lösen wir durch einen Button eine Methode aus, ohne den Button im Sketch explizit abzufragen.
 * Zudem ist es egal, an welcher Stelle der Abarbeitung sich das Sketch aktuell befindet. Der Ablauf wird an der aktuellen Stelle unterbrochen 
 * und die Interrupt-Methode ausgeführt. Anschließend wird das Sketch an der unterbrochenen Stelle fortgesetzt.
 * 
 * https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/
 * https://www.arduino.cc/reference/de/language/variables/variable-scope--qualifiers/volatile/ 
 * 
 * By Daniel Scheidler - Dezember 2019
 */

const unsigned int ROTE_LED_PIN = 3;                     // globale Konstante für Pin der roten LED
const unsigned int INTERRUPT_PIN = 2;                    // globale Konstante für Interrupt Pin

const unsigned int WARTEZEIT = 500;                     // globale Konstante, welche die Wartezeit in ms angibt. 

unsigned int laufzahl = 0;                               // globale Variable die im Loop alle 5 Sekunden hochgezählt wird
unsigned long timer = 0;                                 // globale Variable in der sich der letzte Zeitpunkt 
                                                         // des hochzählens gemerkt wird

volatile byte state = LOW;                               // globale Variable für den aktuellen LED-Status 
                                                         // als volatile deklarierte Variablen sind im RAM hinterlegt. 
                                                         // Variablen die in Interrupt-Methoden verwendet werden, 
                                                         // sollten immer als volatile deklariert werden.


void setup() {
  Serial.begin(38400);
  
  pinMode(ROTE_LED_PIN, OUTPUT);                         // Pin für rote LED als Ausgang definieren
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN),  // Pin mit Button an Interrupt binden.
                  interruptMethode, RISING);             // digitalPinToInterrupt wandelt Pin-Nr des GPIO in die entsprechende Interrupt-ID um.
}

void loop() {
    if ( millis() - timer > WARTEZEIT ) {                // Prüfen, ob WARTEZEIT abgelaufen ist.
      timer = millis();                                  // Falls ja, Timer zurücksetzen.

      laufzahl++;                                        // Laufzahl um 1 erhöhen
      Serial.println(laufzahl);                          // Laufzahl auf Serieller Kommunikation ausgeben.
    }
    
    digitalWrite(ROTE_LED_PIN, state);                   // Rote LED entsprechend state schalten.
    
}


void interruptMethode(){                                 // Methode die von Interrupt aufgerufen wird.
  state = !state;                                        // Status wechseln. "!" bedeutet "NOT". Negiert also den darauf folgenden Wert.
                                                         // TRUE wird auf FALSE, bzw. LOW auf HIGH geändert und anders herum. 
}

im Video wir schon darauf hingewiesen, dass man den Pin 2 prellen sollte. Aber wie mache ich das anständig?
Ich habe ein delay in der Interrupt Methode eingebaut oder auch versucht ein delay nach dem digitalWrite zu setzten.
hatte beides nicht wirklich einen richtigen Erfolg. es kommt immer wieder zum flackern der LED, auch wenn ich die Delay-Werte hoch setze.

Gibt es da andere sinnige Möglichkeiten?

Danke.

kurz gesagt: macht man nicht.
Wenn du einen (prellenden) Button auslesen willst nimm das IDE Beispiel.
02. Digital / Debounce.

Mach dir eine schöne Funktion oder Klasse draus.
Lerne damit.

Leg die IRQ einfach für die nächsten Monate zu Seite.

Lass de ganze unsinnige ISR-geschichte weg.
So wie Du deinen halbsekundenTakt baust, so verhinderst Du ein prellen.

const unsigned int ROTE_LED_PIN = 3;                     // globale Konstante für Pin der roten LED
const unsigned int tasterPin = 2;                    // globale Konstante für Interrupt Pin

const unsigned int WARTEZEIT = 500;                     // globale Konstante, welche die Wartezeit in ms angibt.
const unsigned long bounceTime = 40;
unsigned long lastbounce = 0;

unsigned int laufzahl = 0;                               // globale Variable die im Loop alle 5 Sekunden hochgezählt wird
unsigned long timer = 0;                                 // globale Variable in der sich der letzte Zeitpunkt


void setup()
{
  Serial.begin(38400);
  pinMode(ROTE_LED_PIN, OUTPUT);                         // Pin für rote LED als Ausgang definieren
  pinMode(tasterPin, INPUT);
}

void loop()
{
  if ( millis() - timer > WARTEZEIT )                  // Prüfen, ob WARTEZEIT abgelaufen ist.
  {
    timer = millis();                                  // Falls ja, Timer zurücksetzen.
    laufzahl++;                                        // Laufzahl um 1 erhöhen
    Serial.println(laufzahl);                          // Laufzahl auf Serieller Kommunikation ausgeben.
  }
  if (digitalRead(tasterPin) && millis() - lastbounce > bounceTime)
  {
    digitalWrite(ROTE_LED_PIN, !digitalRead(ROTE_LED_PIN));
    lastbounce = millis();
  }
}

was man einfach in der Hardware abfangen kann, muss man nicht in der Software abfangen.

Ich würde den Widerstand auf 10k erhöhen und parallel zum Taster einen 220nF Kondensator schalten. Damit sollte das Prellen weg sein.

Ja!

#include <CombieTimer.h>
using Combie::Timer::EntprellTimer;

const byte taster =  2; // taster gegen GND schaltend

EntprellTimer entprellen(200);   // in der Praxis wird man wohl eher 20ms verwenden

void setup()
{
 pinMode(LED_BUILTIN,OUTPUT);   
 pinMode(taster,INPUT_PULLUP);
} 

void loop()
{
  // die LED zeigt das, vom prellen bereinigte, Signal
  digitalWrite(LED_BUILTIN,entprellen(!digitalRead(taster)));  // invers, wg. pullup
} 

CombieLib.zip (975 KB)

Du hast Glück, auf dem ATmega2560 des Arduino MEGA2560 sind pin 2 und 3 auch Interruptspins.

Taster prellen; man muß sie entprellen. Prellen bedeutet daß die mechanischen Kontakte bevor sie endgültig geschlossen bleiben einige male Hüpfen und somit einige male schließen und wieder öffnen. Dadurch wird ein Schließen oder Öffnen eines Mechanischen Kontakts als mehrere Schaltvorgänge interpretiert werden. Ein normal abgefragte Kontakt ist ziemlich eunfach weil man einfach verhindert daß der Taster schnell hintereinander abgefragt werden kann. Am einfachsten geht das mit einem kurzen delay() welches (meist) für den Programmablauf keinen negativen Einfuß hat.

  • Innerhalb einer Interruptroutine funktionieren delay() und millis() nicht.
  • Interruptroutinen sollen so kurz / schnell sein wie nur irgend möglich da durch diese das restiche Programm angehalten wird und somit nicht abgearbeitet wird.
  • Reaktionszeiten des Menschen sind so langsam daß auch eine einfache Kontrolle des Tasters innerhalb der loop()-Schleife genügt.

Grüße Uwe

Hallo chrmoehl
Das Entprellen von mechanischen Kontakten über eine ISR kann man machen, muss man aber nicht.
Zu diesem Thema gibt es N-Lösungen, die das über Software lösen.
Und hier kommt die (N+1) Lösung, die ich als Grundlage verwende.

enum {One};
constexpr byte Knop[] {A0};
constexpr unsigned long DebounceTime {20};
struct TIMER {
  unsigned long stamp;
  unsigned long duration;
};
struct KNOP {
  byte pin;
  int statusQuo;
  void (*task[2])();
  TIMER scan;
};
// --------------- add buttons as needed here -start- 
// prototyps
void knop_One_is_released();
void knop_One_is_pressed();
KNOP knops[] {
  {Knop[One], false, knop_One_is_released, knop_One_is_pressed, 0, DebounceTime},
};

// --------------- user tasks for buttons -start-

void knop_One_is_released() {
  Serial.println(__func__);
}
void knop_One_is_pressed() {
  Serial.println(__func__);
}

// --------------- user tasks for buttons -end-
// --------------- add buttons as needed here -end- 

void setup() {
  Serial.begin(9600);
  Serial.println(F("Simple Button Manager"));
  pinMode (LED_BUILTIN, OUTPUT);
  for (auto &knop : knops) pinMode(knop.pin,INPUT_PULLUP); 
}
void loop() {
  unsigned long currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  for (auto &knop : knops) {
    if (currentTime - knop.scan.stamp >= knop.scan.duration) {
      knop.scan.stamp = currentTime;
      int stateNew = !digitalRead(knop.pin);
      if (knop.statusQuo != stateNew) {
        knop.statusQuo = stateNew;
        knop.task[stateNew]();
      }
    }
  }
}

Ich wünsche einen geschmeidigen Tag und viel Spass beim Programmieren in C++.

Wenn man es einfach per Software abfangen kann, braucht man keine Hardware. :slight_smile:
Und Interrupts sind nicht für Taster gedacht, sollte das Lernziel dieser Übung sein.

Üblicherweise sicherlich nicht, aber - it depends - :wink:

Es kommen ja durchaus auch Anwendungen vor, in denen der Controller unglücklicherweise von Routinen über einen längeren Zeitraum gebunden wird (z.B. in einer Lib zum Versenden von Emails), während dessen man einen Tastendruck "verlieren" könnte ...

Dann genügt es u.U. die fallende Flanke im Interrupt zu detektieren, den Interrupt direkt zu detachen und im Programm erst wieder freizugeben, wenn ausreichend Zeit vergangen ist.

"Best practise" ist für die meisten Fälle mit rein mechanischen Schaltern/Tastern sicher das zeitliche Debouncing. Ich würde aber andere Möglichkeiten nicht kategorisch ausschließen, soweit der Entwickler weiß, auf was er sich dann einlässt ...

P.S.: Die @combie-Libs (die ich freundlicherweise schon mal von ihm bekommen habe) sind "erste Sahne", nicht nur weil sie die Aufgabe elegant lösen, sondern auch, weil man beim Nachvollziehen der lib einiges dazulernen kann (ich jedenfalls, habe mir in Folge erstmal ein aktuelles Buch zu C++ zugelegt :wink: ). Allerdings finde ich die Lösung von @paulpaulson ebenfalls interessant!