Forum: Mikrocontroller und Digitale Elektronik Erfahrungen mit I²C


von Hansmeier (Gast)


Lesenswert?

Hallo,

ich habe ein Problem mit dem I²C-Bus in einem embedded System. Der 
Microcontroller ist ein C161, an dem über besagtem Bus 
IO-Treiberbausteine, ein EEPROM und ein RTC hängen.

Das Programm ist ein C-Programm und läuft sequentiell ab. Durch einen 
vorgegeben TIMETICK wird mit allen Komponenten über den I²C-Bus 
kommuniziert.

Es funktioniert reibungsfrei, nur in sehr seltenen Zeitpunkten bricht 
die gesamte Kommunikation plötzlich zusammen und alle Funktionen, die 
vom Bus abhängen, funktionieren einfach nicht mehr.

Das kann direkt nach dem starten des Systems passieren oder nach einem 
durchlaufenden Betrieb von mehreren Tagen.

Hat jemand hier schon so ähnliche Erfahrungen gemacht und eventuell 
sogar die Ursache rausgefunden?

Es darf gemutmaßt werden, womit das zusammenhängen könnte.

von Olaf (Gast)


Lesenswert?

> Hat jemand hier schon so ähnliche Erfahrungen gemacht und eventuell
> sogar die Ursache rausgefunden?

Klar. Das ist systemimmanentes Problem von I2C. Fuer Sachen die 24/7 
funktionieren sollen benutzt man besser was anderes.

Ursache ist eine Uebertragungsstoerung die eine der Statemaschinen in 
den Slaves aus dem Tritt bringen. Sowas kann man natuerlich durch 
vernuenftiges Hardwaredesign verbessern, aber irgendwann gibt es immer 
mal einen Bitfehler.

> Es darf gemutmaßt werden, womit das zusammenhängen könnte

Ein weiteres Problem ist vermutlich Unerfahrenheit beim Programmierer. 
:-)

Man muss damit rechnen das der Bus irgendwann haengt. Dann muss man ihn 
wieder neu aufsetzen. (raustakten einiger Dummybytes) Das hilft oft, 
aber nicht immer. Es gibt auch Slave die haengen sich so weg das sie 
einmal kurz ausgeschaltet werden muessen.

Olaf

von Helmut S. (helmuts)


Lesenswert?

Sendet(taktet) ihr schneller als im Datenblatt erlaubt?

von Hansmeier (Gast)


Lesenswert?

Helmut S. schrieb:
> Sendet(taktet) ihr schneller als im Datenblatt erlaubt?

Nee, eigentlich nicht. Nur die erlaubten 400 KBit/sec.

von Hansmeier (Gast)


Lesenswert?

Olaf schrieb:
>> Hat jemand hier schon so ähnliche Erfahrungen gemacht und
> eventuell
>> sogar die Ursache rausgefunden?
>
> Klar. Das ist systemimmanentes Problem von I2C. Fuer Sachen die 24/7
> funktionieren sollen benutzt man besser was anderes.
>
> Ursache ist eine Uebertragungsstoerung die eine der Statemaschinen in
> den Slaves aus dem Tritt bringen. Sowas kann man natuerlich durch
> vernuenftiges Hardwaredesign verbessern, aber irgendwann gibt es immer
> mal einen Bitfehler.
>
>> Es darf gemutmaßt werden, womit das zusammenhängen könnte
>
> Ein weiteres Problem ist vermutlich Unerfahrenheit beim Programmierer.
> :-)
>
> Man muss damit rechnen das der Bus irgendwann haengt. Dann muss man ihn
> wieder neu aufsetzen. (raustakten einiger Dummybytes) Das hilft oft,
> aber nicht immer. Es gibt auch Slave die haengen sich so weg das sie
> einmal kurz ausgeschaltet werden muessen.
>
> Olaf


Danke für die Antwort. Ich habe mir auch schon gedacht, dass es kein 
richtiger Softwarefehler ist, weil dieser Fehler äußerst selten auftritt 
und man ihn gar nicht reproduzieren kann. Scheint ein transienter Fehler 
zu sein.

Könntest du eventuell deine Lösungsansätze zur Behebung von 
Busaussetzern etwas detaillierter beschreiben, möglicherweise könnte man 
dadurch einen Dauerbetrieb erreichen.

von Marvin (Gast)


Lesenswert?

Hi,

Die Situation kann folgene sein (Master macht read auf einen Slave) :
Einer der Slaves ist noch nicht bereit, dass nächste Byte auf dem Bus zu 
legen und hat "clock stretching" implementiert. Der Master kennt clock 
stretching nicht und taktet fleissig weiter. Nun bekommt der Slave ein 
paar Zyklen der Clock nicht mit. Der Master ist fertig. Der Slave treibt 
nun die Datenleitung, aber ne Clock fehlt -> Bus hängt sich auf.

Frage : Ist die Datenleitung im Fehlerfall "low" ?
Wenn ja, im Fehlerfall nacheinander die Datenleitungen an den Slaves 
unterbrechen -> Damit findest Du den Übeltäter.

Fehlerbehebung :
Sende ein paar Clock Zyklen (normalerweise sind es 8+1, worst case müßte 
man also 7+1 clocks senden + STOP) ohne dass der Master SDA asserted 
(NACK). Die Datenleitung sollte spätestens nach 7 clocks high werden. 
Damit gibst Du den Bus frei. Die 8. Clock und das Stop sollte das 
Interface vom Slave resetten.

Ganz schlecht wäre es, wenn SCL low bleibt...dann wie mit SDA Übeltäter 
identifizieren und Hersteller anschreiben...falls Master : Sein Programm 
mal prüfen :-)

Auch durch andere Einflüsse können solche Fehler passieren und u.U. so 
korregieren.

Gruß

von Mehmet K. (mkmk)


Lesenswert?

Mir ist zwar sowas noch nie passiert. Nichtsdestotrotz: solange das 
Design es zulaesst, spendiere ich jedem I2C-Device ein p-channel Mosfet, 
mit dem ich ihm notfalls den Saft abdrehen kann.

von Falk B. (falk)


Lesenswert?

@ Hansmeier (Gast)

>Danke für die Antwort. Ich habe mir auch schon gedacht, dass es kein
>richtiger Softwarefehler ist, weil dieser Fehler äußerst selten auftritt
>und man ihn gar nicht reproduzieren kann. Scheint ein transienter Fehler
>zu sein.

Eben DAS ist ein SEHR gefählicher Fehler, weil er so schlecht 
reproduzier bar ist. Es könnte ein Fehler im Zusammenhang mit 
einemInterrupt sein.

>Könntest du eventuell deine Lösungsansätze zur Behebung von
>Busaussetzern etwas detaillierter beschreiben, möglicherweise könnte man
>dadurch einen Dauerbetrieb erreichen.

Kann man auch, sooo schlecht ist I2C nicht mal ansatzweise. Aber wie 
schon gesagt wurde, MUSS die Software so robust sein, und ein 
gelegentliche Klemmen der Übertragung oder Bitfehler tolerieren, ohne 
durchzudrehen.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

ich hab auf jedem master folgende "hart" i2c-reset-funktion mit drinnen 
(ist für AVR / ATmega328P)
1
#define I2C_PORT PORTC
2
#define I2C_DDR DDRC
3
#define I2C_SDA _BV(4)
4
#define I2C_SCL _BV(5)
5
6
#define SCL_LO do { I2C_PORT &= ~I2C_SCL; I2C_DDR  |= I2C_SCL; } while (0)
7
#define SCL_HI do { I2C_DDR  &= ~I2C_SCL; I2C_PORT |= I2C_SCL; } while (0)
8
9
#define SDA_LO do { I2C_PORT &= ~I2C_SDA; I2C_DDR  |= I2C_SDA; } while (0)
10
#define SDA_HI do { I2C_DDR  &= ~I2C_SDA; I2C_PORT |= I2C_SDA; } while (0)
11
12
13
static void i2c_reset(void)
14
{
15
    TWCR = 0;      // disable TWI
16
17
    I2C_DDR &= ~(I2C_SCL | I2C_SDA);  // set pins to input
18
    I2C_PORT |= (I2C_SCL | I2C_SDA);  // enable pullups
19
20
    _delay_us(100);
21
22
    // toggle clock 9 times
23
    for (uint8_t i = 9; i > 0; i--) {
24
  SCL_LO;      // pull SCL low
25
  _delay_us(5);
26
  SCL_HI;      // release SCL
27
  _delay_us(5);
28
    }
29
30
    // manually send a STOP condition
31
    _delay_us(5);
32
    SCL_LO;
33
    _delay_us(5);
34
    SDA_LO;
35
    _delay_us(5);
36
    SCL_HI;
37
    _delay_us(5);
38
    SDA_HI;
39
40
    _delay_ms(10);
41
    TWCR = _BV(TWEN);    // enable TWI to standby state
42
    _delay_ms(10);
43
}

Funktioniert bei mir seit Jahren problemlos.

Zusätzlich ist es hilfreich, in die i2c-routinen einen Error-Callback 
einzubauen, der Kommunikationsfehler z.B. per UART rausschreibt.

von Hansmeier (Gast)


Lesenswert?

Michael Reinelt schrieb:
> #define SCL_LO do { I2C_PORT &= ~I2C_SCL; I2C_DDR  |= I2C_SCL; } while
> (0)
> #define SCL_HI do { I2C_DDR  &= ~I2C_SCL; I2C_PORT |= I2C_SCL; } while
> (0)
>
> #define SDA_LO do { I2C_PORT &= ~I2C_SDA; I2C_DDR  |= I2C_SDA; } while
> (0)
> #define SDA_HI do { I2C_DDR  &= ~I2C_SDA; I2C_PORT |= I2C_SDA; } while
> (0

Hi,

so eine Code werde ich auch mal basteln, um den Bus wieder zum laufen zu 
bringen. Wenn der Bus hängt, gibt es eindeutige Zeichen, die man während 
der Softwarelaufzeit abfragen kann und kann dann die RESET-Funktion 
aufrufen.

Was mich aber hier wundert, ist do{}while(0). Sicher funktioniert das, 
aber für mich sieht es so aus, als ob die Funktion nur einmal aufgerufen 
wird und eigentlich keiner Schleife bedarf, weil die Schleife nie ein 
zweites mal durchlaufen wird.
Das sind ja Tricks hier, völlig unkonventionell.

von Kein Name (Gast)


Lesenswert?

>völlig unkonventionell
War früher, als es noch keine Inline-Funktionen gab durchaus so üblich.

von Hansmeier (Gast)


Lesenswert?

Falk Brunner schrieb:
> @ Hansmeier (Gast)
>
>>Danke für die Antwort. Ich habe mir auch schon gedacht, dass es kein
>>richtiger Softwarefehler ist, weil dieser Fehler äußerst selten auftritt
>>und man ihn gar nicht reproduzieren kann. Scheint ein transienter Fehler
>>zu sein.
>
> Eben DAS ist ein SEHR gefählicher Fehler, weil er so schlecht
> reproduzier bar ist. Es könnte ein Fehler im Zusammenhang mit
> einemInterrupt sein.
>

Ja, der TimeTick ist Interrupt gesteuert und wird bei kritischen 
Codesequenzen wie das auslesen und schreiben auf IIC gesperrt, aber 
wieso sollte das denn Bus stören? Der bekommt ja seine Befehle vom 
internen Timer.

von Hansmeier (Gast)


Lesenswert?

Mehmet Kendi schrieb:
> Mir ist zwar sowas noch nie passiert. Nichtsdestotrotz: solange
> das
> Design es zulaesst, spendiere ich jedem I2C-Device ein p-channel Mosfet,
> mit dem ich ihm notfalls den Saft abdrehen kann.


Das ist ja ein sehr harter Reset, das würde sicher die Probleme 
erschlagen, aber hhmm, naja ich weiss nicht.

von Hansmeier (Gast)


Lesenswert?

Marvin schrieb:
> Fehlerbehebung :
> Sende ein paar Clock Zyklen (normalerweise sind es 8+1, worst case müßte
> man also 7+1 clocks senden + STOP) ohne dass der Master SDA asserted
> (NACK). Die Datenleitung sollte spätestens nach 7 clocks high werden.
> Damit gibst Du den Bus frei. Die 8. Clock und das Stop sollte das
> Interface vom Slave resetten.


Sollte während des Clockens die SDA vom Master high oder low sein?

Ich denke mal high, weil so wir wollen ja den BUS am ende auch wieder 
vom low-Zustand befreien.

von Sven (Gast)


Lesenswert?

Hansmeier schrieb:
>> #define SCL_LO do { I2C_PORT &= ~I2C_SCL; I2C_DDR  |= I2C_SCL; } while
>> (0)
>> #define SDA_HI do { I2C_DDR  &= ~I2C_SDA; I2C_PORT |= I2C_SDA; } while
>> (0
> Was mich aber hier wundert, ist do{}while(0). Sicher funktioniert das,
> aber für mich sieht es so aus, als ob die Funktion nur einmal aufgerufen
> wird und eigentlich keiner Schleife bedarf, weil die Schleife nie ein
> zweites mal durchlaufen wird.
> Das sind ja Tricks hier, völlig unkonventionell.

Das mit dem do-while hat einen einfach Hintergrund, erstens benötigst du 
einen ; als Abschluss und der code funktioniert auch in if-bodies ohne 
klammern, etc. Das ist eine übliche Programmierweise bei Makros.

Ich zitiere dazu mal: 
http://stackoverflow.com/questions/154136/do-while-and-if-else-statements-in-c-c-macros

von ben (Gast)


Lesenswert?

Wie bereits gesagt wurde, das do-while Konstrukt in Makros ist durchaus 
üblich.
Was mich bei sowas immer stört sind die fehlenden Klammern. Hier handelt 
es sich ja eindeutig um ein function-like Makro, das anstelle einer 
inline-Funktion genutzt wird. Also bitte auch so kennzeichnen!

Ansonsten siehts aus wie ein Statement, das nichts bewirkt.
Sowas kann den "inneren Parser" sehr durcheinander bringen...

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

ben schrieb:
> Hier handelt
> es sich ja eindeutig um ein function-like Makro, das anstelle einer
> inline-Funktion genutzt wird. Also bitte auch so kennzeichnen!

Wie meinst du, "kennzeichnen"?

Generell habts ihr aber recht: "heutzutage" sollte man das besser als 
inline-Funktion lösen. Ich muss mal mit meinem Unterbewusstsein und 
dessen gewohnheiten ein ernstes Wort reden ;-)

: Bearbeitet durch User
von ben (Gast)


Lesenswert?

Michael Reinelt schrieb:
> Wie meinst du, "kennzeichnen"?

SCL_LO() statt SCL_LO. Dadurch wird sofort ersichtlich dass hier "etwas 
passiert", du das Makro eben "function-like" verwendest.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

ben schrieb:
> Michael Reinelt schrieb:
>> Wie meinst du, "kennzeichnen"?
>
> SCL_LO() statt SCL_LO. Dadurch wird sofort ersichtlich dass hier "etwas
> passiert", du das Makro eben "function-like" verwendest.

Ok, hast recht...

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.