Forum: Mikrocontroller und Digitale Elektronik "hartes" Warten auf Pegel ohne Abbruchkriterium


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


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

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


Lesenswert?

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...
1
uint8_t timeout = 200;
2
while (SCL_PIN & SCL_BIT == 0 && --timeout > 0) {
3
   _delay_ms(1);
4
}

wäre das so ok?

von Wolfgang H. (frickelkram)


Lesenswert?

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.

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


Lesenswert?

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...

von Wolfgang H. (frickelkram)


Lesenswert?

Guckst Du hier
http://www.atmel.com/Images/doc2551.pdf
und hier
https://www.mikrocontroller.net/articles/AVR-Tutorial:_Watchdog

Wenn Du es richtig machst kannst Du auch den Fehler selbst erkennen und 
signalisieren ... bei Deinen 5 Sensoren kein Problem.

von Oliver S. (oliverso)


Lesenswert?

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

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


Lesenswert?

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_t timeout = 200;
2
while (SCL_PIN & SCL_BIT == 0 && --timeout > 0) {
3
   _delay_ms(1);
4
}
5
if (timeout == 0)
6
   return NAK;

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...

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Michael Reinelt schrieb:

>
1
> uint8_t timeout = 200;
2
> while (SCL_PIN & SCL_BIT == 0 && --timeout > 0) {
3
>    _delay_ms(1);
4
> }
5
> if (timeout == 0)
6
>    return NAK;
7
>


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_t timeout = 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
    return NAK;

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.

von Wolfgang H. (frickelkram)


Lesenswert?

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.

von spess53 (Gast)


Lesenswert?

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

von Wolfgang H. (frickelkram)


Lesenswert?

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 ...

von Dietrich L. (dietrichl)


Lesenswert?

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_t volatile timeout = 200;
2
> while (SCL_PIN & SCL_BIT == 0 && timeout > 0) {
3
> }
4
> if (timeout == 0)
5
>    return NAK;
Dann geht bei der Abfrage keine Wartezeit verloren.

Gruß Dietrich

von Nicolas S. (Gast)


Lesenswert?

Dietrich L. schrieb:
> Dann geht bei der Abfrage keine Wartezeit verloren.

Und was machst Du in der gesparten Zeit?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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_t volatile timeout = 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 :-)

von Dietrich L. (dietrichl)


Lesenswert?

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

von Dietrich L. (dietrichl)


Lesenswert?

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

von Wolfgang H. (frickelkram)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Clemens S. (zoggl)


Lesenswert?

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 ;)

von Wolfgang H. (frickelkram)


Lesenswert?

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.

von Reinhard Kern (Gast)


Lesenswert?

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

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


Lesenswert?

Ich danke euch für die interessante und lehrreiche Diskussion!

ich werde als auf die Prüfung verzichten, dafür Watchdog + Panic-LED 
vorsehen.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Wolfgang H. (frickelkram)


Lesenswert?

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.

von Little Helper (Gast)


Lesenswert?

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.

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.