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.
> 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
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.
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ß
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.
@ 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.
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
staticvoidi2c_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_ti=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.
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.
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.
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.
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.
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
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...
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 ;-)
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.
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...