Forum: Mikrocontroller und Digitale Elektronik Mikrocontroller, wie steckengebliebene while vermeiden?


von uks2 (Gast)


Lesenswert?

Hallo,

ich habe eine Frage, die mich schon seit längerem beschäftigt, ich aber 
keine professionelle Hilfe finden konnte.
Ich arbeite mit einem STM32F4 und habe dort ein I2C Sensor 
angeschlossen. Manchmal habe ich das Problem dass ich z.B. in dem 
Bereich stecken beleibe wo auf das erfolgreiche Senden der I2C-Adresse 
gewartet wird.

Hinzu kommt, bevor die while(1) in meiner Main ausgeführt wird und den 
Sensor durchgehend ließt werden viele Parameter vom Sensor gelesen (vor 
while loop - im main Anfang), die nachfolgend für Berechnungen benötigt 
werden. Wie kann ich wenn ein Fehler auftritt hier von vorne anfangen? 
Ist es sinnvoll ein Reset auszuführen wenn die Kommunikation fehlschlägt 
(z.B. eine for() in der while mit 35 durchgängen und dann einen System 
Reset? Watchdog will ich gerade noch nicht verwenden, da ich noch bei 
der Entwicklung des Codes bin.

Ich würde mich sehr über eure Lösungen freuen,
Danke

von Tilo (Gast)


Lesenswert?

Retry Zähler und so programmieren, dass man nicht in einer 
while-Schleife ohne Abbruchbedingung hängen bleibt?
Sich überlegen, wie man in Fehlersituationen weiter machen will?

von Theor (Gast)


Lesenswert?

Der wichtigste Punkt ist, das "Steckenbleiben" selbst zu verhindern.

Grenze die Frage bitte in Bezug auf Ursache und genaue Natur des 
"Steckenbleibens" ein.

Wenn das "Steckenbleiben" aus irgendeinem Grund nicht zu verhindern ist, 
sollte klar sein, warum das so ist.

---

Aber nehmen wir mal an, es gibt einen Grund für das Steckenbleiben, der 
sich nicht korrigieren oder umgehen lässt, aber eine Wiederholung des 
Versuchs wäre dennoch erfolgversprechend.

In dem Wort Wiederholung liegt genau die Lösung: Nimm eine 
while-Schleife oder irgendeine äquivalente Formulierung einer 
Wiederholung. Warum Reset des Controllers? Das wirft noch ganz andere 
und kompliziertere Problem auf.

Das praktische Problem bei der Fehlerbehandlung besteht meist darin, 
dass komplexe Bedingungen für die Wiederholung formuliert werden müssen. 
Auch darin, dass Teilbedingungen kausal erst unter anderen vorher 
ermittelten Bedingungen auftreten.

---

Für eine Beschreibung und Lösung der Fragestellung wäre ein konkretes 
Problem (ein Code, oder wenigstens ein Pseudocode) nützlich.

Aber wie zuerst gesagt: Oberstes Regel ist: Fehler beseitigen; nicht 
umgehen!

von (prx) A. K. (prx)


Lesenswert?

Neben direkt in diverse Warteschleifen eingebaute Zeitbedingungen gibt 
es auch die Möglichkeit, ereignisgesteuert zu arbeiten. Also die I2C 
Aktionen nicht in der Mainloop abzuwickeln, sondern in Interrupts. Dann 
klemmt die Mainloop nicht.

Eine Zeitbedingung benötigt man natürlich immer noch, aber dann reicht 
u.U. etwas wie eine "Sensorwert zu alt" Information in der Mainloop, 
verknüpft mit einem Statuswert, der anzeigt, an welcher Stelle im 
Zustandsdiagramm das I2C grad steckt - da reicht evtl ein I2C 
Statusregister.

Probleme kann man dann ggf durch Reset vom I2C wieder beheben, ohne den 
ganzen Controller resetten zu müssen.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Zum Debugging: Da Probleme auch in der Hardware liegen können, kann es 
hilfreich sein, ein DSO zu bemühen. Einerseits um die Signalqualität 
generell bewerten zu können. Andererseits kann man einen hinreichend 
schnell erkannten Timeout an einen Testausgang legen, der als DSO 
Trigger dient, um ein schlecht reproduzierbares Ereignis einzufangen.

von Martin S. (strubi)


Lesenswert?

Moin,

der alte i2c Klassiker taucht doch immer wieder auf..

Die mE sauberste Variante, die aber bisschen Rahmenwerk erfordert:
- Queue schreiben, in die aus der mainloop/user space die Transaktionen 
reintrudeln (A: Ich möchte was schreiben, B: was gelesen haben)
- In Interrupt-Handler oder ähnlich gearteter Co-Routine (richtiger 
Thread oder irgendwo in der mainloop-Statemachine) die Queue abarbeiten. 
Fürs Lesen brauchts natürlich eine zweite Queue.

So kriegt man die maximale Performance und Sicherheit (sofern man die 
Queue mit atomar zählenden Pointern designt). Von Watchdog-Gemurkse kann 
ich nur abraten. Das schreit nach einer Menge Probleme, manche 
"Spezialisten" spicken den Code an zig Stellen mit Wakeup-Anweisungen 
und brocken sich eine Menge schwer debugbarer 'race-conditions' ein.
Noch ein anderer Klassiker: Wenn es um Anwendungen geht, wo fremde User 
nicht drin rumzulesen haben oder das System nicht in einen non-armierten 
Zustand bringen sollen können, sind solche while(1) {} -> 
Watchdog-Auslöser das Standard-Einbruchstörchen. Also: verboten :-)

von uks2 (Gast)


Lesenswert?

Hallo,
Danke erstmal für die Zahlreichen Antworten.

Ich verwende ein STM32F429I-DISC1 Board.

Warum ich in den I2C Bedingungen hängen bleibe weiß ich nicht! Ich suche 
schon über ein halbes Jahr... Aus meiner Sicht wahrscheinlich, der 
I2C-Slave ist ein Prototyp und ich denke wenn die I2C Kommunikation 
abgebrochen wird ist der Sensor beleidigt (400kHz SCL Signal).
- Läuft der Code einmal, ist dieser stabil!
=> Wenn ich in so einer I2C-Blockade hänge sind beide Busleitungen (SDA 
und SCL) high.
- Meistens komme ich aus dieser I2C-Blockade raus wenn ich das STM Disco 
Board resete (schwarzer Taster). Manchmal aber auch nicht, dann hilft 
neu flashen/ sensor verbindung ganz lösen/ logik analyser anschließen 
(ja es scheint wenn ein logik analyser angeschlossen ist, ist die 
Kommunikation stabiler) oder andere hilflose versuche.

Mein Code bleibt meist hier hängen:
1
void I2C_address(unsigned char address)
2
{
3
  char addess_flag;
4
  I2C1->DR = address; 
5
  while (!(I2C1->SR1 & I2C_SR1_ADDR));
6
  address_flag = (I2C1->SR2); 
7
  address_flag = (I2C1->SR1);
8
}

@strubi: könntest du zu der  "sauberste Variante" ein Beispiel oder 
genauere Beschreibungen posten? Ich bin mir nicht sicher ob ich das 100% 
verstehe.../ würde das gerne umsetzen.

von Stefan F. (Gast)


Lesenswert?

Wenn du auf Kommunikationsstörungen sinnvoll reagieren kannst (z.B. eine 
Fehlermeldung anzeigen, den Bus zurücksetzen und wiederholen, auf 
Ersatz-System umschalten), dann mache das.

Der Watchdog ist nur die letzte Option, wenn einem nicht besseres 
einfällt. Aber dann bedenke die Folgen. Nicht dass durch den 
unerwarteten Reset jemand zu Schaden kommt.

von STK500-Besitzer (Gast)


Lesenswert?

uks2 schrieb:
1
 void I2C_address(unsigned char address)
2
 {
3
   char addess_flag;
4
   I2C1->DR = address;
5
   while (!(I2C1->SR1 & I2C_SR1_ADDR));
6
   address_flag = (I2C1->SR2);
7
   address_flag = (I2C1->SR1);
8
 }
Wenn du in der While-Schleife noch eine Variable herunterzählst, und 
diese in auch der Abfrage auf 0 testest, hast du ein Timeout.
Den nbachfolgenden Code brauchst du dann nur ausführen, wenn Timeout > 0 
ist.
1
 void I2C_address(unsigned char address)
2
 {
3
   char addess_flag;
4
   I2C1->DR = address;
5
   uint32_t TimeOut = 50;
6
   while(!(I2C1->SR1 & I2C_SR1_ADDR) && (Timeout > 0)){
7
    TimeOut--;
8
   }
9
   address_flag = (I2C1->SR2);
10
   address_flag = (I2C1->SR1);
11
 }

von Stefan F. (Gast)


Lesenswert?

> Mein Code bleibt meist hier hängen:
> while (!(I2C1->SR1 & I2C_SR1_ADDR));

Anstatt eine leere Schleife zu bauen die schlimmstenfalls endlos hängt 
könntest du die verstriche Zeit kontrollieren und nach z.B. einer 
Sekunde abbrechen.
1
unsigned long warteSeit=millis();
2
while (!(I2C1->SR1 & I2C_SR1_ADDR))
3
{
4
    if (millis()-warteSeit > 1000)
5
    {
6
       return -1; // Fehler melden
7
    }
8
}

Die wichtige Frage wäre dann: Was kann man nach dem Abbruch sinnvolles 
tun?

von W.S. (Gast)


Lesenswert?

uks2 schrieb:
> Mein Code bleibt meist hier hängen:...

Wenn du es nicht mit dem eingebauten I2C-Core hinbekommst, dann mach das 
Ganze doch schlichtweg in Software.
Also:
- Stardcondition
- Adresse senden
- wenn kein ACK --> Ende mit Fehlermeldung oder so
- Daten senden, wenn auch hier kein ACK, dann ebenfalls Ende mit 
Schrecken
- oder Daten empfangen, selber entscheiden, ab wann man NAK gibt
- Stopconditon
fertig. Ohne Warten auf irgendwas, einfach nur geradeaus. Und wenn da 
ein Slave nicht mitspielt, weiß man aus der Fehlermeldung auch, wer und 
wann.

W.S.

von Brummbär (Gast)


Lesenswert?

uks2 schrieb:
> Manchmal aber auch nicht, dann hilft
> neu flashen/

Das deutet aber nicht auf eine Störung der Verbindung hin. Diese wird 
sich durch Reset oder zumindest spannungsfrei Schalten beheben lassen.

von Martin S. (strubi)


Lesenswert?

>
> @strubi: könntest du zu der  "sauberste Variante" ein Beispiel oder
> genauere Beschreibungen posten? Ich bin mir nicht sicher ob ich das 100%
> verstehe.../ würde das gerne umsetzen.

Ich versuch's mal :-)
Die Queue ist a priori ein Software-FIFO, wo du eine Transaktion (z.B. 
WRITE <addr> <sequence>) reinsteckst, und ein anderer Prozess (oder 
später aufgerufene Routine oder IRQ-Handler) holt es dort wieder raus 
und handelt mit der Hardware die Zustände aus ohne auf irgend etwas zu 
warten. Kann man jetzt schreien: Viel zu kompliziert.
Aber angenommen, du darfst nirgendwo hängen bleiben, oder zu lange 
warten, gerade wenn z.B. ein Watchdog auslösen könnte. Oder die 
Anwendung erfordert, dass du in der Zwischenzeit die CPU für etwas 
anderes als nur Warten benutzt. Dann musst du garantieren, dass die 
Hauptschleife in sinnvoller Zeit in allen möglichen Zuständen 
abgearbeitet ist. Das mit gut dimensionierten Timeouts hinzukriegen ist 
so ein kniffliges Ding, insbesondere den 'worst case' im Rahmen zu 
halten.

Da ich jetzt mal annehme, dass du "bare metal" (ohne Thread-fähiges 
Multitasking-Framework) arbeitest, ist deine Hauptschleife dein 
"Scheduler", der die Arbeiten an die Einzelroutinen verteilen muss.
Das läuft bei der Anforderung auf asynchrone Transaktionen raus, also: 
du schickst eine Anfrage raus, und fragst die Rückantwort im nächsten 
Durchlauf ab. Typischerweise setzt du da in der Hauptschleife eine 
Zustandsmaschine ein, die nicht hängenbleiben kann. Das ganze ist 
einfach insofern unschön, dass du nicht unmittelbar nach dem Aufruf 
eines Kommandos gleich die Antwort (oder Fehlercode) bekommst. Im 
Ergebnis hast du aber damit sehr robusten Code, der (abgesehen von 
IRQ-Ereignissen) vorhersehbar abläuft und recht gut verifizierbar wird.

Der andere Ansatz ist ein multitaskingfähiges Kernel, also Fähigkeit zu 
Tasks, die je nach Ereignis per IRQ zeitnah angesprungen werden. Da 
musst du aber allenfalls in die Tiefe gehen (Assembler, 
CPU-Architektur), falls du das selbermachst.

Dann gibt's noch die "fancy" protothread-Ansätze, die aber nur 
Zustandsmaschine in 'schön geschrieben' abstrahieren und unter Umständen 
den konkreten Ablauf verundeutlichen, ergo sich da auch immer wieder ins 
Knie geschossen wird, weil man da eben nicht darf, was man bei echten 
Thread-Kernels darf.

Ich würde mal mit der Implementierung einer Queue anfangen und die 
Zustandsmaschine implementieren. Dann kannst du die Performance/Timing 
später noch mit IRQ-Unterstützung optimieren, der DMA-Teil des STM32 
kann da ja einiges.

: Bearbeitet durch User
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.