Forum: Mikrocontroller und Digitale Elektronik Problem mit UART auf STM32G0. UART liest nur 0 ab zweiter Iteration


von Mike (Gast)


Lesenswert?

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:
1
  while (1) {
2
    while(!SIM7080G_CSQGood()){
3
      HAL_Delay(50);
4
    }
5
    SIM7080G_HTTPClose();
6
    HAL_Delay(10000);
7
    /* USER CODE END WHILE */
8
    /* USER CODE BEGIN 3 */
9
  }

Funktionen:
1
void SIM7080G_HTTPClose(void) {
2
  HAL_UART_Transmit(&huart1, "AT+SHDISC\r", 10, 50);
3
  HAL_Delay(100);
4
  HAL_UART_Transmit(&huart1, "AT+CNACT=0,0\r", 13, 50);
5
  HAL_Delay(100);
6
}
7
8
uint16_t SIM7080G_CSQGood(void) {
9
  SIM7080G_clearRXBuffer();
10
  HAL_UART_Transmit(&huart1, "AT+CSQ\r", 7, 50);
11
  HAL_UART_Receive(&huart1, rxBuffer, RXBufferLength, 50);
12
13
  //Search for " " in String...
14
  int index = 0;
15
  int CSQ = 0;
16
  char RSSI[2] = { 0, 0 };
17
18
  for (int i = 0; i < RXBufferLength; i++) {
19
    if (rxBuffer[i] == ' ') {
20
      index = i;
21
      break;
22
    }
23
  }
24
25
  RSSI[0] = rxBuffer[index + 1];
26
  RSSI[1] = rxBuffer[index + 2];
27
  CSQ = atoi(RSSI);
28
  if ((CSQ > 1) && (CSQ < 98)) {
29
    return 1;
30
  }
31
  return 0;
32
}


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!

von Stefan F. (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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!

von Stefan F. (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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!

von Stefan F. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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

von N. M. (mani)


Lesenswert?

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.

von N. M. (mani)


Lesenswert?

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.

von Markus M. (adrock)


Lesenswert?

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.

von N. M. (mani)


Lesenswert?

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.

: Bearbeitet durch User
von Markus M. (adrock)


Lesenswert?

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

von Harry L. (mysth)


Lesenswert?

Das
1
HAL_UART_Receive(&huart1, rxBuffer, RXBufferLength, 50);
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.

von Stefan F. (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Harry L. (mysth)


Angehängte Dateien:

Lesenswert?

Ich hab mal einen funktionierenden Code für die Serielle angehängt.

von Mike (Gast)


Lesenswert?

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!

von Peter D. (peda)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Mike schrieb:
> Könntest du das teilen? Würde mir auf jeden Fall sehr weiterhelfen!

Ich bezweifle, dass du mit Java Quelltext viel anfangen kannst,

von Peter D. (peda)


Lesenswert?

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

: Bearbeitet durch User
von Bauform B. (bauformb)


Lesenswert?

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.

von testcode (Gast)


Lesenswert?

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.

von Harry L. (mysth)


Lesenswert?

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)

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.