Hallo zusammen!
Ich habe ein Problem mit dem einlesen von UART Charactern auf einem
STM32G031.
Ab der zweiten Iteration der Main-Schleife liest der UART nur noch 0
(keine Ascii 0, sondern '0') ein, obwohl die Daten auf der UART Line
laut Logic Analyzer vorhanden sind. Die Daten sind AT Commands von einem
Simcom LTE Modul.
Genaue Fehlerbeschreibung:
Mein Programm fragt solange den Empfang des LTE Moduls ab, bis der
Empfang ausreichend gut ist. Dann öffnet es eine HTTP Verbindung,
schreibt Daten und schließt sie. Um die Fehlersuche zu vereinfachen habe
ich die Funktionen für das Öffnen und schreiben der HTTP Verbindung
entfernt. Das Modul kann dann zwar keine HTTP Verbindung schließen,
antwortet aber trotzdem auf alle Befehle korrekt, das sehe ich am Logic
Analyzer.
Ein Elektrisches Problem würde ich auch ausschließen, da es ja im ersten
Durchlauf geht.
Folgende Setups habe ich probiert:
Geht nicht: Eigene Platine mit STM32G031k8u6 mit integriertem SIMCOM
Modul
Geht nicht: STM32G031k8t6 Nucleo Eval Board mit Simcom Eval Board
Geht: STM32F407 Discovery mit Simcom Eval Board
Alle mit dem gleichen Code, nur die Konfiguration in CubeMX in der
CubeIDE hat sich natürlich geändert.
Code:
main:
Vergesse ich irgendwelche Sonderfälle in der UART Benutzung zwischen G0
und F4? Welche Teile des Codes braucht ihr, (wenn überhaupt) um einen
Fehler finden zu können?
Danke euch und viele Grüße!
Woher weißt du, wie viele Zeichen HAL_UART_Receive() tasächlich
empfangen hat und warum wertest du in der for-Schleife immer den ganzen
Puffer aus, obwohl wahrscheinlich weniger Zeichen empfangen wurden?
Stelle dir vor, du empfängst beim ersten mal
> +CSQ: 15
und danach
> ERROR
dann steht im Puffer
> ERROR 15
Du würdest beide male das Leerzeichen an Position 6 finden. Beide male
würde deine Funktion dann die Zahl 15 finden, obwohl das Modem bei
zweiten Durchlauf gar keine 15 zurück geliefert hat.
Stefan ⛄ F. schrieb:> Woher weißt du, wie viele Zeichen HAL_UART_Receive() tasächlich> empfangen hat und warum wertest du in der for-Schleife immer den ganzen> Puffer aus, obwohl wahrscheinlich weniger Zeichen empfangen wurden?
Ist egal, oder? Ich mache vorher einmal den Buffer leer, siehe
clearRXBuffer().
Und der Buffer wird so lange durchsucht, bis das erste Leerzeichen
empfangen wurde.
Geht sicherlich eleganter, aber sollte für den Anfang ja passen.
Und das Problem ist ja, dass der Buffer nach der HAL_UART_Receive
einfach LEER ist.
Hast du noch eine Idee? Vor allem, warum das mit dem F4 geht, mir dem G0
aber nicht?
Mike
Mike schrieb:> Ich mache vorher einmal den Buffer leer, siehe clearRXBuffer().
Ah OK, das habe ich übersehen. Na hoffentlich tut die Funktion auch das
gedachte. Nicht dass sie einfach nur das erste Zeichen auf 0 setzt, wie
es bei Strings üblich wäre.
> Und das Problem ist ja, dass der Buffer nach der> HAL_UART_Receive einfach LEER ist.
Ich hoffe das hast du wirklich kontrolliert, nicht dass es nur eine
Schlussfolgerung aufgrund anderer Symptome ist. Werte mal Fehlerstatus
der Schnittstelle aus (nach dem Senden und nach dem Empfangen).
Wurde das Kommando überhaupt erfolgreich gesendet? Und hat das Modem
darauf reagiert? Hast du einen Logic Analyzer? Oder einen USB-UART
Adapter den du zum Mitschnüffeln von Rx oder Tx missbrauchen kannst?
Eventuell verwendest du den USB-UART Adapter des ST-Link von Nucleo
Board, falls nicht schon anderweitig belegt.
Manche Modems ignorieren Kommandos, wenn man sie zu früh nach dem
vorherigen sendet.
Stefan ⛄ F. schrieb:> Wurde das Kommando überhaupt erfolgreich gesendet? Und hat das Modem> darauf reagiert? Hast du einen Logic Analyzer? Oder einen USB-UART> Adapter den du zum Mitschnüffeln von Rx oder Tx missbrauchen kannst?
Ja, wie oben geschrieben sieht auf der Seriellen Schnittstelle alles gut
aus, genau wie bei der ersten Iteration.
Gecheckt mit Saleae Logic (sogar original...) und Oszi.
Stefan ⛄ F. schrieb:> Ich hoffe das hast du wirklich kontrolliert, nicht dass es nur eine> Schlussfolgerung aufgrund anderer Symptome ist.
Jap, laut Segger J-Link ist der Buffer wirklich leer. Bei der ersten
Iteration stehen die korrekten Daten drin, sodass ich davon ausgehe,
dass mein Setup funktioniert.
Stefan ⛄ F. schrieb:> Werte mal Fehlerstatus> der Schnittstelle aus (nach dem Senden und nach dem Empfangen).
Spannend!
TX Statuscode ist immer 0.
RX Statuscode ist nach der zweiten Iteration 3. Ich finde aber leider
keine Erklärung in der HAL Doku? Hast du da weitere Informationen?
Viele Grüße!
Mike schrieb:> Hast du da weitere Informationen?
Schau mal in den Quelltext von HAL_UART_Receive(). So habe ich damals
für USB die Auflistung der möglichen Fehlercodes samt Doku (im
Quelltext) gefunden.
Stefan ⛄ F. schrieb:> Schau mal in den Quelltext von HAL_UART_Receive().
Alles klar, danke.
3 heißt Timeout. Wobei ja das ungefähr das erwartete Verhalten ist.
Wie du oben geschrieben hast, weiss ich nicht, wie viele Character ich
erwarte. Der Buffer ist einfach groß genug für alles was wohl so kommt
und wenn keine Character mehr kommen geht er halt irgendwann in den
Timeout.
Oder darf man das so nicht machen?
Und warum geht er beim ersten Durchlauf nicht in den Timeout? Auch im
ersten Durchlauf sollte der UART ja in den Timeout laufen.
Und warum ist das beim F4 kein Problem? Das ist für mich ehrlich gesagt
das verwirrendste, dass es mit dem F4 problemlos geht...
Grüße!
Mike schrieb:> Und warum geht er beim ersten Durchlauf nicht in den Timeout?
Ich denke dass ist die entscheidende Frage deren Antwort doch weiter
bringen wird. Forsche da mal zuerst weiter nach.
Mike schrieb:> Und warum ist das beim F4 kein Problem? Das ist für mich ehrlich gesagt> das verwirrendste, dass es mit dem F4 problemlos geht...
Weil der G0 vermutlich etwas anders funktioniert und genau das entweder
der Knackpunkt ist oder dessen HAL (mal wieder) einen Bug enthält.
Stefan ⛄ F. schrieb:> Ich denke dass ist die entscheidende Frage deren Antwort doch weiter> bringen wird. Forsche da mal zuerst weiter nach.
Das kriege ich jetzt irgendwie nicht mehr reproduziert. Einziger Weg,
dass er nicht in den Timeout geht ist, wenn die Buffergröße genau passt.
Aber selbst dann geht es im zweiten Durchlauf nicht mehr, er empfängt
angeblich nichts und der ErrorCode ist 3.
Interessant ist:
Im zweiten Durchlauf ist der erste character der empfangen wird oft noch
'\n' oder 'O' - beides Character, die immer wieder in den Antworten des
Modems enthalten sind.
Interessant zwei:
Wenn ich bei jedem Schleifendurchlauf den Uart deinitialisiere und dann
wieder initialisiere, scheint es erstmal zu gehen. Muss ich nochmal
bisschen testen.
Aber ist das euer Ernst, ST???
Mike schrieb:> Wie du oben geschrieben hast, weiss ich nicht, wie viele Character ich> erwarte. Der Buffer ist einfach groß genug für alles was wohl so kommt> und wenn keine Character mehr kommen geht er halt irgendwann in den> Timeout.
Würde ich so nicht machen. Wieso nutzt du nicht den DMA und den IdleLine
ISR? Hat der Controller das nicht? Dann kopiert dir der DMA die Daten in
deinem Buffer und du nimmst beim IdleLine immer alles was drin steht. Am
Besten mit einem PingPong Buffer, also einfach umklemmen des Buffers im
ISR damit er für das nächste Mal bereit ist.
Mike schrieb:> Und warum geht er beim ersten Durchlauf nicht in den Timeout?
Ich überlege mir gerade folgendes Szenario:
Es wird was Empfangen, der Slave hört aber mittendrin Mal auf zu senden
oder der Timeout ist zu kurz gewählt.
Dann wertest du das zu kurz empfangene aus und wartest eine ziemlich
lange Zeit bevor du nochmal nachschaust.
Was passiert denn wenn in dieser Zeit der Slave nochmal Daten senden
würde? Oder etwas anderes an der RX Leitung zuppelt.
Dann läuft der HW Buffer voll (wenn vorhanden/benutzt) und es kommt zu
einem Overflow. Jetzt ist die Frage wird das in der HAL sauber gehandelt
und die nötigen Flags quittiert damit wieder sauber empfangen wird?
Irgendwie musst du immer sicherstellen dass du auf den Anfang des Frames
(oder das Ende des letzten Frames) sauber aufsychronisierst. Das kannst
du in meinen Augen so nur sehr schlecht.
Mike schrieb:> Interessant zwei:> Wenn ich bei jedem Schleifendurchlauf den Uart deinitialisiere und dann> wieder initialisiere, scheint es erstmal zu gehen. Muss ich nochmal> bisschen testen.
Spricht dafür dass irgendwas stoppt. Ich finde meine Theorie garnicht
schlecht.
Du hast das Timeout auf 50ms gesetzt. Findest Du das nicht etwas kurz?
Wenn das Modem mal etwas länger zum Antworten braucht, läuft Deine
Empfangsroutine sofort in den leeren Buffer.
Tatsächlich würde man zumindest den Emfpang der Daten besser asynchron
programmieren (per ISR), wie schon oben erwähnt. Andernfalls verlierst
Du Bytes sobald Du die Daten nicht rechtzeitig abholst.
Markus M. schrieb:> Du hast das Timeout auf 50ms gesetzt.
Sind das ms oder nur Ticks?
Gerade Mal eine Version der HAL im Internet angesehen.
Da sind es definitiv Ticks.
N. M. schrieb:> Markus M. schrieb:>> Du hast das Timeout auf 50ms gesetzt.>> Sind das ms oder nur Ticks?> Gerade Mal eine Version der HAL im Internet angesehen.> Da sind es definitiv Ticks.
Ja, und die Tick_Frequenz ist 1 KHz per default, also 1 tick = 1ms:
#define HAL_TICK_FREQ_DEFAULT HAL_TICK_FREQ_1KHZ
liefert erst ein HAL_OK, wenn auch tatsächlich RxBufferLength Zeichen
empfangen wurden.
Das ist aber bei der Kommunikation mit AT-Kommandos nicht das, was man
will, da man die Länge der Antwort im Vorfeld nicht kennt.
Es führt kein Weg daran vorbei, die Zeichen auch Zeichen für Zeichen
abzuholen.
Auf PC und anderen Mikrocontrollern schreibe ich mir gerne eine
Prozedur, die folgendes macht:
1) Empfangspuffer leeren
2) AT-Kommando senden
3) Auf einen bestimmten Teilstring warten (meist OK), aber maximal n
Millisekunden (n wird je nach Kommando festgelegt)
4) Alle weiteren Zeichen abholen, bis 100ms lang nichts mehr kommt
5) Den ganzen empfangenen String zurück liefern
Damit bekomme ich auf allen Modems und ähnlichen Geräten eine recht
stabile Kommunikation hin.
Anscheinend eignet sich der DMA basierte Transfer von STM32 sehr gut
dazu. Man kann für jedes Kommando einen frischen Puffer erstellen und er
kann auch die Bedingung "es kommt nichts mehr" automatisch erkennen.
Ich glaub ja nicht, daß Dein HAL_UART_Receive hellsehen kann.
Du mußt also zuerst mal ein Protokoll festlegen. Z.B. daß \r das Ende
eines Kommandos kennzeichnet.
D.h. Du sammelst alle empfangenen Bytes in einen Puffer und prüfst, ob
sie \r lauten. Findest Du \r, dann übergibst Du den Puffer mit den bis
dahin empfangenen Bytes dem Parser. Der Parser analysiert dann den
String, ob er einem bekannten Befehl entspricht und führt ihn aus.
Hallo zusammen!
Klar, das klingt alles gut, was ihr sagt. DMA macht sicherlich Sinn und
auch eine Unterteilung in mehrere Segmente, mit Parser, etc. macht voll
Sinn. Gerade gibt es aber viel zu tun, deswegen bin ich auf der Suche
nach einer schnellen Lösung.
Und ist ja nicht so, dass die Lösung nicht funktionieren sollte oder so.
Auf dem F4 tut sie das ja problemlos!
Markus M. schrieb:> Du hast das Timeout auf 50ms gesetzt. Findest Du das nicht etwas kurz?
Nein, die Antworten vom Modem sind nach wenigen ms komplett da.
Stefan ⛄ F. schrieb:> Auf PC und anderen Mikrocontrollern schreibe ich mir gerne eine> Prozedur, die folgendes macht:>> 1) Empfangspuffer leeren> 2) AT-Kommando senden> 3) Auf einen bestimmten Teilstring warten (meist OK), aber maximal n> Millisekunden (n wird je nach Kommando festgelegt)> 4) Alle weiteren Zeichen abholen, bis 100ms lang nichts mehr kommt> 5) Den ganzen empfangenen String zurück liefern
Könntest du das teilen? Würde mir auf jeden Fall sehr weiterhelfen!
Harry L. schrieb:> Ich hab mal einen funktionierenden Code für die Serielle angehängt.
Top, schaue ich mir an!
Spannend ist:
Auch die Version, bei der ich den UART bei jeder Iteration resette und
neu initialisiere stürzt irgendwann ab, ne Stunde oder so läufts aber.
Bin für jeden Tipp dankbar :-)
VIele Grüße!
Mike schrieb:> Und ist ja nicht so, dass die Lösung nicht funktionieren sollte oder so.> Auf dem F4 tut sie das ja problemlos!
Das möchte ich mal stark anzweifeln. Die Fehler wurden bisher noch nicht
sichtbar.
Z.B. sollte das Array für atoi() wenigstens 3 Byte groß sein für 2
Ziffern.
Ein Parser meint auch nur, daß man die Aufgaben schön voneinander
kapselt, d.h. in einzelne Funktionen zerlegt und nicht wild alles auf
einen Haufen schmeißt.
Ein Parser kann z.B. ein sscanf() sein. Das sscanf() vergleicht den
Befehl im Formatstring und liest optional das Argument in eine Variable
ein. Dann muß man nicht mehr händisch und fehlerhaft mit einzelnen Bytes
rumjonglieren.
Das atoi() hat den großen Nachteil, daß man einen Fehler nicht vom Wert
0 unterscheiden kann.
Protokolle mit Timeout sind so ziemlich das gräßlichste, was es gibt.
Sie zeugen davon, daß der Entwickler zu faul war, etwas vernüftiges zu
implementieren.
Ihr Problem ist, daß sie Fehler verstecken. Man merkt erst in einer sehr
späten Phase, daß Rechenzeit vergeudet wird und alles viel zu langsam
ist. Oft erst, wenn das Produkt beim Kunden Ärger macht und das kann
richtig teuer werden.
Wer das HAL_UART_Receive() verbrochen hat, dem gehört ganz gehörig eins
auf die Finger geschlagen.
Richtige Protokolle benutzen eine Statemaschine, d.h. die CPU weiß immer
ganz genau, in welchem Punkt des Datenstromes sie ist. Damit sind auch
keine Timeouts zur Steuerung nötig, sondern nur zum Fehlerabbruch
(Verbindung gestört).
Peter D. schrieb:> Das atoi() hat den großen Nachteil, daß man einen Fehler nicht vom Wert> 0 unterscheiden kann.
Ein Mittelweg zwischen atoi() und sscanf() ist strtol(). Diese Funktion
liefert einen Pointer auf das Zeichen, das nicht mehr zur Zahl gehört.
Damit kann man die meisten Fehler ganz gut erkennen.
Ich hoffe es handelt sich nur um Testcode, ansonsten hast du ganz andere
Probleme:
1. hardgecodete Stringlänge statt strlen()
2. UART Empfang sollte interrupt basiert sein. Bevor dein
HAL_UART_Receive() Daten empfängt können bereits Zeichen gesendet worden
sein. Oben wurden auch schon andere Grnde dafür genannt.
DMA Empfang ist ok wenn der Gegenpart nur auf Befehle antwortet. Wenn
der Gegenpart aber auch wahllos Text schicken darf ist interrupt basiert
die beste Lösung.
Peter D. schrieb:> Wer das HAL_UART_Receive() verbrochen hat, dem gehört ganz gehörig eins> auf die Finger geschlagen.
Das ist eigentlich nur der Vollständigkeit halber dabei.
I.d.R. nutzt man HAL_UART_Receive_IT() (Interrupt-Version)