Forum: Mikrocontroller und Digitale Elektronik I2C hängt sich auf und startet Programm von neuen


von Jörg T. (kallejoerg)


Lesenswert?

Hallo,

ich habe ein kleines Testsystem mit einem Arduino mega 2560 (Atmel 
ATmega640) aufgebaut. Ich habe mehre Sensoren, die über I2C 
kommunizieren, angeschlossen. Ich verwende die Standard Arduino 
libraries, somit die I2C library von Todd Krein
( https://github.com/arduino/Arduino/tree/master/libraries/Wire ).

Grundsätzlich funktioniert alles tadellos. Jedoch soll auch bei einem 
Fehlerfall mein System kontrollierte Befehle ausführen. Die Fehlerfälle 
sind: SCL Leitung hat einen Wackelkontakt, Slave erhalten keine 
Versorgungsspannung bzw. SDA Leitung und SCL Leitung sind gebrückt. 
Tritt eines dieser Fehler auf, wird das gesamte Programm neu gestärt. 
Daraufhin habe ich mir die library von Todd Krein angesehen, sowie den 
TWSR Register ausgelesen und habe folgende Vermutung was in twi.c 
passiert.

https://github.com/arduino/Arduino/blob/master/libraries/Wire/utility/twi.c

Bei einem dieser o.g. Fehler wird der TWI Interrupt ausgelöst (Zeile 
363) und der TWSR Register ausgelesen. Dieser ist bei einem Fehlerfall 
A0 und die Zeile 462 (stop or repeated start condition received) wird 
ausgeführt. Daraufhin wird die void twi_stop() (Zeile 333) aufgerufen 
und die Stop-Condition wird in TWCR Register geschrieben. Darunter ist 
auch das TWSTO Bit. Laut Atmel Datenblatt Seite 262 
(http://www.atmel.com/images/atmel-2549-8-bit-avr-microcontroller-atmega640-1280-1281-2560-2561_datasheet.pdf) 
sollte dieses automatisch wieder zurückgesetzt werden (When the STOP 
condition is executed on the bus, the TWSTO bit is cleared 
automatically.). Daraufhin bleibt das Programm in der While-Schleife 
(Zeile 340-342) stecken, da dies scheinbar nicht funktioniert, siehe 
Code.

while(TWCR & _BV(TWSTO)){
    continue;
  }

Damit das Programm weiterläuft, wurde ein Abbruchkriterium (Idee aus 
WWW) für alle 5 while-Schleifen in twi.c eingesetzt, siehe Code.

  twi_tout(1);
  while(TWCR & _BV(TWSTO)){
    if (twi_tout(0)) return;
    continue;
  }
…
//Nirea. Time Out
static volatile uint32_t twi_toutc;
uint8_t twi_tout(uint8_t ini)
{
   if (ini) twi_toutc=0; else twi_toutc++;
   if (twi_toutc>=100000UL) {
      twi_toutc=0;
      twi_init();
      return 1;
   }
  return 0;
}

Dies Hilft aus der while-Schleife nach wenigen ms zu springen. Wird 
jedoch in Folge wieder ein Slave über den Befehl wire.requestFrom() 
angesprochen, wird die Funktion uint8_t twi_readFrom (…) in twi.c in 
Zeile 115 aufgerufen. Darin befindet sich ein Start-Condition die in 
TWCR geschrieben werden soll (Zeile 159),
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);.

Und hier wird der TWI Bus nicht mehr wissen was er machen soll, da ja 
voraussichtlich das TWSTO Bit noch gesetzt ist.  Die Idee TWSTO vorab 
programmtechnisch auf 0 zu setzen, habe ich gehabt. Leider ohne Erfolg.

Ich suche nach einem möglichen Rückgabewert bzw. ein Möglichkeit
Bus-Stop auszuführen, ohne das mein Programm neustartet.

Hat jemand eine Idee?

Beste Grüße
Jörg

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg Thomas schrieb:
> Zeile 115 aufgerufen. Darin befindet sich ein Start-Condition die in
> TWCR geschrieben werden soll (Zeile 159),
> TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);.
>
> Und hier wird der TWI Bus nicht mehr wissen was er machen soll, da ja
> voraussichtlich das TWSTO Bit noch gesetzt ist.  Die Idee TWSTO vorab

 Wie gesetzt ?
 Du hast ihn mit obigem Befehl gerade auf Null gesetzt.
 Es ist nicht TWCR |=, sondern TWCR =
 Entschuldige wenn ich da was falsch verstehe...

von Jörg T. (kallejoerg)


Lesenswert?

Hallo Marc,

meine C# Kenntnisse sind nicht die besten aber ich verstehe unter den | 
Operator eine bitweise ODER-Verknüpfung, z.B.

1100
1010
----
1110

Der Operator |= kann man ja auch anderes schreiben, z.B.
X|=Y entspricht X= X|Y.

Somit gehe ich von der Zeile
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
davon aus, dass die TWCR-Registerbits TWEN, TWEIE, TWEA, TWINT und TWSTA 
auf "1" gesetzt werden, zumal das Makro _BV folgende Bedeutung hat:
#default _BV(bit) \ (1<<(bit))

Beste Grüße

von Amateur (Gast)


Lesenswert?

>Der Operator |= kann man ja auch anderes schreiben, z.B.
>X|=Y entspricht X= X|Y.

Irgendwie habt Ihr beide recht und gleichzeitig auch nicht.

Bei X=bb|cc|dd|ee;
bleibt vom ursprünglichen X nichts über
bei X|=bb|cc|dd|ee;
bleiben ursprüngliche Bits verschont.

Also:

X ist 0b10001000
X  =0b00000100|0b00000001; --> X = 0b00000101
X |=0b00000100|0b00000001; --> X = 0b10001101
X=X|0b00000100|0b00000001; --> X = 0b10001101

von Jörg T. (kallejoerg)


Lesenswert?

Danke für deinen Beitrag. Dies würde meine Thoerie bestätigen und nach 
der TWCR = .... Anweisung das Bit TWSTO auf "1" gesetzt werden.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg Thomas schrieb:
> Somit gehe ich von der Zeile
> TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
> davon aus, dass die TWCR-Registerbits TWEN, TWEIE, TWEA, TWINT und TWSTA
> auf "1" gesetzt werden, zumal das Makro _BV folgende Bedeutung hat:
> #default _BV(bit) \ (1<<(bit))

 Ja, nur wird TWCR hier erst mit Null geladen und dann OR.
 Beim TWCR |= bla, bla, wird zuerst TWCR gelesen und dann OR.
 Somit interessiert dich TWSTO nicht, weil der mit obigem Befehl
 automatisch auf Null geht.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg Thomas schrieb:
> Danke für deinen Beitrag. Dies würde meine Thoerie bestätigen und nach
> der TWCR = .... Anweisung das Bit TWSTO auf "1" gesetzt werden.

 ???
 Wie ?

von Jörg T. (kallejoerg)


Lesenswert?

Und warum bleibt er bei einem o.g. Fehlerfall in der folgenden 
while-Schleifewhile (TWCR & _BV(TWSTO)) stehen, wenn er nach deiner 
Meinung auf "0" steht?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Amateur schrieb:
> Irgendwie habt Ihr beide recht und gleichzeitig auch nicht.

 wo bin ich im unrecht ?

von Jörg T. (kallejoerg)


Lesenswert?

Hallo Marc,

hier nochmal der Code:

void twi_stop(void)
{
  // send stop condition
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

  // wait for stop condition to be exectued on bus
  // TWINT is not set after a stop condition!
  while(TWCR & _BV(TWSTO)){
    continue;
  }
...

Bei einem Fehlerfall bleibt das Programm in der while-Schleife stehen, 
somit gehe ich davon aus das im TWCR Register das TWSTO Bit auf "1" 
steh. Oder habe ich einen Denkfehler?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg Thomas schrieb:
> Und warum bleibt er bei einem o.g. Fehlerfall in der folgenden
> while-Schleifewhile (TWCR & _BV(TWSTO)) stehen, wenn er nach deiner
> Meinung auf "0" steht?

  Auweia.
  Hier wird TWSTO gesetzt (von dir):
  // send stop condition
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

  Hier wartest du, dass die STOP-condition ausgeführt wird:
  // wait for stop condition to be exectued on bus
  // TWINT is not set after a stop condition!
  while(TWCR & _BV(TWSTO)){
    continue;
  }

 STOP-condition: SDA-Leitung von LOW- auf HIGH-Pegel, SCL Leitung
 befindet sich auf High-Pegel.
 Low-Pegel ist Dominant, wenn irgendeine Device SDA-Leitung nicht
 freigibt, oder die Leitung im Schluss ist, kann die STOP-condition
 NIEMALS auftretten.

 Deswegen kommst du auch in einem Fehlerfall nie aus der Schleife
 heraus und deswegen ist diese Schleife auch (mE) schlecht geschrieben.

 Klar soweit ?

von Jörg T. (kallejoerg)


Lesenswert?

Somit komm ich aus der Misere bei einem o.g. Fehlerfall auch mit einem 
Abbruchkriterium nie raus, oder?

Seh ich das richtig, das ich am besten eine Fehlerrückgabe in der 
While-Schleife vornehmen müsste?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Marc Vesely schrieb:
> Deswegen kommst du auch in einem Fehlerfall nie aus der Schleife
>  heraus und deswegen ist diese Schleife auch (mE) schlecht geschrieben.
>
>  Klar soweit ?

 Okay.
 Nun ist dein Programm mit Hilfe des Abbruchkriteriums aus der
 Schleife heraus.
 TWSTO ist immer noch auf 1

Jörg Thomas schrieb:
> Zeile 115 aufgerufen. Darin befindet sich ein Start-Condition die in
> TWCR geschrieben werden soll (Zeile 159),
> TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);.

 Deine Zuweisung ist '=", also nimmt der Compiler einen Register -
 irgendeinen, hängt vom Compiler ab, und setzt den erst mal aufs Null.
 Danach wird dieser Register OR mit:
  _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
 Und dann wird TWCR mit diesem Register geladen.

 Wenn aber deine Zuweisung " |=" ware, wurde der Compiler erst
 TWCR einlesen, Register mit diesen Wert laden und weiter wie oben.

 Klar soweit ?

von Jörg T. (kallejoerg)


Lesenswert?

Klar soweit aber mit dem geänderten Operator ist TWSTO immernoch "1".

TWCR|= _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

Das ist somit nur ein noch besseres Abbruchkriterium, oder?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg Thomas schrieb:
> Somit komm ich aus der Misere bei einem o.g. Fehlerfall auch mit einem
> Abbruchkriterium nie raus, oder?

 Ja, wieso ?

Jörg Thomas schrieb:
> Seh ich das richtig, das ich am besten eine Fehlerrückgabe in der
> While-Schleife vornehmen müsste?

 Nee, wieso ?
 Spaß beiseite, es ist doch gar nicht so schlimm - eher schlimmer.

 Fall 1: Du kommst aus der Warteschleife zurück, TWSTO = 0.
   Alles in Ordnung, weiter im Programm.

 Fall 2: Du kommst aus der Warteschleife zurück, TWSTO = 1.
   Keine STOP-condition aufgetretten, SDA Leitung auf Level
   überprüffen - wenn SDA = 0, dann ist weiteres vorgehen im
   Programm sowieso sinnlos - weitere Kommunikation auf dem Bus
   ist unmöglich.

 LED an, Meldung über RS232, USB, SPI, Feuerwehr benachrichtigen...

von Jörg T. (kallejoerg)


Lesenswert?

Vielen Dank für deine Hilfe.

von Jörg T. (kallejoerg)


Angehängte Dateien:

Lesenswert?

Hallo Zusammen,

wie bekannt ist, ist meine I2C-Kommunikation bei einer Fehlersimulation 
(Wackelkontakt SDA- bzw. SCL-Leitung oder SDA- und SCL-Leitung gebrückt) 
ausgefallen. Mit Hilfe des Oszilloskops habe ich erkannt, dass dann die 
SDA-Leitung dauerhaft auf LOW liegt. Dieser LOW-Pegel wurde 
hervorgerufen durch den entsprechende Slave und/ oder des Masters, siehe 
Signalverlauf im Anhang.

Fehlerbehebung konnte erfolgen durch:

Slave-Seite:
Einbau eine bilateralen Switch! Bei einem Fehlerfall wurde der Slave 
kurzzeitig abgeschalten.

Master-Seite:
Sobald das TWEN: TWI Enable Bit im TWCR Register gesetzt wurde ist kein 
weiteres Zugriff auf die SCL & SDA I/O Pins möglich. Dieses Bit wird 
sobald der Befehl „wire.begin();“ aufgerufen wird dauerhaft gesetzt. Da 
bei einem Fehler ein Problem im I2C-Modul vorliegt, wollte ich versuchen 
einen Urzustand herzustellen. Dies konnte ich ermöglichen indem ich die 
wire library mit den Änderung von https://github.com/steamfire/WSWireLib 
vorgenommen und zusätzlich folgendes im twi.cpp file geändert (Fett 
Markierung):

void twi_init(void)
{
  // initialize state
  twi_state = TWI_READY;
  twi_sendStop = true;    // default value
  twi_inRepStart = false;

  TWCR=0;
  delay(1);
  pinMode(SDA , OUTPUT);
  pinMode(SCL , OUTPUT);

  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
  delay (200);

  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);
  delay(200);

  // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

  /* twi bit rate formula from atmega128 manual pg 204
  SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
  note: TWBR should be 10 or higher for master mode
  It is 72 for a 16mhz Wiring board with 100kHz TWI */

  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);

}

Sobald auf Seiten des Master die SDA-Leitung runtergezogen wird, kann 
das I2C-Modul rückgesetzt werden. Dies wird ermöglicht durch den 
Codezusatz twi_tout () in den entsprechenden while-Schleifen.

Natürlich könnte man jetzt verschiedene Fehlerrutinen zusätzlich 
programmiert.

Ich hoffe den einen oder anderen Hilft diese Information.

Beste Grüße

Jörg

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.