nochmal Interrupt MCP23017

Moin,

ich möchte hier nochmal einhaken, bezüglich des “Hängens” beim Prellen des Inputs.
Der MCP läuft mit einem Taster am Input nahezu stabil.
Nur wenn ich wie wild auf dem Taster herumdrücke oder den Input mit einem Draht auf Masse ziehe bleibt der MCP in seiner Interruptroutine “hängen”.
Der IRQ-Ausgang bleibt dann fest auf low.

Zum Verständnis:
in meiner IRQ-Routine disable ich nur den IRQ-Aufruf und setze das IRQ-Flag für den MainLoop:

//===> MCP23017 Interrupt <-----------------------------------------------------
void MCP23017_Interrupt(){
  detachInterrupt(MCP23017_IRQ);
  MCP23017_IRQ_detect = true;
}

Jetzt sollten doch eigentlich weitere Interrupts ins Leere laufen?
Oder ist das Prellen so schnell, dass mehrere Interrupts “durchrutschen”?

Selbst wenn…
Bei der Abarbeitung des Interrupts wird das GPIO-Register ja ausgelesen, dass sollte doch den Interrupt zurück setzen…

void run_MCP23017_interrupt_action(){

  uint8_t IRQpin = MCP23017.getLastInterruptPin();
  uint8_t IRQpinvalue = MCP23017.getLastInterruptPinValue();
  uint8_t IRQactualpinvalue = MCP23017.digitalRead(IRQpin);
  MCP23017_IRQ_detect = false;

  Serial.print("Pin - ");Serial.print(IRQpin);
  Serial.print("|");Serial.print(IRQpinvalue);
  Serial.print("|");Serial.println(IRQactualpinvalue);

  //Serial.print("INTCAPA - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPA), BIN);
  //Serial.print("INTCAPB - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPB), BIN);
  //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
  //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);

  if (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
  }
  
  /*
  while (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
    //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
    //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);
    Serial.print("  GPIOA - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOA), BIN);  
    Serial.print("  GPIOB - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOB), BIN);  
  }*/
 
  
  //Serial.print("INTCAPA - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPA), BIN);
  //Serial.print("INTCAPB - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPB), BIN);
  //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
  //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);
  //Serial.println("");
  //Serial.println("");
}

Trotzdem passiert es, dass der Interrupt des MCP nicht zurück gesetzt wird.
Der Ausgang bleibt LOW.

Dennoch bin ich in der Lage durch die Abfrage des INTFx-Registers das “Hängen” zu detektieren.

  if (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
  }

Nur bin ich jetzt nicht in der Lage diesen zurück zu setzen.
Das sollte doch eigentlich spätestens mit dem Lesen der GPIO-Register geschehen?

    Serial.print("  GPIOA - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOA), BIN);  
    Serial.print("  GPIOB - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOB), BIN);

Ist zwar hier noch auskommentiert, bringt aber auch nichts…

Vielleicht hat ja hier noch jemand eine Idee…

Hier nochmal der ganze Code:

//===> interfaces <--------------------------------------------------------
#include <Wire.h>             //I²C
#include <Adafruit_MCP23017.h>

//===> GPIO <--------------------------------------------------------------
//I2C
  #define I2C_SDA 4
  #define I2C_SCL 5
//MCP23017 Interrupt
  #define MCP23017_IRQ 13 
  
//MCP23017 PortExpander
  Adafruit_MCP23017 MCP23017;
  volatile boolean MCP23017_IRQ_detect = false;

//===> Routines <----------------------------------------------------------
void MCP23017_SETUP();
void MCP23017_Interrupt();
void run_MCP23017_interrupt_action();


void setup() {
//Hardware Setup
//RS232 Setup
  Serial.begin(115200); 
  delay(100);
  Serial.println("");
  Serial.println("");
  Serial.println("MCP23017-I2C-Portexpander");
  Serial.println("");
//I²C
  //start_I2C();
    Wire.begin(I2C_SDA, I2C_SCL);   //begin(sda, scl)
    MCP23017_SETUP();
}

//===============================================================================
//  main LOOP
//===============================================================================
void loop() {
  if (MCP23017_IRQ_detect == true){
    run_MCP23017_interrupt_action();  
    attachInterrupt(MCP23017_IRQ, MCP23017_Interrupt, FALLING);
  }else{
  }  
}

//===============================================================================
//  routines
//===============================================================================

//===> MCP23017 SETUP <-----------------------------------------------------
void MCP23017_SETUP() {
//ESP8266 Interrupt SETUP
  pinMode(MCP23017_IRQ, INPUT);
  attachInterrupt(MCP23017_IRQ, MCP23017_Interrupt, FALLING);

//----------------------------------------------------------
//MCP23017 SET I²C Address
  MCP23017.begin(0);    //0 = 0x20, 1 = 0x21, ...
//----------------------------------------------------------
//MCP23017 PORTA SETUP
  //Datenrichtungsregister | 1=INPUT 0=OUTPUT
  MCP23017.writeRegister(MCP23017_IODIRA,  B01111111);
  //INPUT Polaritaet
  MCP23017.writeRegister(MCP23017_IPOLA,   B01111111);
  //INPUT PullUP
  MCP23017.writeRegister(MCP23017_GPPUA,   B01111111);

  //Interrupt ENABLE
  MCP23017.writeRegister(MCP23017_GPINTENA,B01111111);
  //Interrupt Vorgabewert für Vergleich
  MCP23017.writeRegister(MCP23017_DEFVALA, B00000000);
  //Interrupt Vergleichsverfahren | 1=use DEFVAL; 0=PIN on change
  MCP23017.writeRegister(MCP23017_INTCONA, B00000000);

  //Konfigurationsregister | 7=BANK | 6=MIRROR | 2=openDrain | 1=INT Pol
  MCP23017.writeRegister(MCP23017_IOCONA,  B01000000);

  //MCP23017.status("A");

//----------------------------------------------------------
//MCP23017 PORTB SETUP
  //Datenrichtungsregister | 1=INPUT 0=OUTPUT
  MCP23017.writeRegister(MCP23017_IODIRB,  B00000000);
  //INPUT Polaritaet
  //MCP23017.writeRegister(MCP23017_IPOLB,   B11111111);
  //INPUT PullUP
  //MCP23017.writeRegister(MCP23017_GPPUB,   B11111111);

  //Interrupt ENABLE
  //MCP23017.writeRegister(MCP23017_GPINTENB,B11111111);
  //Interrupt Vorgabewert für Vergleich
  //MCP23017.writeRegister(MCP23017_DEFVALB, B00000000);
  //Interrupt Vergleichsverfahren | 1=use DEFVAL; 0=PIN on change
  //MCP23017.writeRegister(MCP23017_INTCONB, B00000000);

  //MCP23017.status("B"); 
}
//===> MCP23017 Interrupt <-----------------------------------------------------
void MCP23017_Interrupt(){
  detachInterrupt(MCP23017_IRQ);
  MCP23017_IRQ_detect = true;
}

void run_MCP23017_interrupt_action(){

  uint8_t IRQpin = MCP23017.getLastInterruptPin();
  uint8_t IRQpinvalue = MCP23017.getLastInterruptPinValue();
  uint8_t IRQactualpinvalue = MCP23017.digitalRead(IRQpin);
  MCP23017_IRQ_detect = false;

  Serial.print("Pin - ");Serial.print(IRQpin);
  Serial.print("|");Serial.print(IRQpinvalue);
  Serial.print("|");Serial.println(IRQactualpinvalue);

  //Serial.print("INTCAPA - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPA), BIN);
  //Serial.print("INTCAPB - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPB), BIN);
  //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
  //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);

  if (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
  }
  
  /*
  while (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
    //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
    //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);
    Serial.print("  GPIOA - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOA), BIN);  
    Serial.print("  GPIOB - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOB), BIN);  
  }*/
 
  
  //Serial.print("INTCAPA - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPA), BIN);
  //Serial.print("INTCAPB - ");Serial.println(MCP23017.readRegister(MCP23017_INTCAPB), BIN);
  //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
  //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);
  //Serial.println("");
  //Serial.println("");
}

Hallo,

ein Taster oder Schalter kann je nach Qualität mehrfach prellen. 30ms sind normal. Genaues weiß man nur mit Oszi.

Das auslösende Interrupt Flag setzt sich nur dann automatisch zurück, wenn man die zugehörige Interrupt Service Routine (ISR) verwendet. Nur durch ein Register auslesen passiert da nix.

Ich weiß ja nicht warum du für die Taster Interrupts benutzt, aber wenn du weißt was du tust und ganz hart bist, dann mußte die Flags von Hand zurücksetzen im zugehörigen Flag Register. Nur solange der Taster prellt, wird das Flag gleich wieder gesetzt.

Moin Doc,

Doc_Arduino:
Das auslösende Interrupt Flag setzt sich nur dann automatisch zurück, wenn man die zugehörige Interrupt Service Routine (ISR) verwendet. Nur durch ein Register auslesen passiert da nix.

The first interrupt event will cause the port contents to
be copied into the INTCAP register. Subsequent
interrupt conditions on the port will not cause an
interrupt to occur as long as the interrupt is not cleared
by a read of INTCAP or GPIO.

Demnach sollte das lesen des GPIO-Registers der Interrupt doch gelöscht werden.
Das funktioniert ja im "Normalbetrieb" auch ohne Probleme.

Probleme treten jetzt scheinbar auf, wenn während der Controller mit dem IRQ beschäftigt ist und den IRQ im MCP schon zurück gesetzt hat, ein weiterer IRQ auftritt (MCP_IRQ-Pin -> LOW) der vom Controller nicht registriert wird.
Vielleicht sollte ich nach dem Eintritt in die IRQ-Routine den IRQ im MCP deaktivieren....

Doc_Arduino:
Ich weiß ja nicht warum du für die Taster Interrupts benutzt, aber wenn du weißt was du tust und ganz hart bist, dann mußte die Flags von Hand zurücksetzen im zugehörigen Flag Register. Nur solange der Taster prellt, wird das Flag gleich wieder gesetzt.

Wie würdest du die Taster denn einfangen?

Gruß
Pf@nne

Wieso klemmst Du den Interrupt überhaupt ab?

Selbst wenn mehrere Interrupts auftreten, wird das Flag gesetzt und bleibt gesetzt, bis es in loop() gelöscht wird.

Hallo Pfanne,

um Deinen Schalter zu entprellen gibt es noch einen ganz einfachen Trick: Löte einen 100nF Kondensator an die Kontakte Deines Schalters.

Viele Grüße

Jörg

Hallo,
ich sehe hier zwei Themenstränge:

  1. Benötigt Dein Projekt einen Interrupt?

  2. Warum verklemmt sich der Interrupt?

Zu 1. Meine Einschätzung zur Verwendung von Interrupts:

  • Interrupts sind die elegante Lösung für Eingänge gegenüber Polling, aber bilden auch die schwieriger zu implementierende.
  • Interrupts eignen sich für zeitkritische Signale, wo es auf den Zeitpunkt der Flanke oder das nicht Verpassen eines kurzen Signals geht.

Wenn der Taster in Deinem Projekt tatsächlich nur ein Taster ist, genügt nach meiner Einschätzung Polling.

Zu 2. Im Bibliotheksbeispiel interrupt.ino habe ich dies gefunden:

// handy for interrupts triggered by buttons
// normally signal a few due to bouncing issues
void cleanInterrupts(){
  EIFR=0x01;
  awakenByInterrupt=false;
}

Möglicherweise führt Dich das auf eine Idee.

Hallo,

gut, dann wird es die Adafruit Lib wohl intern löschen. Sieht man so nicht.

Die Frage nach der Notwendigkeit des Interrupts wegen eines Taster würde ich auch erstmal stellen wollen. :wink:
Im Grunde genommen einfach das Teil debouncen. Meinetwegen mit der Bounce2 Lib.

Warum dein Code sich verhaspelt. Gute Frage. Sieht erstmal nicht danach aus.
Nur wenn der Interruptpin detached ist, dann kann während dieser Zeit kein Signal erkannt werden. Ist ja still gelegt.

loop läuft ...
du hämmerst auf den Taster > springt in Interrupt Routine

Pin Interrupt wird detached
bool IRQ detect wird true
zurück in loop > interrupt action Funktion wird ausgeführt
bool IRQ detect wird false
serielle Ausgaben ...
Pin Interrupt wird wieder attached

Sollte funktionieren. Irgendwas unter Haube kommt da wohl durcheinander. Wobei mir das laufende attach und detach auch nicht gefällt. Nur eine Frage bleibt ungeklärt. An welcher Stelle liest du INTCAP oder GPIO aus? Sehe ich erstmal so nicht.

Kurzum, entweder du mußt dich nochmal mit der Lib befassen oder läßt das wegen einem Taster mittels Interrupt sein und tust den normal entprellen. Wie man es eben gewöhnlich macht. Wurde ja schon angesprochen. :slight_smile:

Wenn der MCP einen Interrupt signalisiert, während kein Handler mit attachInterrupt() angegeben ist, dann wird auch nach attachInterrupt() der Handler nicht aufgerufen, weil keine Flanke mehr nachkommt. Deshalb darf detachInterrupt() bei Flankentriggerung nicht verwendet werden.

Zudem ist es unsinnig, ein Flag per Interrupt zu setzen, wenn stattdessen in loop() auch der Eingang direkt abgefragt werden kann. Daß nichts verloren geht, dafür sorgt ja schon der MCP, der sein Interrupt-Signal so lange auf LOW hält, bis der Interrupt zurückgesetzt wird.

Hallo,

genau, deswegen wäre es gut zu wissen warum er überhaupt Interrupts verwendet.
Bleibt der Taster ein Taster oder kommt daran später etwas anderes ...

Moin,

danke für die Unterstützung!

Die Taster sollen tatsächlich "nur" einfache Taster ohne zeitkritische Abhängigkeiten werden.
Ich habe den MCP bisher immer nur mit IRQ genutzt, weil es einfach praktisch ist erst loszulegen wenn tatsächlich ein IRQ ausgelöst wird. Polling geht da natürlich genau so gut.
Aufgefallen ist mir die Problematik ja auch erst beim Hantieren mit prellenden Drahtbrücken.
Mit "normalen" Tastern läuft es ja fast stabil.
Den Ansatz mit der Kondensatorentprellung könnte man in Betracht ziehen, obwol das auch eher unsauber ist.

Was mich noch stört, ist die Tatsache, dass ich zwar in der Lage bin einen erneuten IRQ innerhalb der IRQ-Abarbeitung zu erkennen:

  if (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
    //MCP23017.readRegister(MCP23017_GPIOA);  
  }

Jedoch habe ich es bisher nicht geschaft den IRQ im MCP zurück zu setzen.

// handy for interrupts triggered by buttons
// normally signal a few due to bouncing issues
void cleanInterrupts(){
  EIFR=0x01;
  awakenByInterrupt=false;
}

Hilft da auch nicht wirklich weiter, weil EIFR nicht mal definiert ist, dieser Code läßt sich nicht kompilieren.

Nach MCP-Datenblatt sollte das bloße Lesen der GPIO-Register den IRQ des MCP zurück setzen.

Ich denke, ich werde wohl auf Polling umsteigen müssen, da auch mit Taster ein IRQ hängen bleiben könnte. Dann würde nur noch ein Reset hefen.

Gruß
Pf@nne

Läuft…

Die Abarbeitung des IRQ innerhalb der IRQ-Sub darf erst verlassen werden, wenn alle durch das Prellen entstandenen MCP-IRQs wieder zurückgesetzt werden.

  while (MCP23017.readRegister(MCP23017_INTFA) > 0){
    Serial.println("  hanging.....");
    //Serial.print("INTFA - ");Serial.println(MCP23017.readRegister(MCP23017_INTFA), BIN);
    //Serial.print("INTFB - ");Serial.println(MCP23017.readRegister(MCP23017_INTFB), BIN);
    Serial.print("  GPIOA - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOA), BIN);  
    Serial.print("  GPIOB - ");Serial.println(MCP23017.readRegister(MCP23017_GPIOB), BIN);  
  }

Hallo,

schön wenn es funktioniert. Aber mit Tasterabfrage ist es wohl einfacher.

Ich habe den MCP bisher immer nur mit IRQ genutzt, weil es einfach praktisch ist erst loszulegen wenn tatsächlich ein IRQ ausgelöst wird. Polling geht da natürlich genau so gut.

Was ändert das im Vergleich zur Tasterabfrage mit Polling? Die hinterlegte Aktion wird auch hier erst ausgeführt wenn der Taster gedrückt wurde. Aber gut, mach das mit dem du aktuell klar kommst.

Pfanne:
Jedoch habe ich es bisher nicht geschaft den IRQ im MCP zurück zu setzen.

// handy for interrupts triggered by buttons

// normally signal a few due to bouncing issues
void cleanInterrupts(){
 EIFR=0x01;
 awakenByInterrupt=false;
}




Hilft da auch nicht wirklich weiter, weil EIFR nicht mal definiert ist, dieser Code läßt sich nicht kompilieren.

Nach MCP-Datenblatt sollte das bloße Lesen der GPIO-Register den IRQ des MCP zurück setzen.

EIFR ist ein ATmega Register für den externen Interrupt, hat nichts mit dem MCP zu tun. Das Lesen der MCP Register steht im Code von loop(). Dieser Code reicht ja auch meistens, wie Du schon festgestellt hast, er wird nur wegen detachInterrupt() nicht immer aufgerufen.

Hallo Pfanne,

beim suchen einer Lösung nach diesem Problem kam ich mit Google zufällig wieder auf diesen alten Thread.
Nach lesen des Threads muss ich gestehen das ich dein Problem damals überhaupt nicht verstanden hatte. :confused: Leider.

Ich tüftel schon seit Tagen an einer Lösung den On-Change Interrupt vom MCP in den Griff zubekommen. Er hängt sich bei ungünstiger Konstellation der Eingangssignale bei mir auch auf. Man kann den MCP Interrupt nicht mehr löschen. Der Test mit Tastern und deren prellen ist genau der Worst-Case den der Code bzw. MCP bestehen muss.

Falls du in den letzten 3 Jahren eine Lösung erarbeiten konntest lasse sie mich bitte wissen.

Hallo Doc_Arduino,

ich sitze momentan an dem selben Problem.
Hattest du eine Lösung gefunden und läßt mich teilhaben :wink:

Besten Dank vorab
Gruß Rakohr

Prellende Taster lassen sich ordentlich nur einzeln oder als Keyboard Matrix entprellen und abfragen. Interrupts haben dabei nichts zu suchen.

Hi

Das ist eine Interrupt-Leitung des MCP - DIESER signalisiert damit eine Änderung an Seinen Eingängen.
Blöd halt, daß man diese Signalisierung nicht zurück setzen kann - mit de mArduino hat Das nur so viel zu tun, daß Dieser nun laufend 'glaubt', neue Tasten einlesen zu müssen, wo sich rein gar Nichts geändert hat.

MfG

Hallo,

das Interruptsignal vom MCP muss man nicht zwangsweise als Interrupteingang am µC nutzen. Man kann es pollen um bei einem Ereignis dann mittels I2C auf den MCP zuzugreifen. Damit muss man nicht ständig die I2C Schnittstelle pollen.

Meine Lib ist im Anhang. Ich hoffe du kommst damit klar. Soweit ich das testen konnte sollte das mit dem MCP Interrupt funktionieren. Bitte ausgiebig testen, weil jeder testet anders.

Edit:
Weil das in einem anderen Thread aktuell ein Thema ist. Lizenz reiche ich nach wenn ich mich zwischen GPL3 und MIT entschieden habe. Für rein private Verwendung erstmal uninteressant.

DocMCP23017.zip (116 KB)

Vielen herzlichen Dank. Ich teste mich mal durch und geb Feedback.

postmaster-ino:
Blöd halt, daß man diese Signalisierung nicht zurück setzen kann - mit de mArduino hat Das nur so viel zu tun, daß Dieser nun laufend 'glaubt', neue Tasten einlesen zu müssen, wo sich rein gar Nichts geändert hat.

MfG

Wieso kann die Signalisierung nicht zurück gesetzt werden?
Ich habe mehrere MCP´s an einem Raspi hängen. Da laufen sie mit Interrupt ohne Probleme, egal ob ich mit ordentlichen Tastern arbeite oder mit extrem prellenden Drahtbrücken.

Hi

Bezog sich auf #13 - dort wurde eben ausgesagt, daß Das eben der Fall ist.

Nicht mehr - nicht weniger.

MfG