Forum: Mikrocontroller und Digitale Elektronik Analyse NMEA Datensatz klappt nicht so richtig - Rechnenleistung schuld


von Torsten B. (torty)


Angehängte Dateien:

Lesenswert?

Guten Morgen

Ich habe das angehängte Programm geschrieben, um einen NMEA Datensatz 
eines GPS Gerätes (FLARM) auszuwerten.
Eigentlich dachte ich, dass ich es recht strukturiert gelöst habe, als 
es mit dem ersten Datensatz $PFLAU recht gut funktioniert hatte.
Danach habe ich die Analyse Routine in "Auswerten" kopiert und PFLAU 
durch einen zweiten Satz-Namen "GPRMC" ersetzt.
Leider erkennt die Software den zweiten Satz nicht, obwohl er 
einwandfrei im Datenstrom vorkommt (Kontrolle durch Hyperterminal).

Könnte es sein, dass die Rechenleistung aufgrund des 3,68MHz Quarzes zu 
eingeschränkt ist und die 1 Sekunde pause zwischen den Datensätzen nicht 
zur kompleten Analyse ausreicht ?

Hier noch mal kurz die Eckdaten:

ISR(USART_RXC_vect) speichert mit 19,2kBaud einkommende Daten in 
uart_string[MaxSatz][UART_MAXSTRLEN + 1]

ISR (TIMER1_COMPA_vect) erkennt die Pause (ca 1Sekunde) zwischen 
Datensätzen

Auswerten Analysiert die Strings und dröselt sie in die Structs auf.

von spess53 (Gast)


Lesenswert?

Hi

>Könnte es sein, dass die Rechenleistung aufgrund des 3,68MHz Quarzes zu
>eingeschränkt ist und die 1 Sekunde pause zwischen den Datensätzen nicht
>zur kompleten Analyse ausreicht ?

Unwahrscheinlich. Da kann der Controller noch nebenbei Kuchen backen.

Wenn mich meine rudimentären C-Kenntnisse nicht trügen, gehst du davon 
aus, das alle Werte in einem NMEA-String auch gesetzt sind. Hier mal 2 
reale Strings:

$GPRMC,125124.993,V,5023.2549,N,01107.3971,E,,,180505,,*1A
$GPRMC,124516.031,A,5023.2646,N,01107.3834,E,0.31,172.45,180505,,*03

Fällt dir etwas auf?

MfG Spess

von Torsten B. (torty)


Lesenswert?

Hallo
Danke für Deine Antwort.

Ich weiß, dass einige Strings leer sein können.
Aus diesem Grund haben ich auch die strsep Funktion genommen und nicht 
die strtok Funktion, die nämlich mit leeren Token nichts anfangen kann.

Reale Strings aus meinem Gerät sind uim Quelltext ganz oben.

Hast Du noch eine andere Idee ?

Gibt es Links zu Seiten, wo andere das Problem mit diesen Strings schon 
gelöst haben ?

danke und Gruß
Torsten

von Konrad S. (maybee)


Lesenswert?

Du reservierst Speicher mit
  running = strdup(uart_string[j]);
und gibst ihn mit
  free(running);
frei. Das ist im Prinzip ok, aber dazwischen wendest du mit
  ptr = strsep(&running,delimiter);
eine Funktion an, die running modifiziert. Das ist nicht ok, weil dann 
beim free() eine Adresse übergeben wird, die nichts mehr mit dem zu tun 
hat, was du von strdup() erhalten hast. Das bringt die 
Speicherverwaltung durcheinander, die Folgen sind nicht absehbar.

von Torsten B. (torty)


Lesenswert?

@Konrad:
Bin auch noch neu auf diesem Gebiet, aber diese vorgehensweise ist in 
vielen Beispielen zu der Funktion strsep so zu sehen.
Wie würdest Du es machen ?
Ich habe schon versucht ohne strdup auszukommen, aber dann geht gar 
nichts.

grüße
Torsten

von Konrad S. (maybee)


Lesenswert?

Eine Möglichkeit:
  char *merken;
  merken = running = strdup(uart_string[j]);
  ... //unverändert
  free(merken); //statt free(running);
Hier wird die richtige Adresse an free() übergeben.

Andere Möglichkeit:
  running = uart_string[j]; //kein strdup(), kein free()
Hier wird zwar das String-Array manipuliert (',' mit '\0' überschrieben, 
aber wenn du damit leben kannst, sparst du dir die gesamte dynamische 
Speicherverwaltung und damit auch Speicher.
"Spare in der Zeit, dann hast du in der Not", haben die Alten uns immer 
gesagt. Passt hier ganz gut, finde ich. ;-)

von Simon K. (simon) Benutzerseite


Lesenswert?

Mal davon abgesehen ist der NMEA Parser Schrott. Sowas macht man eher 
per State-Machine und hangelt sich an den Kommata durch, da die Anzahl 
der Zeichen innerhalb eines NMEA Datensatzes nicht festgelegt ist (wie 
oben schon angemerkt).

von Torsten B. (torty)


Lesenswert?

Danke
Werde ich gleich zu hause mal ausprobieren.
Wenn ich mich richtig entsinne, habe ich aber schon ausprobiert, den 
running pointer direkt auf den Ur-String zeigen zu lassen. Ich meine, 
dass strsep dann nicht funktioniert hat. Aber ich werde testen und 
berichten :-)
Versuch macht kluch ...

Was ich allerdings noch nicht verstanden habe ist, warum sich die Start 
Adresse der String-Kopie unter strdup verändert. Es wird doch nur ein 
1Byte Komma durch ein 1Byte "0" ersetzt. Bleibt die Länge  und damit der 
belegte Speicherbereich dabei nicht gleich?

grüße
Torsten

von Rolf Magnus (Gast)


Lesenswert?

Torsten B. schrieb:
> Bin auch noch neu auf diesem Gebiet, aber diese vorgehensweise ist in
> vielen Beispielen zu der Funktion strsep so zu sehen.
> Wie würdest Du es machen ?

Den von strdup zurückgegebenen Wert in einem zweiten Zeiger speichern 
und dann am Schluß den an free() übergeben.
1
char* running, copy;
2
running = copy = strdup(uart_string[j]);
3
4
ptr = strsep(&running,delimiter);
5
// ...
6
7
free(copy);

Wo ist in einem Programm eigentlich der Code, der sich um das $ am 
Satz-Begin kümmert?


Nur noch kurz zur Anmerkung, unabhängig vom Problem:
1
struct _PFLAU

Namen, die mit einem Unterstrich, gefolgt von einem Großbuchstaben 
anfangen, sind für den Compiler reserviert und dürfen von dir nicht 
selbst definiert werden.
Es ist in C auch allgemein eher unüblich, seinen Datentypen Namen zu 
geben, die komplett aus Großbuchstaben bestehen. Das wird in der Regel 
nur bei Makros gemacht, letztendlich auch, damit man daran gleich 
erkennt, daß es sich um ein Makro handelt. Es gibt zwar Ausnahmen (wie 
z.B. FILE* in Standard-C), aber die haben meist historische Gründe.

von Torsten B. (torty)


Lesenswert?

@Simon:
Bin mir wegen der Anzahl der Komma in einem Satz-Typ nicht sicher, aber 
vorstellen kann ich mir nicht, dass die Anzahl nicht festgelegt ist.
Ob das Feld beschrieben ist, oder nicht ist nicht festgelgt, aber woher 
sollte ein Parser (egal welcher Bauart) wissen, in welchem Feld er 
gerade ist, wenn die Anzahl der Felder nicht vorgegeben wäre ?

Werde es aber auch mal mit einer State-Machine probieren.

Grüße
Torsten

von Rolf Magnus (Gast)


Lesenswert?

Torsten B. schrieb:
> Was ich allerdings noch nicht verstanden habe ist, warum sich die Start
> Adresse der String-Kopie unter strdup verändert.

strsep...

> Es wird doch nur ein 1Byte Komma durch ein 1Byte "0" ersetzt.

Was denkst du, woher strsep beim zweiten Aufruf weiß, daß das erste 
Token schon gelesen wurde? Oder warum du nicht den Zeiger, sondern 
dessen Adresse übergeben mußt? Antwort: In diesem Zeiger speichert 
strdup die Position des ersten Zeichens nach dem Komma, so daß es beim 
nächsten Aufruf da weitermachen kann.

> Bleibt die Länge  und damit der belegte Speicherbereich dabei nicht
> gleich?

Doch, aber du mußt an free() einen Zeiger auf den Anfang des allokierten 
Blocks übergeben und nicht einen, der da irgendwo reinzeigt.

von Torsten B. (torty)


Lesenswert?

@Rolf:
Ich prüfe in der Hauptschleife die Checksumme des Satzes. Hier wird 
festgestellt, ob alles drin ist. Wenn das der Fall ist, gehe ich davon 
aus, dass das $ an der ersten Stelle im String steht.
Ist das Flasch ?

Grüße
Torsten

von Torsten B. (torty)


Lesenswert?

@Rolf:

Rolf Magnus schrieb:
> Was denkst du, woher strsep beim zweiten Aufruf weiß, daß das erste
> Token schon gelesen wurde? Oder warum du nicht den Zeiger, sondern
> dessen Adresse übergeben mußt? Antwort: In diesem Zeiger speichert
> strdup die Position des ersten Zeichens nach dem Komma, so daß es beim
> nächsten Aufruf da weitermachen kann.

OK, jetzt hab ich es kapiert .... (mechanisches Hirn halt) :-)

von spess53 (Gast)


Lesenswert?

Hi

>Bin mir wegen der Anzahl der Komma in einem Satz-Typ nicht sicher, aber
>vorstellen kann ich mir nicht, dass die Anzahl nicht festgelegt ist.

Die Anzahl der Kommas ist festgelegt. Auch das Format eines Wertes 
zwischen zwei Kommas. Wenn aber der Chipsatz bestimmte Werte nicht 
unterstützt oder der Wert z.B. wegen der Empfangsbedingungen nicht 
ermittelt werden kann, dann steht halt nichts zwischen zwei Kommas.

MfG Spess

von Rolf Magnus (Gast)


Lesenswert?

Torsten B. schrieb:
> Ich prüfe in der Hauptschleife die Checksumme des Satzes. Hier wird
> festgestellt, ob alles drin ist. Wenn das der Fall ist, gehe ich davon
> aus, dass das $ an der ersten Stelle im String steht.
> Ist das Flasch ?

Nein, aber du testest den Anfang deines Satzes z.B. gegen "PFLAU". Müßte 
das nicht "$PFLAU" sein? Damit fängt der Satz doch an.

spess53 schrieb:
> Wenn aber der Chipsatz bestimmte Werte nicht unterstützt oder der Wert
> z.B. wegen der Empfangsbedingungen nicht ermittelt werden kann, dann
> steht halt nichts zwischen zwei Kommas.

Und damit wären wir wieder am Anfang:

Torsten B. schrieb:
> Ich weiß, dass einige Strings leer sein können.
> Aus diesem Grund haben ich auch die strsep Funktion genommen und nicht
> die strtok Funktion, die nämlich mit leeren Token nichts anfangen kann.

von spess53 (Gast)


Lesenswert?

Hi

>Und damit wären wir wieder am Anfang:

>Torsten B. schrieb:
>> Ich weiß, dass einige Strings leer sein können.
>> Aus diesem Grund haben ich auch die strsep Funktion genommen und nicht
>> die strtok Funktion, die nämlich mit leeren Token nichts anfangen kann.

Ich hatte auch weiter oben geschrieben:

>Wenn mich meine rudimentären C-Kenntnisse nicht trügen,...

Ich habe das bisher nur in Assembler realisiert.

MfG Spess

von Konrad S. (maybee)


Lesenswert?

Torsten B. schrieb:
> Was ich allerdings noch nicht verstanden habe ist, warum sich die Start
> Adresse der String-Kopie unter strdup verändert. Es wird doch nur ein

Generell ist jede Verwendung einer Pointer-Referenz (also die Adresse 
des Pointers, wie bei &running), bei welcher der Pointer mit strdup(), 
malloc() usw. belegt wurde, verdächtig (==Fehler), sofern nicht 
besondere Vorkehrungen getroffen wurden (Sicherungskopie).

Performanz:
1. Wenn du es eilig hast, dann solltest du nach "GPRMC" (oder "$GPRMC") 
nicht mit strstr() suchen, weil immer der gesamte String abgesucht 
werden muss, falls der gesuchte String nicht enthalten ist. Da du eine 
genaue Vorstellung davon hast (hast du doch, ja?), wo der String zu 
finden sein müsste, kannst du dort besser mit strncmp() nachschauen. Und 
evtl. ist es angebracht nach "$GPRMC," zu suchen, weil es auch ein 
"$GPRMCL" geben könnte (von ',' zum 'L' ist es nur ein Bit das umfallen 
muss und ein weiteres Bit an gleicher Position in einem anderen Byte, 
damit die NMEA_checksum() wieder stimmt).

2. Zwischen die
  if (strstr(uart_string[j],"PFLAU")) { ... }
  if (strstr(uart_string[j],"GPRMC")) { ... }
würde ich noch ein else setzen. Wenn es ein "PFLAU" ("$PFLAU,") war, 
kann es kein "GPRMC" sein. Und auf die häufiger vorkommenden Datensätze 
zuerst testen.

von Torsten B. (torty)


Lesenswert?

Konrad S. schrieb:
> Performanz:
> 1. Wenn du es eilig hast, dann solltest du nach "GPRMC" (oder "$GPRMC")
> nicht mit strstr() suchen, weil immer der gesamte String abgesucht
> werden muss, falls der gesuchte String nicht enthalten ist. Da du eine
> genaue Vorstellung davon hast (hast du doch, ja?), wo der String zu
> finden sein müsste, kannst du dort besser mit strncmp() nachschauen. Und
> evtl. ist es angebracht nach "$GPRMC," zu suchen, weil es auch ein
> "$GPRMCL" geben könnte (von ',' zum 'L' ist es nur ein Bit das umfallen
> muss und ein weiteres Bit an gleicher Position in einem anderen Byte,
> damit die NMEA_checksum() wieder stimmt).
>
> 2. Zwischen die
>   if (strstr(uart_string[j],"PFLAU")) { ... }
>   if (strstr(uart_string[j],"GPRMC")) { ... }
> würde ich noch ein else setzen. Wenn es ein "PFLAU" ("$PFLAU,") war,
> kann es kein "GPRMC" sein. Und auf die häufiger vorkommenden Datensätze
> zuerst testen.

Das leuchtet ein und wird heute Abend gleich mal angetestet.
Danke

von Rolf Magnus (Gast)


Lesenswert?

Es gibt auch die Antwort auf meine Frage. Ich hatte hier an strcmp 
gedacht, aber durch die Verwendung von strstr wird's auch gefunden, wenn 
es nicht ganz am Anfang steht, sondern noch ein $ davor.

von Torsten B. (torty)


Lesenswert?

HAllo Gruppe

Hab all Eure Tips mal ausprobiert,aber keinen Erfolg gehabt. Der Fehler 
liegt in einer anderen Ecke.

Habe ein kleines Programm geschrieben, dass den Datenstrom meines 
Gerätes simuliert und die Sätze vertauscht ausgeben kann.
Bisher kam $PFLAU zuerst und wurde als einziger Satz erkannt.

Wenn ich jetzt $GPRMC als ersten Satz übertrage, wird dieser als 
einziger erkannt.

Es hat also was mit der Reihenfolge der Sätze zu tun.
????

Vielleicht versuche ich doch mal eine klassische Statemashine zu bauen, 
wenn das alle so machen.

Hat sonst noch jemand eine Idee ?

Danke
Torsten

von Konrad S. (maybee)


Lesenswert?

Poste doch noch mal den aktuelle Stand des Codes.

von Torsten B. (torty)


Angehängte Dateien:

Lesenswert?

gerne

von spess53 (Gast)


Lesenswert?

Hi

>Hat sonst noch jemand eine Idee ?

-Mal abgesehen von deinen Schwierigkeiten mit der Stringverarbeitung 
finde
 ich die Verwendung des Timers schon etwas suboptimal. Ein NMEA-String 
fängt
 mit '$' an und endet mit CR/LF. Beides lässt sich einfach und schnell 
im
 RXC-Interrupt detektieren.

-Von einem NMEA-Datensatz sind selten alle Werte interessant. Also warum 
die
 Zeit mit dem Aufdröseln dieser Werte vergeuden?

MfG Spess

von Konrad S. (maybee)


Lesenswert?

Die Variablen BlockOK und SatzNr müssten wohl noch volatile deklariert 
werden, da sie zur Kommunikation zwischen ISRs und dem Programm 
verwendet werden.

von Torsten B. (torty)


Angehängte Dateien:

Lesenswert?

Hallo Gruppe
Hier nun eine im Grunde gut funktionierende Version.

ABER !!!

In Zeile 79 steht folgendes geschrieben:
"char uart_string_copy[UART_MAXSTRLEN + 1];"

Das ist eine Stringvariable, die ich zwischenzeitlich mal gebraucht 
habe, aber in dieser Version nur noch Speicher belegt, weil nicht mehr 
benötigt.

Wenn ich diese Zeile nun aber mit "//" stilllege, kommt zwar beim 
kompilieren keine Meldung (auch kein Warning), aber dafür erscheint auf 
meinem LCD Display nur noch Müll.

?!?!?!?

Wenn ich die "//" wieder lösche und ich den Compiler die Zeile wieder 
verarbeiten lasse, geht wieder alles super.

Kann mir das jemand erklären ?

Danke
Torsten

von ... (Gast)


Lesenswert?

Kann es sein, daß SatzNr zu groß wird und Du dann in uart_string_copy 
(so vorhanden) schreibst?

von ... (Gast)


Lesenswert?

... Zum testen kannst Du ja mal folgendes versuchen:
1
char uart_string[MaxSatz + 1][UART_MAXSTRLEN + 1];
2
//char uart_string_copy[UART_MAXSTRLEN + 1];

von Simon B. (nomis)


Lesenswert?

An welcher Stelle stellst Du sicher, dass du maximal MaxSatz Sätze 
empfängst?
1
for (unsigned char j = 0; j <= SatzNr; j++)

Bist Du sicher, dass Du da nicht vielleicht "<" meinst?

Viele Grüße,
        Simon

von ... (Gast)


Lesenswert?


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.