Interrupt MCP23017

Guten Morgen Allerseits!

Ich bin eher neu in der uC Programmierung, habe aber die Hallo Welt und LED-Blink Programme doch schon länger hinter mir gelassen ;o)

Für ein neues Projekt laboriere ich derzeit mit dem MCP23017 herum. Ziel des Versuches ist die Umsetzung von interruptgesteuerter Pinabfrage. Als Versuchsaufbau dient Port A als Input (Pullups aktiv), GPB0 als Output, mit welchem ich eine Status LED betreibe. Interrupt am Uno ist INT0 an Pin 2.
Ich will später Taster verwenden, und simuliere die Tastendrücke derzeit auf dem Steckbrett mit einem Jumperkabel gegen Masse.

Als Bestätigung dass ein Interrupt erfolgt ist, blinke ich kurz mit der LED. Danach Frage ich das INTCAPA Register ab um den Verursacher zu finden.

Nach meinem Verständnis des Datenblattes müssen folgende Register gesetzt sein:

GPINTENA auf 0xFF: Interrupt für alle Pins von Port A aktiv
DEFVALA auf 0xFF: Vergleichswert auf 1 (Pullup an Inputs aktiv)
INTCONA auf 0xFF: Interrupt wenn Input-Pin ungleich DEFVALA

Den Code habe ich derzeit nicht zur Hand (da auf Arbeit) und werde ihn heute Abend noch nachliefern.

Ich habe nun folgendes Problem. Mit obenstehenden Konfigurationen funktioniert der Interrupt nicht. Die LED bleibt aus und das Register wird auch nicht gelesen.
Wenn ich allerdings, unter Beibehaltung ALLER Konfigurationen, das INTCONA auf 0x00 setze (Interrupt bei Änderung), dann funktioniert (fast) alles wie gewünscht, Int löst aus, LED blinkt, Register wird gelesen.
Nur, dass ich dann natürlich zwei Interrupts habe, den ersten wenn der Pin auf Masse geht, und der zweite wenn er wieder hochgezogen wird. Ich möchte aber pro Tasterbetätigung einen Interrupt (deshalb INTCONA auf 0xFF).

Hatte jemand schon ähnliche/gleiche Probleme?
Ich habe bereits Stunden mit der Fehlersuche verbracht, Register geändert, Code umgeschrieben, etc. aber das Verhalten bleibt das gleiche. Google hilft leider auch nicht weiter...

Vielen Dank für eure Hilfe!
Luke

PS: Ich nutze keine MCP Bibliothek. Alle Befehle sende ich einzeln mit der Wire-lib

ebmm_luke:
... und simuliere die Tastendrücke derzeit auf dem Steckbrett mit einem Jumperkabel gegen Masse.

Das mache ich auch gerne, erzeugt aber heftiges "Prellen", was gänzlich nicht zu Interrupts paßt. Also "Taster" entprellen oder einen zweiten Arduino ein sauberes Rechtecksignal ausgeben lassen.

...und hier noch der Code. Ist natürlich extrem umständlich geschrieben, aber momentan gehts für mich auch darum zu verstehen, was genau passiert und wie ich mit dem MCP kommunizieren muss etc.

#include <Wire.h>
#include <LCD03.h>

LCD03 lcd;

volatile byte state = LOW;
byte portA = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin(); 

  Wire.beginTransmission(0x20);
  Wire.write(0x00);               //IODIRA register address
  Wire.write(0xFF);               //bank A inputs
  Wire.endTransmission();

  Wire.beginTransmission(0x20);
  Wire.write(0x01);               //IODIRB register address
  Wire.write(0x0);                //bank B outputs
  Wire.endTransmission();  

  Wire.beginTransmission(0x20);
  Wire.write(0x02);               //IPOLA register address
  Wire.write(0x0);                //all pins normal gepolt
  Wire.endTransmission();    

  Wire.beginTransmission(0x20);
  Wire.write(0x04);               //GPINTENA register address
  Wire.write(0xFF);               //all pins on interrupt
  Wire.endTransmission();    

  Wire.beginTransmission(0x20);
  Wire.write(0x06);               //DEFVALA register address
  Wire.write(0xFF);               //normal state 1
  Wire.endTransmission(); 

  Wire.beginTransmission(0x20);
  Wire.write(0x08);               //INTCONA register address
  Wire.write(0xFF);               //interrupt when pin differenet to DEFVALA
  Wire.endTransmission(); 

  Wire.beginTransmission(0x20);
  Wire.write(0x0A);               //IOCONA register address
  Wire.write(0b00011000);         //
  Wire.endTransmission();   

  Wire.beginTransmission(0x20);
  Wire.write(0x0C);               //GPPUA register address
  Wire.write(0xFF);               //Pull ups
  Wire.endTransmission(); 

  Wire.beginTransmission(0x20);
  Wire.write(0x12);               //clear INT
  Wire.endTransmission();

  Wire.beginTransmission(0x20);
  Wire.write(0x13);               //clear INT
  Wire.endTransmission(); 

  pinMode(2, INPUT);
  //digitalWrite(2, HIGH);
  attachInterrupt(0, hoppla, FALLING);

  delay(1000);
  
  // Initialise a 16x2 LCD
  lcd.begin(16, 2);
 
  // Turn on the backlight
  lcd.backlight();    

  lcd.clear();
}


void hoppla() {
  state = HIGH;
}

void interruptReceived() {
  delay(50);

  Wire.beginTransmission(0x20);
  Wire.write(0x13);               //GPIOB register address
  Wire.write(0b00000000);         // LED off
  Wire.endTransmission();
     
}

void readMCP() {
  lcd.clear();
  
  Wire.beginTransmission(0x20);
  Wire.write(0x0E);
  Wire.endTransmission();
  Wire.requestFrom(0x20, 1);
  portA=Wire.read();

  lcd.print(portA, BIN);
  //delay(10);
  lcd.newLine();
  
  Wire.beginTransmission(0x20);
  Wire.write(0x12);
  Wire.endTransmission();
  Wire.requestFrom(0x20, 1);
  portA=Wire.read();

  lcd.print(portA, BIN);
  delay(500);

  state = LOW;
}

void loop() {
  if(state == HIGH) {
    Wire.beginTransmission(0x20);
    Wire.write(0x13);               //GPIOB register address
    Wire.write(0b00000001);         // LED on
    Wire.endTransmission(); 
  
    readMCP();
    interruptReceived();
  }
}

agmue:
Das mache ich auch gerne, erzeugt aber heftiges "Prellen", was gänzlich nicht zu Interrupts paßt. Also "Taster" entprellen oder einen zweiten Arduino ein sauberes Rechtecksignal ausgeben lassen.

Hmm... da aber der Interrupt sofort nach dem Auslösen sperrt, und erst wieder scharf wird wenn das GPIO-Register gelesen wurde, müsste er das Prellen ja eigentlich ignorieren.
Und interessanterweise habe ich keinerlei Probleme mit Prellen im anderen Interruptmodus.

Kleiner Gedankenfehler: Prellen bedeutet, viele positive und negative Flanken beim Drücken wie auch beim Loslassen des Tasters. :slight_smile:

Ich weiss was Prellen ist :wink: Da der Int ja beim ersten Event ausgelöst und erst beim Lesen des GPIO wieder gelöscht wird, laufen alle in dieser Zeit reinkommenden "Prellsignale" ins Leere. Ich frage ja nicht die Taster direkt ab, sondern das INTCAP, welches nur den Int-Verursacher speichert.

Was ich immer noch nicht verstehe ist, wieso beim einen Int-Mode alles prima läuft, beim anderen aber gar nichts.
Gibts da irgendwo ein verstecktes enable Register im Chip? Oder ists ein Fehler im Code?
Errata habe ich ebenfalls mal gecheckt, da ist nichts vermerkt.

Gruss, Luke

ebmm_luke:
Ich weiss was Prellen ist :wink:

Darum ist es ja ein Gedankenfehler: Aus einem bekannten Sachverhalt ziehst Du den falschen Schluß :slight_smile:

Ein Beispiel, rot die den Interrupt auslösende Flanke:

1. Du fragst auf Veränderung ab: ^^^^//__________///^^^^
2. Du fragst auf fallende Flanke ab: ^^^^//__________///^^^^
2. Du fragst auf steigende Flanke ab: ^^^^//__________///^^^^

Den Unterschied kannst Du nicht bemerken :slight_smile:

agmue:
Ein Beispiel, rot die den Interrupt auslösende Flanke:

1. Du fragst auf Veränderung ab: ^^^^//__________///^^^^
2. Du fragst auf fallende Flanke ab: ^^^^//__________///^^^^
2. Du fragst auf steigende Flanke ab: ^^^^//__________///^^^^

Den Unterschied kannst Du nicht bemerken :slight_smile:

Also entweder ich steh völlig aufm Schlauch, oder wir reden aneinander vorbei :wink:
Der Int am Arduino hängt ja am Interrupt Pin vom MCP, und da sollte ja erst mal gar nichts prellen. Der Arduino reagiert auf die fallende Flanke des Interrupts vom MCP. Dieser geht auf LOW sobald, z.B. bei Nr.1, die rote Flanke an einem Input Pin auftritt.

Fall 1: IOC from pin change (Datenblatt 1.7.2)
Der Int am MCP löst (wieder bei Nr.1) bei beiden roten Flanken aus, was ich nicht will.
Und dieses Verhalten sehe ich beim Versuchsaufbau, wenn ich INTCON (1.6.5) entsprechend setze. Ich sehe 2 Interrupts: Beim runterziehen eines Inputpins, und dann wieder einer beim "loslassen", also Hochziehen.

Fall 2: IOC from register default (Datenblatt 1.7.3)
Der Int am MCP löst (wieder bei Nr.1) nur bei der ersten roten Flanke aus, ich habe also 1 Int pro 1 Tasterbetätigung.
Hier sehe ich aber keinen Interrupt, obwohl ja genau wie in Fall 1, bei der fallenden Flanke einer auftreten sollte.

ebmm_luke:
Also entweder ich steh völlig aufm Schlauch, oder wir reden aneinander vorbei :wink:

Beides ist möglich :slight_smile:

Was macht denn Dein Sketch bei entprelltem Signal?

Möglicherweise macht INTCONA (INTERRUPT-ON-CHANGE CONTROL REGISTER) nicht, was Du vermutest. Ich habe den Eindruck, solange der Eingang unterschiedlich zu dem Register ist, wird immer wieder ein Interrupt ausgelöst, nicht nur bei der Flanke. Zumindest sieht es bei mir so aus.

Mit INTCONA = 0 wird ein Interrupt nur bei einer Flanke ausgelöst, allerdings bei positiver wie negativer.

Guten Morgen
Ich meine den Fehler gefunden zu haben :smiley:
Im Datenblatt unter 1.7.5 (siehe Anhang) ist das Verhalten des Interrupts setzen/löschen beschrieben. Hatte ich zwar auch schon durchgelesen, aber etwas Wichtiges nicht beachtet. Der Interrupt wird nur gelöscht, wenn beim Lesen von GPIO oder INTCAP kein Signal mehr an einem Input anliegt.
Bei meinem Testaufbau und der Geschwindigkeit mit welcher der Ardu den MCP nach dem Interrupt ausliest, steckt meine Drahtbrücke natürlich immer noch am Pin und zieht diesen auf Masse. Der Ardu liest zwar das Register, aber der Interrupt wird nicht gelöscht. Jeder weitere Interrupt von einem Inputpin wird also ignoriert, bis der MCP neu initialisiert wird. Dann geht es wieder genau einmal und nacher nicht mehr :roll_eyes:
Als ich vor dem Lesen des INTCAP einen delay(1000) (man vergebe mir) eingefügt hatte, lief alles einwandfrei, da ich in dieser Sekunde die Brücke wieder entfernen konnte, und der Int beim Auslesen des Registers gelöscht wurde.

Dies bringt mich aber zum nächsten Problem: Entweder muss ich diesen Umstand irgendwie Abfangen und dauernd das INTCAP lesen, da ich ja nicht weiss, wie lange ein Button gedrückt wurde und ob der Interrupt ordnungsgemäss gecleart ist. Kann ich auch gleich pollen...
Oder aber ich nutze trotzdem den anderen Interruptmode des MCP, habe zwei Interrupts pro Tasterbetätigung (welche aber IMMER gelöscht werden), und kann somit quasi umsonst die Betätigungsdauer messen und kurz/lang unterscheiden, Doppelklick etc. 8).

Da muss ich mir dann aber wieder Gedanken ums Prellen machen. Es kann ja sein, dass der Taster immer noch prellt, wenn ich das INTCAP lese und den Interrupt weider freischalte. Wäre hier eine Leseverzögerung sinnvoll, Stichwort millis? Diese Verzögerung ist dann aber je nachdem wieder lästig. Die Anwendung ist zwar nicht zeitkritisch (Pedalboard), aber je nachdem kann das trotzdem störend sein.
Andere Variante wäre eine sofortige Reaktion auf den Interrupt, und dann ein Ignorieren weiterer Interrupts für eine bestimmte Zeit. Finde ich sympathischer...
Ich werde mich da mal eindenken.

Vielen Dank für die Hilfe!!

int.JPG

ebmm_luke:
Wäre hier eine Leseverzögerung sinnvoll, Stichwort millis?

Ja, ich denke schon.

ebmm_luke:
Diese Verzögerung ist dann aber je nachdem wieder lästig. Die Anwendung ist zwar nicht zeitkritisch (Pedalboard), aber je nachdem kann das trotzdem störend sein.
Andere Variante wäre eine sofortige Reaktion auf den Interrupt, und dann ein Ignorieren weiterer Interrupts für eine bestimmte Zeit. Finde ich sympathischer...

Für "interrupt-on-change from register value" habe ich leider keine Lösung gefunden, daher verwende ich "interrupt-on-pin change".

Die Verzögerung habe ich durch Lesen von INTFA (ohne Löschen des Interrupts) und Merken des letzten Zustandes von INTCAPA eleminiert. Hier mein Testsketch:

#include <Wire.h>

// MCP23017 registers (everything except direction defaults to 0) Author: Nick Gammon
#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IPOLA    0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IPOLB    0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON  0x0B   // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INTFA    0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INTFB    0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLATA    0x14   // Output latch. Write to latch output.
#define OLATB    0x15

#define MCP_I2C_ADRESSE 0x20

volatile byte state = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Anfang");
  Wire.begin();
  expanderWrite(MCP_I2C_ADRESSE, IODIRA, 0x07); //bank A inputs
  expanderWrite(MCP_I2C_ADRESSE, IODIRB, 0x0); //bank B outputs
  expanderWrite(MCP_I2C_ADRESSE, IPOLA, 0x0); //alle pins normal gepolt
  expanderWrite(MCP_I2C_ADRESSE, GPINTENA, 0x07); //pins on interrupt
  expanderWrite(MCP_I2C_ADRESSE, DEFVALA, 0x07); //normal state 1
  //expanderWrite(MCP_I2C_ADRESSE, INTCONA, 0x07); //interrupt when pin differenet to DEFVALA
  expanderWrite(MCP_I2C_ADRESSE, INTCONA, 0x00); //interrupt when pin differenet against the previous value
  expanderWrite(MCP_I2C_ADRESSE, IOCON, 0b00011000); //
  expanderWrite(MCP_I2C_ADRESSE, GPPUA, 0x07); //Pull-up resistor (0 = disabled, 1 = enabled)
  expanderWrite(MCP_I2C_ADRESSE, GPPUA, 0x07); //Pull-up resistor (0 = disabled, 1 = enabled)
  expanderRead(MCP_I2C_ADRESSE, GPIOA); //Clear Interrupt

  pinMode(13, OUTPUT);
  attachInterrupt(0, hoppla, FALLING);  // Interrupt an Pin 2
  expanderWrite(MCP_I2C_ADRESSE, GPIOB, 0b00000000);  //LED off
  delay(1000);
}

void hoppla() {
  state = 2;
}

void interruptReceived() {
  const byte intervall = 30;
  unsigned long aktMillis = millis();
  static unsigned long altMillis = aktMillis;
  static bool ledStatus = 0;
  byte portA;
  static byte intcapA = 0xFF;
  switch (state) {
    case 2:
      digitalWrite(13, HIGH);

      portA = expanderRead(MCP_I2C_ADRESSE, INTFA); //Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
      Serial.print("\tINTFA: ");
      Serial.print(portA, BIN);
      if (portA & intcapA) {
        if (ledStatus) {
          expanderWrite(MCP_I2C_ADRESSE, GPIOB, 0b00000000);  //LED off
        } else {
          expanderWrite(MCP_I2C_ADRESSE, GPIOB, 0b00000001);  //LED on
        }
        ledStatus = !ledStatus;
      }

      state = 1;
      altMillis = aktMillis;
      break;
    case 1:
      if (aktMillis - altMillis >= intervall) {
        intcapA = expanderRead(MCP_I2C_ADRESSE, INTCAPA);  //Interrupt capture (read only) : value of GPIO at time of last interrupt
        Serial.print("\tINTCAPA: ");
        Serial.println(intcapA, BIN);

        digitalWrite(13, LOW);
        state = 0;
      }
      break;
  }
}


void loop() {
  if (state > 0) {
    interruptReceived();
  }

}

void expanderWrite (const byte I2Cadr, const byte reg, const byte data )
{
  Wire.beginTransmission (I2Cadr);
  Wire.write (reg);
  Wire.write (data);
  Wire.endTransmission ();
} // end of expanderWrite

byte expanderRead (const int I2Cadr, const int reg)
{
  Wire.beginTransmission (I2Cadr);
  Wire.write (reg);
  Wire.endTransmission ();
  Wire.requestFrom (I2Cadr, 1);
  return Wire.read();
} // end of expanderRead

Was meinst Du dazu?

Guten Morgen!

Vielen Dank für deinen Vorschlag!! Habe ich gestern Abend noch kurz ausprobiert. Selber hatte ich etwas ganz Ähnliches gemacht, allerdings mit der in meinem letzten Post angesprochenen kurzen Zeitverzögerung.

Pseudocode:

Loop {
jetzt = millis

If( Interrupt && jetzt - vorher >= Debounce) {
MCP auslesen
vorher = millis
}

Funktionieren tut beides, deine Lösung ist natürlich eleganter.
Ein Problem hatte ich allerdings bei beiden Ansätzen. Wenn ich in kurzer Zeit möglichst viele Interrupts produziere (wild aufm Taster rumhauen ;)), dann hängt sich das ganze irgendwann auf. Wo's klemmt weiss ich aber nicht. Ob sich da die I2C verschluckt? Manchmal half es nämlich auch nichts, wenn ich den Intpin am Ardu manuell auf Masse zog, um einen Lesevorgang auszulösen falls dort irgendwas klemmen sollte. Da half nur ein Reset oder Neustart des Boards.

Ich habe mir auch mal noch die Timer Entprellroutine von Peter Dannegger angeschaut. Gefällt mir ebenfalls sehr gut, und das gleichzeitige erkennen von verschiedenen Tastendrücken käme mir sehr gelegen. Aber halt ohne Int vom Expander, sondern mit Pollen (ja ich weiss, Timer int ist auch ein Interrupt :P).

Da muss ich mir mal überlegen, wie ich kurzes/langes Drücken und Doppelklick etc. in die Interrupt-Lösung integrieren kann (Beim 1. Int ein Flag setzen, beim 2. Int die vergangene Zeit auswerten).
Ebenfalls möchte ich gerne eine Auswertung wenn zwei Taster gleichzeitig lange gedrückt werden. Beim aktuellen Ansatz würde der zweite Taster, sollte er innerhalb der Entprellzeit nach dem ersten gedrückt werden, einfach ignoriert. Bei Dannegger nicht.