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.
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
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
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.
@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
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. ;-)
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).
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
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.
@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
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.
@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
@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) :-)
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
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.
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
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.
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
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.
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
Poste doch noch mal den aktuelle Stand des Codes.
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
Die Variablen BlockOK und SatzNr müssten wohl noch volatile deklariert werden, da sie zur Kommunikation zwischen ISRs und dem Programm verwendet werden.
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
Kann es sein, daß SatzNr zu groß wird und Du dann in uart_string_copy (so vorhanden) schreibst?
... 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];
|
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
Warum zur Hölle der Doppelpost ?!?!? Beitrag "Weg-kommentierte Variablen verändern den Programmablauf"
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.