Stop I2C Interrupt am Slave?

Hallo,
wie kann ich I²C Interrups deaktivieren und später wieder aktivieren(Timer... sollen aktiv bleiben)?

Hintergrund:

Der I²C Master schickt eine Anfrage an den Slave.

Der Slave soll:

  1. einen "Wert" zurücksenden
  2. I²C Interrupt sperren
  3. zeitkrietische Messungen durchführen und "Wert" in Variable ablegen(wird dann bei 1. gesendet)
  4. I²C wieder starten und auf die Anfrage vom Master warten...

Gruß,
Tobias

#include <util/atomic.h>

// ..............


void loop()
{
     Wire.begin();
     ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
     {
        // zeitkritische Messung, welche ohne Interrupts aus kommen muss
     }
     Wire.begin(SLAVE_ADDR);
}

Was für das Verständnis wichtig ist, dass Interrupts nichts unbedingt verloren gehen wenn die Interrupts kurz gesperrt sind. Was dabei deaktiviert ist das globale Interrupt Enable Flag. Die individuellen Interrupt Flags werden aber durch die Quellen noch gesetzt. Dadurch wird ein Interrupt abgearbeitet wenn sie wieder aktiviert sind. Erst wenn auf einem Vektor mehr als ein Interrupt ankommt gehen Ereignisse verloren.

Also ich verstehe das so, dass nach dem Block der Befehl “Wire.begin(2);” stehen muss, um den I2C Empfang wieder zu starten. Also habe ich den Befehl auskommentiert. Leider wird bei jeder Masteranfrage die Routine angesprungen… Was muss ich ändern?

const unsigned long BaudRate = 115200;  

int Wert_h;   //Variable für I2C High
int Wert_l;   //Variable für I2C Low

#include <Wire.h>                   // Bibliothek für I2C
#include <util/atomic.h>            // Interrupt blockieren

//**************************************************************************************
//EINSTELLUNGEN:
void setup() {
  Serial.begin(BaudRate);  // Serielle Schnittstelle mit 115200 Baud starten

  Wire.begin(2);                // I2C mit Adresse #2 starten
  Wire.onRequest(requestEvent); // register event für I2C
  Serial.println("-Programm-START-");
}
//**************************************************************************************
//Bei I2C Empfang:
void requestEvent() {           // Diese Schleife wird bei I2C Empfang durchlaufen

 Serial.println("--I2C Empfang vom MASTER--");

     // I2C Ausgeben
    Wire.write(5);
    Wire.write(7);


 ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
     {
 // zeitkritische Messung, welche ohne Interrupts aus kommen muss
 Serial.println ("zeitkritische Messung");

   }
    // Wire.begin(2);  //auskommentiert dürfte kein I2C Empfang mehr möglich sein?!

}
//**************************************************************************************
void loop(){  
  //nix tun
    }

Es macht keinen Sinn, in einer ISR die Interrupts abzuschalten.
Sie sind schon abgeschaltet.
Darum macht es auch keinen Sinn, in einer ISR serielle Ausgaben zu tätigen. Die Gefahr für Verklemmungen ist viel zu groß.

So weit mir bekannt darf in einem onRequest Handler nur ein write gemacht werden. Alle weiteren werden im Nirvana landen.

Also ich verstehe das so, dass nach dem Block der Befehl "Wire.begin(2);" stehen muss, um den I2C Empfang wieder zu starten.

Entweder hast du meine Lösung nicht verstanden, oder ich dein Problem.

Was ist das für ein zeitkritische Messung?

Also ich verstehe das so, dass nach dem Block der Befehl "Wire.begin(2);" stehen muss, um den I2C Empfang wieder zu starten

Nein. Wieso? ATOMIC_BLOCK deaktivert das Interrupt Enable Flag und ATOMIC_RESTORESTATE setzt es am Ende wieder auf den vorherigen Zustand. Das ist alles.

Aber das gehört nicht in die ISR! Sondern in nicht-ISR Code um auf Variablen zuzugreifen die in der ISR verändert werden. Dadurch verhindert man dass Interrupts Daten verändern auf die man gerade zugreift.

Noch zwei Fehler:
1.) Serial Ausgaben haben in Interrupts nichts verloren.
2.) Du kannst im Request Event Handler nur ein einziges mal write() machen!!
Wenn du mehrere Bytes verschicken musst, verwende ein Array. Für Multi-Byte Variablen bietet sich eine Union aus den Daten und einem Array an

Es funktioniert :slight_smile:

Der Interrupt lässt sich nun beliebig abschalten und beliebig auch wieder einschalten.
Ich habe es nun so gelöst.

// I2C Interrupt Sperren:
// unter "void Messung() {" wird der Interrupt abgeschaltet (Zeile 51).
// In Zeile 56 wird der Interrupt wieder aktiviert. Wird die Zeile auskommentiert,
// wird nur der erste Interrupt erkannt.

const unsigned long BaudRate = 115200;  

#include <Wire.h>                   // Bibliothek für I2C

boolean FlagAusgabe = false;  // Flag für die Auagabe


//**************************************************************************************
//EINSTELLUNGEN:
void setup() {
  Serial.begin(BaudRate);  // Serielle Schnittstelle mit 115200 Baud starten

  Wire.begin(2);                // I2C mit Adresse #2 starten
  Wire.onRequest(requestEvent); // register event für I2C
  Serial.println("-Programm-START-");
}
//**************************************************************************************
//Bei I2C Empfang:
void requestEvent() {           // Diese Schleife wird bei I2C Empfang durchlaufen
     FlagAusgabe = true;        // und es wird nichts weiter gemacht, als ein Flag zu setzen, das in Loop ausgewertet wird

}
//**************************************************************************************
void loop(){  
  if (FlagAusgabe == true) { // Falls I2C Server Daten haben will
    I2CAusgabe();     // Daten ausgeben
    Messung();      // Neue Messung anstoßen
    FlagAusgabe = false;  // Flag zurücksetzen
  }
    } 

    //**************************************************************************************
void I2CAusgabe() {

  Serial.println("--I2C-Ausgabe--");

    Wire.write(5);
    Wire.write(7);

}

//**************************************************************************************
void Messung() {

  // I2C abschalten:
  TWCR = _BV(~TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
  
 Serial.println ("zeitkritische Messung");

  // I2C einschalten: (zum Testen auskommentiert)
  //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);

}

Du kannst auch per noInterrupts() oder cli() (clear interrupts) alle Interrupts deaktivieren und sie mit interrupts() oder sei() wieder aktivieren. Das ist der normale Weg.
Die Atomic Makros machen das gleiche, aber es gibt da auch die Option das Interrupt Enable Flag auf den vorherigen Wert zu setzen falls sie schon deaktiviert waren (was aber normal nicht der Fall ist)