Also wenn es an sowas hapert, hilft es häufig obskure "Konstrukte"
aufzulösen...
Erstmal wir i nicht initialisiert, als nächstes wird Buffer etwas
zugewiesen bevor die Schleife verlassen wird, und als nächstes wird
falls kein Komma kommt (warum auch immer) fröhlich der Speicher
vollgeschrieben in alle möglichen anderen Datenstrukturen hin nein...
Dieses "Konstrukt" ist das Ergebniss eines "Einlaufs" den ich hier im
Forum bekommen habe weil ich es gewagt habe das Komma NICHT zur
auswertung heranzuziehen. ;-) ;-) ;-)
Ich lese damit ein NMEA Protokol ein. Beispiel:
$GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*57
Das Konstrukt liest das auch wunderschön ein. Auch der Vergleich des
Strings "GPRMC," mit dem eingelesenen String funktioniert. Ich würde nur
gerne "GPRMC" und nicht "GPRMC," vergleichen.
Ich möchte es gerne verstehen:
1) Warum muss ich i initialisieren? Ich schreibe doch unmittelbar mit
recieve() was rein!
2) Warum sollte denn das NMEA Protokol kein Komma ausspucken? Oder
andersrum: Sollte es kein Komma mehr ausspucken ist dann nicht sowieso
alles schon zu spät und der Buffer dann auch egal?
Ich meine es wirklich im Sinne von verstehen wollen:
Mein C Buch sagt dass in einer nicht initialsierten variablen eben
irgendein Wert drin steht. Aber das ist doch nicht schlimm wenn man
unmittelbar was hineinschreibt?
Wenn das Komma zu unsicher ist ( habe ich das richtig verstanden?) um
eine Schleife zu beenden, was wäre dann der bessere Weg?
Attila schrieb:> Mein C Buch sagt dass in einer nicht initialsierten variablen eben> irgendein Wert drin steht. Aber das ist doch nicht schlimm wenn man> unmittelbar was hineinschreibt?
Ja, aber in deiner ersten Version wird nicht unmittelbar was
hineingeschrieben. Vorher findet schon der erste "i!=','"-Test statt.
> Wenn das Komma zu unsicher ist ( habe ich das richtig verstanden?) um> eine Schleife zu beenden, was wäre dann der bessere Weg?
Besser wäre es zusätzlich eine Längenbegrenzung mit in die Schleife
aufzunehmen.
Du hängst jedes Zeichen nach receive() an den buffer an.
hier ist es natürlich einfach
void uartstr (char* buffer)
{
int i;
while((i=receive())!=',')
{
*buffer++=i;
}
*buffer='\0';
}
Oft hilft auch der ,-Operator im while um einen zweiten identischen
Funktionsaufruf zu umgehen, sieht dann so aus:
while(scanf("%d",&i), i!=0)
{
...
}
Peter
@Peter: Ganz ehrlich: Ich habe auch nach mehrmaligem lesen nicht
verstanden was Du sagst.
Der ,- operator? Was ist das?
zweiter identischer Funktionsaufruf?Wie jetzt?
Das "Konstrukt":
while(scanf("%d",&i), i!=0)
{
...
}
bedeutet dass solange niemand eine "0" eingibt die while Schleife nicht
verlassen wird, richtig?
Mit dem ,-Operator kannst du mehrere Anweisungen nacheinander ausführen,
fast wie das ;. In while() darf z.B. kein Semikolon stehen, hier
und in for() ist der ,-operator manchmal ganz praktisch
Prinzipiell wird erst der Ausdruck links vom , bestimmt, das Ergebnis
wird dann weggeworfen, dann wird der Wert rechts vom , bestimmt
und das ist dann das Ergebnis.
In der oben angegebenen whileschleife bestimmt also das i!=0 ob
die Schleife ausgeführt wird. Das Ergebnis des Scanf ist hierfür
völlig irrelevant (außer das als Nebeneffekt natürlich i einen
neuen Wert bekommt)
http://en.wikipedia.org/wiki/Comma_operator
Peter
Attila schrieb:> Wenn das Komma zu unsicher ist ( habe ich das richtig verstanden?) um> eine Schleife zu beenden, was wäre dann der bessere Weg?
Nein das Komma an sich ist nicht unsicher!
Unsicher ist es einen Pointer gnadenlos ohne wissen der Länge des
reservierten Speichers vollzuschreiben.
Attila schrieb:> Warum sollte denn das NMEA Protokol kein Komma ausspucken?
Wer garantiert dir das du einen gültigen NEMA String empfängst? Zumal
dieser nicht mit einem Komma Endet! (Ein Zeilenumbruch sollte also am
besten auch noch die Schleife abbrechen..., und das anfängliche $ könnte
man auch noch unterdrücken...)
Und spätestens hier kommt man an einen Punkt, wo alles in eine While
Bedingung zu packen die Sache nicht übersichtlicher macht.
Und das ist am Anfang das aller wichtigste!
Und was machst du wenn der Aufrufer (sei es auch Speichernot sei es aus
Unachtsamkeit) nur einen Buffer der Länge 3 angelegt hat... deshalb
sollte man immer die Länge des buffers zusätzlich übergeben und auch
prüfen!
Peter schrieb:> Mit dem ,-Operator kannst du mehrere Anweisungen nacheinander ausführen,
Bitte!
Theatere den Fragesteller nicht in den Komma-Operator hinein, zumal er
an dieser Stelle unnötig ist, weil ein && hier ganeusogut das gewünschte
erledigt. Ganz im Gegenteil, wenn der scanf schief geht, kannst du dir
mit dem Komma Operator hier ganz schönen Ärger einhandeln, weil kein
Mensch dir garantiert, dass i dann 0 wird.
Der Komma-Operator ist ein konstanter Quell des Ärgernisses,
insbesondere für Neulinge.
Läubi .. schrieb:>> Warum sollte denn das NMEA Protokol kein Komma ausspucken?> Wer garantiert dir das du einen gültigen NEMA String empfängst?
@Attila
Das ist überhaupt ein wichtiger Punkt bei (fast) jeglicher
Eingabeverarbeitung.
Nachdem du deine Eingabebehandlung fertig hast, setz dich hin und
überleg dir, was alles noch passieren kann. Dazu stellst du dir einfach
vor, deine Engabe kommt nicht einfach voin irgendwo her, sondern von
einer Tastatur und auf diese legst du einen Stein drauf. Übersteht das
deine Eingaberoutine?
Oder noch schlimmer, du fährst mit der Hand ein paar mal wahllos von
links nach rechts und wieder zurück über die Tastatur (Gewiefte setzen
ein Kleinkind an die Tastatur und lassen es einfach drauflosklimpern).
Eine gute Eingaberoutine übersteht so etwas, ohne das etwas Schlimmes
passiert. Klar das Programm wird (und soll) einen Fehler registrieren
und vielleicht sogar auch melden, aber es wird unter keinen Umständen
etwas Schlimmes passieren, wie zb Speicher niederbügeln, der gar nicht
mehr zum Eingabearray gehört oder Auswertungen mit falschen Daten
machen.
Das Interessante daran. Es ist gar nicht mal so schwer eine Eingabe
gegen derartige Dinge abzusichern und oft sind die Modifikationen gar
nicht schwer und gar nicht mal so groß. Mit ein paar kleinen
Umstellungen und Abfragen ist das alles erledigt und berücksichtigt.
@Läubi und @Karl Heinz:
Vielen vielen Dank für den Input und ich werde ihn versuchen Schritt für
Schritt umzusetzen!
Wenn ich sehe was ich mir hier zusammenstricke und mit euren Kommentaren
verquicke......stelle ich fest: Ich habe noch einen sehr weiten Weg zu
gehen!
Wenn es recht ist werde ich das "Konstrukt" nochmal überarbeiten und
hier wieder einstellen.
Danke euch Jungs, andere müssen für sowas zahlen!
Attila schrieb:> So richtig?>> void uartstr (char* buffer)
nein.
Wenn du eine Funktion schreibst, die in einem Array etwas ablegen soll,
dann ist es PFLICHT, dass der Aufrufer die Größe des Arrays mitgeben
muss.
Alles andere ist 'Ask for trouble'
> {> int i=0,check=0;>> i=receive();> while(i!=','&&check<=10)
:-)
Woher weißt du die 10?
Genau davon hat der erste Absatz gehandelt. Diese 10 muss der Aufrufer
der Funktion der Funktion mitgeben. Denn nur er kann das wissen.
1
voiduartstr(char*buffer)
2
{
3
inti=0,check=0;
4
5
i=receive();
int i? Wieso int? Hier ist von Zeichen aus der UART die Rede. Konkret
ASCII Zeichen. Also char
@Karl Heinz:
Genau: Wieso eigentlich int? Ärgerlicher Fehler!
Zu der 10: Ich lese ja ein NMEA Protokol aus. Zumindest in dem Protokol
(RMC) was ich auslese ist das länsgte string 10 Zeichen lang. Nämlich
der Längengrad. Ist aber falsch gedacht wie ich jetzt begriffen habe.
Allerdings wirft das eine neue Frage auf:
Wie mache ich das denn bei einem String bei dem das Protokol selber
entscheided wieviele zeichen es hat?
Beispiel: Geschwindigkeit über Grund kann vom Format her "0" bis "0.000"
Knoten sein.
Attila schrieb:> @Karl Heinz:>> Genau: Wieso eigentlich int? Ärgerlicher Fehler!>> Zu der 10: Ich lese ja ein NMEA Protokol aus. Zumindest in dem Protokol> (RMC) was ich auslese ist das länsgte string 10 Zeichen lang. Nämlich> der Längengrad. Ist aber falsch gedacht wie ich jetzt begriffen habe.
Yep.
Frag dich einfach:
Ist diese Funktion in irgendeiner Art und Weise NMEA spezifisch?
Eigentlich doch nicht. Denn diese Funktion soll einen String empfangen.
Ob den ein Benutzer eingibt, oder ob der von einer Fräsmaschine kommt
oder eben wie bei dir von einem GPS Empfänger ist doch erst mal für die
Funktion selber uninteressant.
Und da du nicht bei jedem Projekt das Rad immer wieder neu erfinden
willst, schreibst du dir am liebsten immer Funktionen, die eben dieses
Wissen nicht in ihrem Code enthalten haben, sondern allgemein verwendbar
sind.
In deinem Code gibt es keinerlei Sicherung gegen diesen Fall
char buffer[4];
uartstr (buffer);
Ooops. Das Feld hier ist nur 4 Zeichen lang. Trotzdem behandelt es deine
Funktion als ob es 10 Zeichen lang wäre. Komplette Rechenzentren wurden
wegen solcher blödsinniger Annahmen in Funktionen schon lahmgelegt.
Hier, beim Aufruf weiß ich, wei lang mein Array ist, in welches ich den
String reinhaben möchte. Also
char buffer[4];
uartstr( buffer, sizeof(buffer) );
uartstr muss jetzt nur noch mit dieser Zahl arbeiten. Das kostet uartstr
nichts und das kostet mir hier beim Aufruf nichts. Nur hab ich plötzlich
eine Funktion, die ich allgemeiner benutzen kann.
Welche Annahmen hast du in uartstr noch getroffen?
Du hast noch die Annahme getroffen, dass der String mit einem ','
aufhört. Muss das so sein? (Denk jetzt an den allgemeinen Fall). Könnte
man diese Annahme in der Funktion leicht loswerden?
Ja, könnte man
Dann sieht halt der Aufruf so aus
char buffer[4];
uartstr( buffer, sizeof(buffer), ',' );
und die Funktion bekommt das Ende Zeichen mit.
> Allerdings wirft das eine neue Frage auf:>> Wie mache ich das denn bei einem String bei dem das Protokol selber> entscheided wieviele zeichen es hat?
Das geht in C auf deinem µC nicht vernünftig. Dazu müsste man dynamisch
Speicher allokieren und das möchte man auf so einem kleinen µC
eigentlich nicht tun. Leb damit, dass du den Buffer beim Aufrufer
großzügig genug dimensionierst, so dass alles reinpasst.
http://www.mikrocontroller.net/articles/FAQ#wie_schreibt_man_eine_Funktion.2C_die_einen_String_liefert.3F
Karl Heinz!
Ok ich habe verstanden dass es nicht sonderlich "schön" ist was ich mir
da zusammengebastelt habe und ich werde alles was Du oben anmerkst auch
umsetzen. Nur: Ist denn das was ich jetzt habe falsch im Sinne von
"abstürzenden Rechenzentren"?
void uartstr (char* buffer,int laenge)
{
char i=0;
int zaehler=0;
i=receive();
while(i!=','&&zaehler<=laenge)
{
zaehler++;
*buffer++=i;
i=receive();
}
*buffer='\0';
}
Wobei im main dies steht:
char rein[11]
Und als Aufruf dies z.b.:
uartstr(rein,1); //Wenn es um das Zeichen "N" oder "S" oder ähnliches
geht
Oder z.B.:
uartstr(rein,10) //Wenn es um den Längengrad geht
Attila schrieb:> Wobei im main dies steht:>> char rein[11]>> Und als Aufruf dies z.b.:>> uartstr(rein,1); //Wenn es um das Zeichen "N" oder "S" oder ähnliches> geht
Nein, das machst du so nicht.
Du hast das ',' von der Eingabe, welches den String beendet.
Hier geht es nicht darum, wie lang der String ist, den du erwartest,
sondern darum wie groß der Buffer ist, den du uartstr zur Vefügung
stellst um dort den String abzulegen.
Denn ob von der SChnittstelle tatsächlich der String kommt, den du
erwartest, kannst du sowieso nicht sicher stellen.
> uartstr(rein,10) //Wenn es um den Längengrad geht
char rein[11]
uartstr(rein,11);
weil rein 11 char groß ist.
Oder eben
char rein[11]
uartstr( rein, sizeof(rein) );
dann brauchst du das nicht selber abzählen sondern der Compiler macht
das für dich.
Ich glaube so soll es sein:
void uartstr (char* buffer,int size,char zeichen)
{
char i=0;
int zaehler=0;
i=receive();
while(i!=zeichen&&zaehler<=size)
{
zaehler++;
*buffer++=i;
i=receive();
}
*buffer='\0';
}
Aufruf geht so:
uartstr(rein,sizeof(rein),',');
Funktioniert, sieht besser aus, löst aber mein Problem nicht: Ich
versuche es mal zu beschreiben:
Ich habe ein Programm geschrieben welches den Kurs von einer Koordinate
zu einer anderen Koordinate berechnet.
Diese funktionert auch auf Visual Express korrekt.
Ich habe das ganze dann auf einen Atmega8 übertragen inclusive der UART
Funktion und der Displaysteuerung.
Wenn ich eine Zielkoordinate Eingebe die etwas weiter weg von meiner
aktuellen Position ist arbeitet es halbwegs in etwa 98% der Zeit
richtig.
Wenn ich meine aktuelle Poition als Ziel eingebe dann "eiert" der Kurs
genau genommen ja um mich herum. Schön und faszinierend anzusehen und
auch richtig wenn nicht nach kürzester Zeit dinge wie "836" auf meinem
Display stehen würde. Das kann garnicht sein weil ein Arkustangens
nunmal nur etwas zwischen -180 und +180 generieren kann. Irgenwann
bleibt die Anzeige bei -180 eingefroren stehen.
2 Fragen nun:
Kann es sein das der Atmega 8 schlichtweg überfordert ist? Ist ja ne
Menge Holz (wie ich als Anfänger finde)
Soll ich diesbezüglich einen neuen Thread aufmachen?
Attila schrieb:> Ich glaube so soll es sein:>> void uartstr (char* buffer,int size,char zeichen)> {> char i=0;> int zaehler=0;>> i=receive();> while(i!=zeichen&&zaehler<=size)> {> zaehler++;> *buffer++=i;> i=receive();> }> *buffer='\0';> }>> Aufruf geht so:>> uartstr(rein,sizeof(rein),',');>> Funktioniert, sieht besser aus,
Ist so schon eine gute Lösung.
> löst aber mein Problem nicht:
Das ist jetzt aber auch eine komplett andere Baustelle
> Kann es sein das der Atmega 8 schlichtweg überfordert ist?
Überfordert wäre er jetzt höchstens, wenn er mit der Rechenzeit nicht
mehr mitkommt. Aber davon war ja jetzt erst mal noch überhaupt nicht die
Rede
Zum anderen musst du bedenken, dass dein Mega mit wesentlich weniger
Kommastellen arbeitet als dein PC. Rechenungenauigkeiten werden so im
Lauf der Zeit immer größer, wenn man es ungeschickt anfängt.
Deine Beschreibung klingt aber eher nach einem Programmfehler.
Ohne Programm kann man da aber wenig dazu sagen.
Was mich stutzig macht ist, das du anscheinend beim UART Empfang schon
weißt, was du empfangen wirst. Das kann gut gehen, muss es aber nicht,
je nachdem wie es programmiert ist. Daher würde ich NMEA auch niemals so
empfangen, dass ich der Empfangsroutine sage: lies mal alles bis zum
','. Sondern immer so, dass ich erst mal eine komplette NMEA Zeile
empfange. Und nur dann, wenn ich die komplett habe, fange ich an sie zu
zerlegen und weiterzuverarbeiten. Denn eines ist auch klar. Während der
Mega am Rechnen ist, beginnt das GPS schon die nächste Übertragung. Das
wartet nicht auf dich.
Aber wie gesagt: Ohne Programm kann man da überhaupt nichts sagen.
Jaja lieber Karl Heinz: Komplett andere Baustelle weil die Baustelle die
ich vermutete nicht die Richtige war.
Ich arbeite , so glaube ich, mit, für einen Atmega, ziemlich grossen
Zahlen beispiel: 81356.8
Dennoch:
track=360+atan2(-distl,distb)*180/3.141592;
Selbst wenn distl und distb falsch sind ist es doch so dass egal welche
zahlen man da reinschreibt das Ergebniss nie , im schlimmsten Falle,
grösser als 540 ist.
Bedeutet doch: Der Fehler liegt hinter dieser Zeile und nicht davor,
richtig?
Und dahinter passiert nur ein itoa und die Ausgabe.
@Stefan: Ein "Raus aus Schranke" Zugriff? ;-) Und was ist das? Warum
haben C Bücher eigentlich kein Stichwortverzeichniss? Gnrlgnn ;-) Da
könnte ich dann nachschlagen und versuchen das Problem selber zu lösen!
@Karl Heinz:
Diese Bemerkung von Dir ist "Stöffchen" für , locker, die nächsten 2
Tage:
Denn eines ist auch klar. Während der
Mega am Rechnen ist, beginnt das GPS schon die nächste Übertragung. Das
wartet nicht auf dich.
Vielen Dank dafür! Ich melde wenn ich die Konsequenzen deiner Anmerkung
abgearbeitet habe!
@Karl Heinz:
Doch nicht 2 Tage das Programm umstricken. Es fragt nämlich nicht das
UART ab bevor es zu ende gerechnet hat.
Ich habe, glaube und hoffe ich, das Problem an dieser Stelle
eingegerenzt:
float track (float distb,float distl)
{
float track;
if(distl<=0)
{
track=atan2(-distl,distb)*180/3.141592;
}
if(distl>0)
{
track=360+atan2(-distl,distb)*180/3.141592;
}
return track;
}
Im Moment spielen sich distl und distb bei +- 1.00 ab. Kann es sein das
man die funktion atan2 nicht mit floats füttern darf?
Hi
>Während der Mega am Rechnen ist, beginnt das GPS schon die nächste>Übertragung. Das wartet nicht auf dich.
Normalerweise kommen die NMEA-Strings etwa in Sekundentakt. Da kann der
Controller noch nebenbei Kuchen backen.
MfG Spess
Ich hab da was gefunden, das könnte das Problem sein:
If both arguments passed are zero, a domain error occurs, which sets the
global variable ERRNO to the EDOM value.
Stefan Ernst schrieb:> Karl heinz Buchegger schrieb:>> Ist so schon eine gute Lösung.>> Na, na, na, Karl Heinz, also ich sehe da einen Out-Of-Bound-Zugriff. ;-)
Hast recht.
Zwischendurch hab ich ihn mal gesehen, aber dann wieder verschwizt.
Attila schrieb:> @Stefan: Ein "Raus aus Schranke" Zugriff? ;-) Und was ist das?
Wenn du 6 Zeichen in ein Array der Länge 5 quetschen willst
while(i!=zeichen&&zaehler<=size)
Hier: das <= fängt diesen potentiellen Fehler nicht ab.
Attila schrieb:> Muss es < heissen damit noch Platz für das "/0" ist?
nein, weil die Zählung bei 0 beginnt...
Warum beist du dich eigentlich mit aller Kraft an deiner while Schleife
fest? Ich hatte oben doch shcon ein Beispiel gepostet was dieses
"Problem" umgeht, und welches die "normale" Notation für Array zugriffe
nutzt...
Ebenso das problem mit int/char war dort auch schon gelöst man darf auch
gerne sich Beispiele mal angucken, es besteht keine Pflicht zu
iterative-trial-and-error-programming...
Hallo Läubi!
Es muss size-1 heissen!
Ich hatte das so verstanden dass meine Schleife jetzt in Sinne aller
Profis korrekt ist:
Hier nochmal die fertige Lösung:
void uartstr (char* buffer,int size,char zeichen)
{
char i=0;
int zaehler=0;
i=receive();
while(i!=zeichen&&zaehler<size-1)
{
zaehler++;
*buffer++=i;
i=receive();
}
*buffer='\0';
}
Und hier der Aufruf:
uartstr(rein,sizeof(rein),',');
Den Fehler im Programm ( welches jetzt einwdfrei läuft) habe ich auch
gefunden und obwohl es mir sehr peinlich ist, vielleicht hilft es
anderen:
Meine LCD Routine machte nur Cursor return und NICHT clear display. Wenn
dann der Kurs z:b 347 war stand da:
"347"
Und wenn Kurs sich dann nach 8 Grad änderte stand da:
"857"
Ich weiss es tut weh. ;-)
Attila schrieb:> Meine LCD Routine machte nur Cursor return und NICHT clear display.
Sie soll auch nicht 'Clear Display' machen.
Das führt zu Flackern auf dem LCD.
Ist doch nicht so schwer. Wenn du die Zeichen dahinter weghaben willst,
dann schreib halt einfach Leerzeichen drüber.
Oder formatiere die Zahl immer so, dass sie immer in einem String mit 3
Zeichen rechtsbündig dargestellt wird. Aussuchen kannst du dir ob du
führende 0-en haben willst oder ob führende 0-en durch Leerzeichen
ersetzt werden sollen.
Clear Display willst du sparsam einsetzen, aber auf keinen Fall um dein
Display in Ordnung zu halten, wenn sich Werte ändern.
@Karl Heinz:
Ich finde das nicht einfach.Echt nicht.
Ich finde deinen Hinweis aber sehr nützlich und werde versuchen ihn
umzusetzen.
Gibt es Beispiele zu , am liebsten, beide Versionen?
Attila Ciftci schrieb:> Ach so: Wieso will man clear display nicht einsetzen um das Display in> Ordnung zu halten. Nur wegen des Flackerns
Genau.
Ausserdem musst du dann andere Dinge auf dem LCD wieder neu schreiben.
Ist ja alles ebenfalls weg.
Nach Möglichkeit willst du am LCD immer nur die Dinge verändern, die
auch verändert werden müssen. Und das so, dass du nichts anderes dabei
beschädigst. Clear ist so ziemlich die schlimmste Form von
'Beschädigung', die du deiner Anzeige antun kannst.
Wie gesagt, die Technik dazu ist nicht weiter schwierig (*). Du musst
nur darauf achten, dass auch immer alles alte überschrieben wird. Am
einfachsten geht das, wenn man für jede Ausgabe auf dem LCD ein 'Feld'
mit einer bestimmten Stellenanzahl vorsieht und dort Zahlen
reinschreibt. Wenn alle Stricke reissen, kann man vorher immer noch
genau dieses Feld vorher mit einer entsprechenden Anzahl Leerzeichen
'löschen' und dann die neue Zahl dort hinschreiben. Macht man diesen
Update nur dann, wenn er auch tatsächlich notwendig ist (nur dann wenn
sich die Zahl auch geändert hat), dann merkt das noch nicht einmal wer.
Aber es ist auch nicht weiter schwer, die Zahlen so zu formatieren, dass
sie immer alles überschreiben.
Zahlen auch am besten rechtsbündig ausgeben. Nichts ist so lästig, wie
eine Zahl zu beobachten, die am Display ständig hin und her springt. Die
Einerstelle der Zahl sollte immer an der gleichen Position am LCD sein.
(*) Entweder schreibt man sich selber was, oder aber wenn man es sich
leisten kann benutzt man einfach sprintf. Das hat ein paar nette
Formatieroptionen mit, die man hier sinnvoll einsetzen kann.