Analyse NMEA Datensatz klappt nicht so richtig - Rechnenleistung schuld

OP #2225714
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.
Angehängte Dateien:
Gast #2225749
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
OP #2225757
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
#2225758
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.
#2225816
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. ;-)
OP #2225832
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
Gast #2225842
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.
OP #2225845
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
Gast #2225851
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.
OP #2225854
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) :-)
Gast #2225857
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
Gast #2225864
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.
Gast #2225891
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
#2225979
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.
OP #2226126
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
Gast #2226133
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.
OP #2226833
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
Gast #2226943
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
OP #2228111
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
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren