Problem mit Taster entprellen

Hallo Leute,
ich habe mir eine Fernbedienung gebastelt und verwende den PinChange-Interrupt, um den ATmega aus dem Tiefschlaf zu holen. Danach wird eine bestimmte Aktion durchgeführt und der µC wieder schlafen gelegt. Klappt eigentlich ganz gut, nur manchmal kommt der Tastendruck mehrfach an, d.h. das Entprellen klappt irgendwie nicht.

-> Im Beispiel sind zwei Taster an A0 und A1 angeschlossen. Status wird geprüft (1>0 oder 0>1) und nach einem delay ein Flag in der Interrupt-Routine gesetzt.
Irgendwie wird das delay in der Interrupt-Routine ignoriert, selbst wenn ich den Wert z.B. auf 2 Sekunden setze, kann ich schnell hintereinander den Taster drücken. Muss der Interrupt mit noInterrupt() deaktiviert werden? Hat irgendwie auch nicht geklappt...

Mein Wunsch wäre es, dass beim Drücken einer Taste die Aktion sofort ausgeführt wird und weitere Tastendrücke für ein paar Millisekunden zu sperren, um das Prellen zu ignorieren. Letztendlich wie eben eine Fernbedienung funktioniert :slight_smile:
Jemand eine Idee?

#include <LowPower.h>      // für powerDown des µC's

volatile byte counter0 = 0;        // Counter incremented by pin change interrupt
volatile byte counter1 = 0;        // Counter incremented by pin change interrupt
volatile byte IRQ0PrevVal = 0;     // Contact level last IRQ0
volatile byte IRQ1PrevVal = 0;     // Contact level last IRQ1

volatile int irqFlag = 0;             // 1=display counters after IRQ; 0=do nothing
volatile byte bounceTime  = 2000;     // Switch bouncing time in milliseconds

void setup(){
  Serial.begin(115200);

  // Tastereingänge definieren und Pull-Up Widerstand aktivierten
  pinMode(A0, INPUT);
  digitalWrite(A0, HIGH);
  pinMode(A1, INPUT);
  digitalWrite(A1, HIGH);

  PCICR |= (1 << PCIE1);             // Set port bit in CICR for INT3 and INT4
  PCMSK1 |= (1<<PCINT8);             // Set pin interrupt for INT4
  PCMSK1 |= (1<<PCINT9);             // Set pin interrupt for INT4

  interrupts();                      // Enable interrupts
  Serial.println("Waiting for an interrupt...");
  Serial.flush();                    // Serielle Ausgabe beenden vor powerDown
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}


void loop()
{
  // Wird nach Interrupt ausgeführt
  if (irqFlag==1)                   // Flag was set by IRQ routine
  {
    Serial.print("A0: ");
    Serial.print(counter0);
    Serial.print(", A1: ");
    Serial.println(counter1);    
    irqFlag=0;                      // Reset IRQ flag  
    Serial.flush();                 // Serielle Ausgabe beenden vor powerDown
  }

  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);    // ATmega wieder schlafen legen
}

//Interrupt-Funktion
ISR(PCINT1_vect)
{
  // You have to write your own interrupt handler. Don't change the name!
  // This code will be called anytime when PCINT23 switches high to low, 
  // or low to high. This is for INT3 and INT4 (both Port C)
  byte PVal;                                   // Port value (8 Bits)
  byte IRQ0ActVal;                             // Actual IRQ0 value
  byte IRQ1ActVal;                             // Actual IRQ3 value

  PVal = PINC;                                 // Read port C (8 bit)
  IRQ0ActVal = PVal & (1<<PCINT8);             // Mask out all except IRQ3
  IRQ0ActVal = IRQ0ActVal >> PCINT8;           // shift to right for bit0 position
  IRQ1ActVal = PVal & (1<<PCINT9);            // Mask out all except IRQ4
  IRQ1ActVal = IRQ1ActVal >> PCINT9;          // shift to right for bit0 position
  
  // für A0-Interrupt
  if(IRQ0PrevVal==0 && IRQ0ActVal==1)        // Transition 0-->1
  {
    // Place your command for rising signal here...
    IRQ0PrevVal=IRQ0ActVal;
  }
  else if(IRQ0PrevVal==1 && IRQ0ActVal==0)        // Transition 1-->0
  {
    // Place your command for falling signal here... 
    IRQ0PrevVal=IRQ0ActVal; 
    counter0++; 
    delay(bounceTime);      
    irqFlag=1;    
  }

  // für A1-Interrupt
  else if(IRQ1PrevVal==0 && IRQ1ActVal==1)        // Transition 0-->1
  {
    // Place your command for rising signal here...
    IRQ1PrevVal=IRQ1ActVal;
  }
  else if(IRQ1PrevVal==1 && IRQ1ActVal==0)        // Transition 1-->0
  {
    // Place your command for falling signal here... 
    IRQ1PrevVal=IRQ1ActVal; 
    counter1++;
    delay(bounceTime);  
    irqFlag=1;
  }  
}
  1. Die Tasten hardwaremäßig entprellen
    2~~) Mittels millis die Zeit abspeichern und bei einem weiteren Interrupt kontrollieren ob eine gewisse Zeit vobei ist. Ist sie vorbei wird die Aktion ausgeführt, ist sie nicht vorbei wird der Interrupt ignoriert indem die Interruptfunktion beendet wird.~~ funktioniert nicht da millis im Sleep-modus nicht gezählt wird.

Grüße Uwe

uwefed:
funktioniert nicht da millis im Sleep-modus nicht gezählt wird.

Hallo,

ich finde Deine zweite Methode doch gut, nur ein bisschen umgebaut: Tastendruck löst Interrupt aus, Interrupt weckt Arduino, Arduino merkt sich Tastendruck, Arduino wartet meinetwegen 20ms, Arduino sendet entsprechenden Befehl, Arduino legt sich wieder schlafen.

Gruß,
Ralf

Irgendwie wird das delay in der Interrupt-Routine ignoriert, s

delay() geht in ISRs nichts, da beim Eintritt in ISRs die Interrupts gesperrt werden und damit der millis() Zähler nicht mehr inkrementiert, da der Timer0 Interrupt nicht mehr auslöst.

Was geht ist delayMicroseconds(), da das NOPs in einer for-Schleife macht. Damit gehen maximal ca. 16ms:
http://arduino.cc/en/Reference/DelayMicroseconds

EDIT:
Hier ist ein sehr gutes Tutorial wie man das inklusive Entprellen implementiert:
http://www.kriwanek.de/arduino/grundlagen/182-pin-change-interrupts-grundlagen.html
http://www.kriwanek.de/arduino/grundlagen/183-mehrere-pin-change-interrupts-verwenden.html

Man kann millis() schon einmal abspeichern. Der Zähler wird dann auch im nächsten ISR Aufruf höher sein und das kann man vergleichen

Bei dir kommt halt noch der Sleep Modus hinzu und wenn der Timer0 abschaltet, dann geht das auch nicht mehr. Man könnte auch Timer0 weiterlaufen lassen. Dann wacht er aber alle 1ms auf, was wesentlich mehr Strom verbraucht.

Mein Sketch basiert auf dem von der Kriwanek-Seite und ich hatte Anfangs auch das Entprellen über den Timer drin. Das Problem ist jedoch, das ein Tastendruck erst nach der Entprellzeit übernommen wird. Das bedeutet, wenn der Taster nur kurz angetippt wird dann passiert eventuell gar nichts...

Das mit dem delayMicroseconds() werde ich nachher mal testen, danke für den Tipp. Ich hatte auch versucht den Interrupt zu deaktivieren während der if-Abfrage in der loop und dort noch ein delay() einzubauen, aber ich bekomme den Interrupt danach nicht mehr zum Laufen.
Funktioniert noInterrupt() und interrupt() nicht zusammen mit dem PinChange oder gibt es noch einen anderen Befehl?

Notfalls würde ich hardwaremäßig entprellen, hab leider nur schon alles zusammen gelötet :relaxed: Am einfachsten mit 'nem Widerstand und Kondensator, oder?

Das geht theoretisch schon. Du müsstest es aber anders machen als du es oben gesagt hast. Die Interrupts in der ISR wieder mit interrupts() aktiveren. Dann kann die ISR unterbrochen werden.

Noch besser ist es bei der Definition der ISR ISR_NOBLOCK als Attribut zu übergeben:
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

The AVR hardware clears the global interrupt flag in SREG before entering an interrupt vector. Thus, normally interrupts will remain disabled inside the handler until the handler exits, where the RETI instruction (that is emitted by the compiler as part of the normal function epilogue for an interrupt handler) will eventually re-enable further interrupts. For that reason, interrupt handlers normally do not nest. For most interrupt handlers, this is the desired behaviour, for some it is even required in order to prevent infinitely recursive interrupts (like UART interrupts, or level-triggered external interrupts). In rare circumstances though it might be desired to re-enable the global interrupt flag as early as possible in the interrupt handler, in order to not defer any other interrupt more than absolutely needed. This could be done using an sei() instruction right at the beginning of the interrupt handler, but this still leaves few instructions inside the compiler-generated function prologue to run with global interrupts disabled. The compiler can be instructed to insert an SEI instruction right at the beginning of an interrupt handler by declaring the handler the following way:

ISR(XXX_vect, ISR_NOBLOCK)

{
  ...
}

Mhh das war mir nun vom Verständnis her zu schwierig, nach ein bisschen rumprobieren ist mir aber noch was anderes eingefallen, ich hab jetzt ganz einfach ein delay() in der loop eingefügt. Nun habe ich folgende Logik:

Tastendruck löst Interrupt aus und Flag wird gesetzt
-> If-Schleife in der Loop wird umgehend ausgeführt
-> am Ende der if-Schleife noch ein kurzes delay() und erst zum Schluss das Flag wieder auf 0 setzen

Dadurch wird beim Drücken einer Taste diese nun sofort erkannt, das Prellen stört aber nicht solange die if-Schleife noch abgearbeitet wird.
Ein Counter in der ISR funktioniert so nicht, dieser war aber auch nur für Testzwecke da. Ich denke das ist eine ganz passable Lösung welche ganz gut funktioniert, es sei den jemand hat 'nen besseren Vorschlag :slight_smile:

Vollständigkeit halber noch den Sketch hierfür. Um Strom zu sparen kann man anstatt dem delay() auch den µC noch kurz schlafen legen. Habe somit eine 6-Tasten Fernbedienung die nur ein paar µA benötigt, solange keine Taste gedrückt wird :stuck_out_tongue:

#include <LowPower.h>      // für powerDown des µC's

volatile byte IRQ0PrevVal = 0;     // Contact level last IRQ0
volatile byte IRQ1PrevVal = 0;     // Contact level last IRQ1

volatile byte Taste = 255;         // 255 entspricht nicht aktiv
volatile int irqFlag = 0;          // 1=display counters after IRQ; 0=do nothing

void setup(){
  Serial.begin(115200);

  // Tastereingänge definieren und Pull-Up Widerstand aktivierten
  pinMode(A0, INPUT);
  digitalWrite(A0, HIGH);
  pinMode(A1, INPUT);
  digitalWrite(A1, HIGH);

  PCICR |= (1 << PCIE1);             // Set port bit in CICR for INT3 and INT4
  PCMSK1 |= (1<<PCINT8);             // Set pin interrupt for INT4
  PCMSK1 |= (1<<PCINT9);             // Set pin interrupt for INT4

  interrupts();                      // Enable interrupts
  Serial.println("Waiting for an interrupt...");
  Serial.flush();                    // Serielle Ausgabe beenden vor powerDown
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}


void loop()
{
  // Wird nach Interrupt ausgeführt
  if (irqFlag==1 && Taste!=255)                   // Flag was set by IRQ routine
  {
    Serial.print("Button pressed: A");
    Serial.println(Taste);
    Taste = 255;
    delay(100);   
    irqFlag=0;                      // Reset IRQ flag 
  }
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);    // ATmega wieder schlafen legen
}

//Interrupt-Funktion
ISR(PCINT1_vect)
{
  // You have to write your own interrupt handler. Don't change the name!
  // This code will be called anytime when PCINT23 switches high to low, 
  // or low to high. This is for INT3 and INT4 (both Port C)
  byte PVal;                                   // Port value (8 Bits)
  byte IRQ0ActVal;                             // Actual IRQ0 value
  byte IRQ1ActVal;                             // Actual IRQ3 value

  PVal = PINC;                                 // Read port C (8 bit)
  IRQ0ActVal = PVal & (1<<PCINT8);             // Mask out all except IRQ3
  IRQ0ActVal = IRQ0ActVal >> PCINT8;           // shift to right for bit0 position
  IRQ1ActVal = PVal & (1<<PCINT9);            // Mask out all except IRQ4
  IRQ1ActVal = IRQ1ActVal >> PCINT9;          // shift to right for bit0 position
  
  // für A0-Interrupt
  if(IRQ0PrevVal==0 && IRQ0ActVal==1)        // Transition 0-->1
  {
    // Place your command for rising signal here...
    IRQ0PrevVal=IRQ0ActVal;
  }
  else if(IRQ0PrevVal==1 && IRQ0ActVal==0)        // Transition 1-->0
  {
    // Place your command for falling signal here... 
    IRQ0PrevVal=IRQ0ActVal; 
    Taste = 0;
    irqFlag=1;    
  }

  // für A1-Interrupt
  else if(IRQ1PrevVal==0 && IRQ1ActVal==1)        // Transition 0-->1
  {
    // Place your command for rising signal here...
    IRQ1PrevVal=IRQ1ActVal;
  }
  else if(IRQ1PrevVal==1 && IRQ1ActVal==0)        // Transition 1-->0
  {
    // Place your command for falling signal here... 
    IRQ1PrevVal=IRQ1ActVal; 
    Taste = 1;
    irqFlag=1;
  }  
}