Forum: Mikrocontroller und Digitale Elektronik UART receive complete timeout


von Reiner W. (reiner_w)


Lesenswert?

Meine Problematik:
Ich habe einen Satz UART Routinen (ARM M0) implementiert, die an sich 
schon mal bestens funktionieren. Nur für einen Fall fällt mir kein 
sauberer Lösungsansatz ein.
Mein uC erwartet Strings, die ein definiertes String-Ende Kennzeichen 
haben (In meinem Fall FF FF FF). Prinzipiell gehe ich so vor:

Pseudocode

ISR_RX:
RX-Byte -> FIFO
Setzte RX-Flag

In Main()
if (RX-Flag)
   getStr()

getStr():
Lese FIFO in ReceivedString[] bis FIFO leer oder String-Ende
if (String-Ende)
   Setzt getString-Flag

Das geht alles super. Nun will ich einen Timeout für den kompletten 
String implementieren.
Es könnte ja sein, dass die Verbindung abbricht, bevor das String-Ende 
Kennzeichen empfangen wurde, der String also nicht komplett ist.
Der Teilstring steht nun in ReceivedString[] und falls irgendwann ein 
neuer String empfangen wird, steht der davor.
Ich will nun eine Möglichkeit haben festzustellen, dass zwischen 2 
empfangenen Zeichen eine Zeit > timeout aufgetreten ist, und die älteren 
für ungültig erklärt werden.
Bisher ist mir nur eingefallen, einen Timestamp beim Empfang zu 
generieren und mitzuführen. Allerdings müßte der ja dann auch im FIFO 
mitgeführt werden?
Gibt es da bessere Ansätze?

Reiner

von Georg (Gast)


Lesenswert?

Reiner W. schrieb:
> Gibt es da bessere Ansätze?

Einen Timer starten. Der wird mit dem Empfang des Stringendes gestoppt, 
sonst läuft er weiter und bei einem gewünschten Wert ruft er eine 
Routine auf, die den Timeout meldet, wohin auch immer, oder es wird ein 
Flag gesetzt.

Man kann den Timer bei jedem empfangenen Zeichen auf Null setzen, dann 
wird der Timeout nur durch eine lange Sendepause ausgelöst. Oder man 
startet den Timer mit dem ersten empfangenen Zeichen, dann gibt es einen 
Timeout wenn die maximale Zeit für einen Record verstrichen ist. Oder 
man startet mit dem Start der Empfangsroutine, dann gibt es einen 
Timeout nach der maximalen Antwortszeit (das ist nicht das gleiche). Es 
gibt also viele Möglichkeiten.

Georg

von Sascha W. (sascha-w)


Lesenswert?

Und ganz wichtig, wenn der Timeout aufgetreten ist löscht man den FIFO. 
Ich gehe in dem Fall einfach mal davon aus das der vorhergehende String 
verarbeitet wurde bis der nächste kommt. Wenn nicht dann bleibt nur den 
FIFO von hinten zu löschen bis zur Stringendemarkierung des vorherigen.

Sascha

von Reiner W. (reiner_w)


Lesenswert?

Georg schrieb:
>
> Einen Timer starten. Der wird mit dem Empfang des Stringendes gestoppt,
>
Ja, das hab ich auch ünerlegt. Der Timer wird in der ISR gestartet. Die 
String-Ende Erkennung mach ich allerdings nicht in der ISR. Die Zeit 
zwischen tatsächlichem String-Ende und dessen Erkennung ist also davon 
abhängig, was der uC sonst noch so zu tun hat.
Wenn timeout aber entsprechend dimensioniert ist, sollte man evtl. mit 
diesem Fehler leben können.

Reiner

von Joggel (Gast)


Lesenswert?

Das Ende eines Strings per timeout ? Sowas Bescheuertes. Eine Meldung 
muss natuerlich immer die eigene Laenge enthalten, dann kann man 
zaehlen.

Das Problem des Timeouts ist die nicht-Vorhersagbarkeit des PC UARTS. 
Der PC kann irgend eine Blockgroesse aus Mal Senden, sich dann 
irgendwelche Zeit lassen, und dann den Rest nachschieben. Womit 
irgendwelche Zeit in die mehrere 100ms gehen kann. Immer die maximale 
Zeit als Timeout vorzugeben ist extrem ineffizient.
Aendere das Protokoll sodass du zehlen kannst.

von Reiner W. (reiner_w)


Lesenswert?

Sascha W. schrieb:
> Und ganz wichtig, wenn der Timeout aufgetreten ist löscht man den
> FIFO.

Ja, der ist doch dann schon leer? Ich überführe ja den Fifo Inhalt mit 
getStr() in ReceivedString[], auch wenn der nicht komplett ist. Ich 
müsste also den dann löschen.

Ich dachte jetzt an sowas:
UART-RX -> Start Timeout-Timer (setze Timer auf 0)

Timer-Ablauf -> lösche ReceivedString[] (Fifo sollte eigentlich leer 
sein)

Führt aber dazu, dass der Timer generell in Sendepausen ausgelöst wird 
(nicht nur im Fehlerfall).

von Reiner W. (reiner_w)


Lesenswert?

Joggel schrieb:
> Das Ende eines Strings per timeout ? Sowas Bescheuertes. Eine
> Meldung
> muss natuerlich immer die eigene Laenge enthalten, dann kann man
> zaehlen.
>
ja, ich geb das mal an die NEXTION Leute weiter, dass die ihr Protokoll 
bitte ändern sollen,-)

von Reiner W. (reiner_w)


Lesenswert?

Joggel schrieb:
>
> Das Problem des Timeouts ist die nicht-Vorhersagbarkeit des PC UARTS.
> Der PC kann irgend eine Blockgroesse aus Mal Senden, sich dann
> irgendwelche Zeit lassen, und dann den Rest nachschieben. Womit
> irgendwelche Zeit in die mehrere 100ms gehen kann.

Das ist genau der Grund, warum ich mich damit schwer tue.
Aber was solls.
Die eigene Länge zählen, oder was auch immer, löst ja nicht das Problem, 
dass der uC irgendwann entscheiden muss, ob er noch länger auf 
ausstehende Daten warten soll.
Und im Übrigen ging es nicht darum, dem Timeout als Stringende zu 
verwenden.

Reiner

von Nosnibor (Gast)


Lesenswert?

Da das Hauptprogramm den Stringanfang womöglich schon aus der FIFO 
geholt hat, muß im Fehlerfall auch das Hauptprogramm den String ungültig 
machen, denn der Interrupt kommt ja nicht mehr an die Daten. Das 
einfachste wäre es wohl, bei Timerablauf eine spezielle Kennung in die 
FIFO zu schreiben, damit das Hauptprogramm weiß, daß der String 
unvollständig ist.

Alternativ könnte man auch die Zeitüberwachung dem Hauptprogramm 
überlassen. Zeitstempel merken, wenn etwas empfangen wurde, und 
gelegentlich nachsehen, ob der Zeitstempel schon zu alt und damit der 
bisher empfangene String ungültig ist. Man braucht hier ja keinen 
Präzisionstimer.
Je nach Anwendung kann man den Timeout ja auch recht großzügig 
definieren, so daß der PC damit kein Problem bekommt, sofern er nicht 
sowieso überlastet ist.

von Reiner W. (reiner_w)


Lesenswert?

Nosnibor schrieb:
> Da das Hauptprogramm den Stringanfang womöglich schon aus der FIFO
> geholt hat, muß im Fehlerfall auch das Hauptprogramm den String ungültig
> machen, denn der Interrupt kommt ja nicht mehr an die Daten.

In der Tat.
Habts jetzt so gelöst:

Pseudocode

ISR_RX:
RX-Byte -> FIFO
Setzte RX-Flag
Start/Reset uartCmdCompleteTimer //der TimeOut Timer

In Main()
if (RX-Flag)
   getStr()

if (uartCmdCompleteTimer vorhanden und abgelaufen)
   clearStr()


getStr():
Lese FIFO in ReceivedString[] bis FIFO leer oder String-Ende
if (String-Ende)
   Setzt getString-Flag
   release uartCmdCompleteTimer  //<- das sollte ich evtl. atomic machen

clearStr():
setzte Pointer des letzten gelesenen Zeichen in ReceivedString[] auf 0


Das läuft soweit gut. Zumindest hab ich mal die Verbindung abgebrochen. 
Nicht komplett empfangene Strings werden sicher gelöscht.
Den Fifo muss ich nicht löschen, da da getStr() ja den fifo immer 
komplett oder bis zum Endkennzeichen löscht.
Muss noch mal auf evtl. Seiteneffekte testen, dann sollte das so gehen.
Den Release des Timers in getStr() hab ich drin, damit in main() das 
clearStr() nur aufgerufen wird, falls überhaupt was empfangen wurde. Ist 
zwar nicht unbedingt nötig, muss ja aber nicht sein.
Muss mal sehen, ob ich den release evtl. atomic machen muss, da ja just 
in dem Moment wieder ein RX-IR kommen könnte.

Ich danke Euch für die hilfreichen Tipps!

Reiner

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.