Hallo zusammen,
immer wieder sehe ich Codefragmente in solcher oder ähnlicher Form:
1
i2c_ack_wait:
2
sbis SCL_IN,SCL ;wait SCL high (in case wait states are inserted)
3
rjmp i2c_ack_wait
(Quelle: Soft-I2C von Peter Fleury)
Meine bedenken sind: Was wenn aufgrund eines Fehlers im Slave dieser
Clock-Leitung dauerhaft auf Low zieht? Dann bleibt das Programm ewig im
i2c_write hängen, ohne Fehlerbehandlung, ohne irgendwas.
Muss einem das Sorgen bereiten? Wenn ja, was dagegen tun? Zähler+Delay,
was aber nicht unbedingt effizienzsteigernd ist...
oder ist es "anerkannte praxis" zu sagen: Das darf nciht vorkommen, und
wenn doch, muss halt jemand den Reset-Knopf drücken?
Michael Reinelt schrieb:> Meine bedenken sind: Was wenn aufgrund eines Fehlers im Slave dieser> Clock-Leitung dauerhaft auf Low zieht?
Dann hast du ein Problem.
> Dann bleibt das Programm ewig im> i2c_write hängen, ohne Fehlerbehandlung, ohne irgendwas.
Richtig.
> Muss einem das Sorgen bereiten?
Kommt drauf an, wie hoch du die Wahrschinelichkeit dafür einschätzt. In
einer fixen µC-Applikation, in der der Slave nicht abgekoppelt sein
kann, kann man das wahrscheinlich ignorieren.
Als Teil eines Betriebssystems, welches I2C als Service für beliebige
Applikationen bereit stellt, wird man sich diesen Luxes nicht leisten
können.
> Wenn ja, was dagegen tun? Zähler+Delay,> was aber nicht unbedingt effizienzsteigernd ist...
Tja. So ist das nun mal.
Von nichts kommt nichts. Und gerade Fehlerbehandlung zieht einem oft
einen Strich durch die Rechnung.
Die Frage geht ja dann auch weiter: Was macht der übergeordnete Code,
wenn der Transfer schief geht? Behandelt der das sauber? Behandeln die
weiteren Algorithmen diesen Fall sauber, dass es kein weiteren Daten
gibt? Fehlerbehandlung zieht meistens Kreise.
Karl Heinz Buchegger schrieb:> Die Frage geht ja dann auch weiter: Was macht der übergeordnete Code,> wenn der Transfer schief geht? Behandelt der das sauber? Behandeln die> weiteren Algorithmen diesen Fall sauber, dass es kein weiteren Daten> gibt? Fehlerbehandlung zieht meistens Kreise.
Der Teil ist einfach: das i2c-write liefert ja sowieso das ACK/NAK des
Client "nach oben", in dem Fall würde ein Hängen des SCL auf Low wie ein
NAK interpretiert.
Aber generell hast du schon recht: Es gibt nix besch**sseneres als ein
sauberes übersichtliches Error-Handling :-(
(und kommts mir jetzt nicht mit euren "Exceptions", ich sagte
"übersichtlich")
Hmmm... der Slave ist nicht wirklich fix verbaut, da könnte schon mal
was schiefgehen. In meinem Fall gehts nicht mal um i2c sondern um den
e2-bus (eine langsame Abart von i2c für Sensoren von E+E).
Werd ich wohl eine dumme schleife machen...
Ja, passieren kann das und dein Code bleibt dann hängen.
Anstatt aber alle Schleifen so zu schreiben das ein Hängern überwacht
wird, kannst Du entweder einen Watchdog-Timer verwenden, den einige
Prozessoren schon mit bringen, oder Du baust Dir eine Wacthdog-Software
Lösung mit einem übrig geblieben Timer, oder packst den "Soft-Watchdog"
noch mit in eine ISR-Routine, die regelmäßig aufgerufen wird und in der
Du noch ein wenig Code unterbringen kannst.
Bei MPS430xx Prozessoren von TI ist der Watchdog z.B. beim Start ein
geschaltet und Du musst ihn entweder explizit aus schalten oder
bedienen.
Mit "delay" würde ich da nicht arbeiten, das bremst die Kommunikation
insgesamt aus.
Und was macht der Watchdog?
Am AVR (um den gehts hier konkret) gibts zwar einen, hab aber noch nie
damit gearbeitet.
Wenn ich z.B. 5 Sensoren abfrage, und Nummer 4 ein problem hat, wärs
schon spannend das zu wissen, ohne Hängen, oder komplett-Reboot.
Andererseits: Wenn jemand die SCL-leitung dauerhaft auf Low zieht, ist
eh der ganze Bus blockiert, und alle Sensoren hängen...
Michael Reinelt schrieb:> Werd ich wohl eine dumme schleife machen...>...> wäre das so ok?
Die Schleife an sich schon, nur, wie geht es dann im Falle eines Falles
weiter?
Oliver
Oliver S. schrieb:> Michael Reinelt schrieb:>> Werd ich wohl eine dumme schleife machen...>>...>> wäre das so ok?>> Die Schleife an sich schon, nur, wie geht es dann im Falle eines Falles> weiter?
1
uint8_ttimeout=200;
2
while(SCL_PIN&SCL_BIT==0&&--timeout>0){
3
_delay_ms(1);
4
}
5
if(timeout==0)
6
returnNAK;
Das NAK heisst eh soviel wie "Slave mag mich nicht", ob der jetzt
tatsächlich mit NAK antwortet oder gar nicht antwortet (durch Pullup
ergibt sich ein NAK) oder der Bus hängt...
Michael Reinelt schrieb:> Und was macht der Watchdog?
Einen µC-Reset
> Am AVR (um den gehts hier konkret) gibts zwar einen, hab aber noch nie> damit gearbeitet.
Dann fängt dein Programm von vorne an. Und das beginnt höchst
wahrscheinlich damit, die Hardware zu überprüfen. Ob noch alle Sensoren
da sind, ob es Probleme gibt, wie zb eine auf Dauer-Low gezogene SCL
Leitung und meldet dann dieses Problem dem Benutzer. Beim Hochfahren des
Systems hat man auch oft den Luxus, dass das alles nicht wirklich
zeitkritisch ist. D.h. ob du 5ms lang die SCL Leitung überwachst, ob die
jemals auf High geht, tut dir nicht wirklich weh. Das 'System' steht zu
diesem Zeitpunkt ja sowieso.
Bei manchen Problemen, wie zb ein Sensor meldet sich nicht mehr aber die
anderen funktionieren könnte dann sich das Programm beim Startup auf
diese Situation konfigurieren und trotzdem laufen. Bei wieder anderen
blinkt dann eben eine 'fatal-Error' LED. etc. etc.
Genau das meinte ich:
Aus dieser Schleife rauskommen ist nur die halbe Miete. Denn irgendwie
muss das Programm ja auch mit der Fehlersituation klar kommen.
Das ist so wie bei einem strncpy. Das ist auch schön und gut, dass
strncpy zumindest den Bufferoverlow handhabt. Aber wenn dann im String
anstelle von "SHUTDOWN" nur noch "SHUTD" steht und der Commando
Interpreter das nicht als Shutdown akzeptiert, dann ist dir damit auch
nicht wirklich geholfen.
Yep.
Wobei.
Wenn du dir das genau überlegst, ist das gar nicht so verheerend für die
Effizienz. Ob der µC jetzt 0.5ms (so lange wie eben der Slave braucht)
wieder und immer wieder das SCL Bit abfrägt, oder ob er zwischendurch
noch eine Variable runterzählt, ist auch schon egal.
Dein _delay soll nicht exzessiv lange sein, aber auch nicht exzessiv
kurz. Aus dem Bauch raus würde ich mal sagen: irgendwas in der
Größenordnung, was der typische Slave so ungefähr an Zeit braucht. So
dass halt die Schleife bei der 2-ten Abfrage der Bedingung abbricht. Du
könntest auch
1
uint8_ttimeout=200;
2
_delay_ms(1);
3
4
while(SCL_PIN&SCL_BIT==0&&--timeout>0){
5
_delay_ms(1);
6
}
7
if(timeout==0)
8
returnNAK;
und den ersten _delay so bemessen, dass er gerade in klein wenig länger
ist, als der Slave normalerweise braucht, so dass die darauf folgende
Abfrage vom SCL im Grunde nur noch Alibimässig ist und nur im Fehlerfall
zu einem FALSE führt. Dann loopst du halt noch ein paar mal bis du dir
sicher bist: das wird nichts mehr. Das dauert schon viel zu lange. Wenn
sich der Slave normalerweise nach 1ms meldet, dann kann man davon
ausgehen, dass er sich nach 10ms sicher nicht mehr melden wird - da ist
was passiert.
Wobei man sich auch überlegen könnte, die Timeout-Vorgabe (die 200)
überhaupt je nach Slave konfigurierbar zu machen.
Karl Heinz Buchegger schrieb:> Michael Reinelt schrieb:
...
> Interpreter das nicht als Shutdown akzeptiert, dann ist dir damit auch> nicht wirklich geholfen.
deswegen schrieb ich davon das Du geschickt Testen musst. Es gibt Fehler
die gut zugeordnet werden könne, z.B. ein Sensor ist kaputt oder das
Kabel ab. Wenn der BUS blockiert ist fallen alle Sensoren aus.
Wenn Du testest musst Du also die übergeordneten Funktionen zuerst
prüfen, dann die untergeordneten. Es ist nicht egal was Du wann wie
testest. Es kann ja sein das Du sonst einfach wieder in deinem Fehler
landest. Dann hast Du einen Watchdog-Schleife. Die hilft Dir genau so
wenig wie die Fehlerbehandlung innerhalb der Kommunikation.
Alle Fehler kannst Du sowieso nicht abfangen. Konzentriere Dich auf die
wahrscheinlichen Fehler. Das ist genau wie bei den Try-Catch
Verschachtelungen in modernen Programmiersprachen. Man kann sich darin
verlieren ohne einen Nutzen zu erzielen.
Vielleicht genügt es Dir ja wirklich nur zu erkennen ob die I2C
Kommunikation generell kaputt ist. Den Einzelfehler kannst Du dann ja
mit dem Messgerät suchen.
Hi
>> Und was macht der Watchdog?>Einen µC-Reset
Bei den neueren AVRs hat man folgende Möglichkeiten zu Wahl:
-Interrupt
-Interrupt mit nachfolgendem Reset
-Reset
MfG Spess
Nachtrag ...
Für den I2C BUS-Test würde ich nicht die selben Routinen verwenden wie
für die eigentliche Kommunikation. Da macht es Sinn eigene
Test-Algorithmen zu entwickeln. Sonst besteht halt wieder die Gefahr in
den Fehler zu laufen ...
Karl Heinz Buchegger schrieb:> Dein _delay ...
Karl Heinz, dass Du das _delay "erlaubst", wundert mich schon ;-))
Warum dann nicht gleich die von Dir auch schon propagierte "saubere"
Lösung:
In der Timer-ISR (z.B. 1ms):
1
if(timeout>0)timeout--;// "Warteschleife"
und im Programm:
1
>uint8_tvolatiletimeout=200;
2
>while(SCL_PIN&SCL_BIT==0&&timeout>0){
3
>}
4
>if(timeout==0)
5
>returnNAK;
Dann geht bei der Abfrage keine Wartezeit verloren.
Gruß Dietrich
Dietrich L. schrieb:> Karl Heinz Buchegger schrieb:>> Dein _delay ...>> Karl Heinz, dass Du das _delay "erlaubst", wundert mich schon ;-))
gegen kurze delay hab ich nichts.
Ich würde auch eine DS1820 Abfrage bedenkenlos mit delay machen.
Der springende Punkt ist 'kurz'! Da wäre eine Timer-Lösung viel zu
aufwändig, zumal man in den typischen paar µs sowieso nichts
vernünftiges dazwischen machen kann.
Dietrich L. schrieb:> Dann geht bei der Abfrage keine Wartezeit verloren.
Na ja.
Der springende Punkt ist doch, dass du an dieser Stelle sowieso warten
musst.
Ob du das jetzt mittels
uint8_t volatile timeout = 200;
while (SCL_PIN & SCL_BIT == 0 && timeout > 0) {
}
und timer machst, oder mittels
uint8_t volatile timeout = 200;
while (SCL_PIN & SCL_BIT == 0 && timeout > 0) {
_delay_us( 10 );
}
ist auch schon egal. Der Delay sollte IMHO nicht so kurz sein, dass aus
dem uint8_t ein uint16_t werden muss. er sollte aber auch nicht so lang
sein, dass da de facto nur alle heiligen Zeiten nachgesehen wird. Wie
gesagt: ich denke aus dem Bauch raus wäre eine Wartezeit in der
Größenordnung der typischen Slave Antwortzeit angebracht. Und da finde
ich auch bei
1
uint8_tvolatiletimeout=20;
2
_delay_us(100);
3
4
while(SCL_PIN&SCL_BIT==0&&timeout>0){
5
_delay_us(10);
6
}
nicht viel dabei, wenn der Slave sich normalerweise nach ca. 120us
meldet. Die 100us sind geschenkt und treten sowieso immer auf (egal ob
mit oder ohne Timer). Danach wird im 10us Takt abgefragt. Im schlimmsten
Fall werden dann gerade mal 10us verplempert, was ja nicht exzessiv viel
ist und wo dann eine Timerlösung mit ISR betreten, bearbeiten und
verlassen dann auch schon in diese Größenordnung kommt.
Aber letzten Endes, muss das jeder machen wie er es für richtig hält.
Was natürlich nichts daran ändert, dass ich nach wie vor mein Veto gegen
20 Millisekunden Delays in der Hauptschleife einlege :-)
Nicolas S. schrieb:> Dietrich L. schrieb:>> Dann geht bei der Abfrage keine Wartezeit verloren.>> Und was machst Du in der gesparten Zeit?
Dann läuft das Hauptprogramm so früh wie möglich weiter und tut das, was
es tun soll.
Wenn da allerdings nichts zu tun ist, ist das natürlich egal.
Aber es soll auch Programme geben, die nach der i2c-Übertragung noch
etwas anderes tun sollen ;-)) Und ob der max. "Verlust" von 1ms (in dem
Beispiel) relevant ist, weiß nur der TO und sein gesamtes Programm...
Gruß Dietrich
Karl Heinz Buchegger schrieb:> Im schlimmsten Fall werden dann gerade mal 10us verplempert, was ja> nicht exzessiv viel ist.
Ok, wenn Du so weit runter gehst (10µs) ist das natürlich wurscht bzw.
die Timerlösung nicht sinnvoll.
Aber oft ist es sehr sinnvoll, ein (z.B. 1ms-) Takt zu haben, der an
mehreren Stellen verwendet werden kann. Die Investition in die ISR ist
ja auch nicht so gewaltig...
Und dann kann der Timeout auch großzügiger dimensioniert werden - das
ist ja kein Betriebsfall. Er soll ja im Normalbetrieb nie ansprechen,
und im Fehlerfall ist die verlorene Zeit meist ohne Bedeutung.
Aber ich sehe ein, in dem Fall ist es auch Geschmacksache.
Gruß Dietrich
Dietrich L. schrieb:> Karl Heinz Buchegger schrieb:
...
> Aber ich sehe ein, in dem Fall ist es auch Geschmacksache.>> Gruß Dietrich
Generell kann man natürlich ein wenig warten, das ist hier aber nicht
die entscheidende Frage. Die Frage ist was passiert dann? Die hat Karl
Heinz ja schon gestellt. Wie geht man mit dem Fehler um?
Ich tendiere eher dazu die erprobten Routinen so zu belassen und die
Fehlerbehandlung zu entkoppeln ... mit WDT halt. Ich mache mir um die
Funktion in der Bibliothek dann keine weiteren Gedanken.
Den Funktionstest nach dem Reset sollte man eh machen, wenn man schon
eine "Betriebssichere" Lösung haben will.
Man muss sich halt klar darüber sein ob man es mit einem Regel- oder
einem Fehlerverhalten zu tun hat. In der Regel braucht die Warteschleife
nicht auf Fehlerbedingungen zu regieren.
Wolfgang Heinemann schrieb:> Man muss sich halt klar darüber sein ob man es mit einem Regel- oder> einem Fehlerverhalten zu tun hat. In der Regel braucht die Warteschleife> nicht auf Fehlerbedingungen zu regieren.
Mit dieser Philosophie, denn um nichts anderes handelt es sich ab diesem
Punkt, kommt man glaub ich auf einem µC ganz gut klar. Den Satz, im
Zusammenhang mit einem Watschdog, merk ich mir - das ist prägnant und
gut zu merken.
ich verwende hierzu manchmal einen eigenen timer und eine Variable:
main
HW_Test();
WHILE(1)
{
While(Warten_auf_Bus)
{
Variable=5;
}
Variable=0;
Reset_Timer();
}
ISR_Timer_Overflow
Case Variable OF:
{
0: // gesamtausführung hat zu lange gedauert tue etwas
3: // ADC hat verkackt... tue etwas
5: // Bus hängt tue etwas
}
dazu noch den WD und einen HW Test am Anfang
ja ich weiß, ich bin ein paranoider resourcenverschwender ;)
Clemens S. schrieb:> ich verwende hierzu manchmal einen eigenen timer und eine Variable:> main
...
> 5: // Bus hängt tue etwas> }> dazu noch den WD und einen HW Test am Anfang>> ja ich weiß, ich bin ein paranoider resourcenverschwender ;)
Nein, warum? Ich programmiere auch gerne so das der Code klar
verständlich ist, auch noch nachdem man einige Zeit nicht mehr drauf
geguckt hat. Wenn man den Platz hat warum nicht? Wenn es knapp wird kann
man immer noch optimieren.
Wolfgang Heinemann schrieb:> Die Frage ist was passiert dann? Die hat Karl> Heinz ja schon gestellt. Wie geht man mit dem Fehler um?
Richtig ist, dass man auf viele Fehler in einem beschränkten System
nicht adäquat reagieren kann, weil es einfach keine Möglichkeit gibt,
den Fehler zu umgehen - aber was ich als Minimum immer vorsehen würde,
ist eine blinkende Fehler-LED, sonst merkt man von aussen garnicht, dass
was schiefgelaufen ist.
Die kann ja auch wieder aufhören zu blinken, wenn z.B. die Kommunikation
wieder angelaufen ist.
Gruss Reinhard
In einer seiner Grundlagen Artikel hat Philips/NXP auch eine empfohlene
Prozedur zum Rücksetzen eines hängenden I2C Busses beschrieben.
Ich habs nicht mehr genau im Kopf, aber es war irgendwas mit 9-mal an
der CLK zuppeln, dann schauen, ob DATA wieder high ist, und dann eine
normale STOP Kondition anfügen.
Unter Umständen kann man sich also die komplette Reset Prozedur sparen
und nur den Bus zurücksetzen.
Hi Matthias,
Matthias Sch. schrieb:> In einer seiner Grundlagen Artikel hat Philips/NXP auch eine empfohlene> Prozedur zum Rücksetzen eines hängenden I2C Busses beschrieben.> Ich habs nicht mehr genau im Kopf, aber es war irgendwas mit 9-mal an> der CLK zuppeln, dann schauen, ob DATA wieder high ist, und dann eine> normale STOP Kondition anfügen.
beziehst Du Dich auf das "I2C-bus specification and user manual"?
http://www.nxp.com/documents/user_manual/UM10204.pdf
Weil ich Deinen Hinweis interessant finde habe ich mal darin gestöbert.
Gefunden habe ich ein wiederholtes Senden von START. Slaves sollten
darauf reagieren indem sie ihre Zustandsmaschine zurück setzen.
Im Falle eines defekten Bauteils wird es wohl nichts nützen, aber einen
Versuch ist es Wert, besonders deshalb weil die START-Sequenz in einem
Interface-Treiber sowieso implementiert sein muss und die Erweiterung um
diesen "BUS-Reset" keine große Herausforderung sein sollte.
Den Thread nur überflogen, aber diese Info nicht gefunden:
Der Watchdog muss nicht zwingend einen Reset auslösen, auch ein
Interrupt ist möglich ("Interrupt Mode"). Dort kann man dann z.B. eine
Fehleranalyse und -ausgabe einbauen, wenn gewünscht.