Hallo, ich bin gerade dabei ein Protokoll für die Kommunikation zwischen PC und µC zu schreiben. Ich nutze die Bibliothek von Peter Fleury für den UART mit Ringpuffer. Ich empfange immer einen String, setze ein Flag, dass dieser zur Auswertung bereit ist und werte ihn dann aus. Das Grundprotokoll sieht so aus: [XON] [STX] [Name der Variable] [Daten] [ETX] ['OK'] [XOFF] - Nach Empfang XON weiß der µC, dass Daten kommen - STX ist das Startsignal - Danach kommt ein Variablenname zur Zuordnung - Danach die Daten als Zahl - ETX das Endsignal der Übertragung, hier sendet der µC die Empfangen [Daten] zurück - Die Gegenstelle prüft die Daten auf Gleichheit und quittiert bei Richtigkeit mit dem String "OK" - ... nächste Übertragung beginnt Ich möchte noch erwähnen, dass ich Anfänger bin und das erst mal so ein "Protokoll" schreibe. Mir geht es hier nur darum, was könnte man von der Struktur her besser oder einfacher machen oder wo sind noch Fehler. Danke.
- XON / XOFF kann man weglassen; das ist eigentlich für was anderes gedacht. - "Name der Variable" hat eine feste Länge? Wenn nicht, brauchst Du noch eine Längenangabe, damit man weiß, wo die "Daten" anfangen. - Je nach Sicherheitsbedarf kann man noch eine Prüfsumme hinter dranhängen. Gruß Dietrich
Dietrich L. schrieb: > "Name der Variable" hat eine feste Länge? Wenn nicht, brauchst Du noch > eine Längenangabe, damit man weiß, wo die "Daten" anfangen. Nein, bis jetzt ist der Name eigentlich unterschiedlich lang. Ich kann aber noch nicht ganz nachvollziehen, wieso dies eine feste Länge haben soll. Der UARTString enthält ja den Wert der Variable und bei jedem \0 geht der String zur Auswertung und wird dann über einen strcmp verglichen. Die Länge ergibt sich daher von [0] bis an die Stelle des erste \0 oder nicht?
>Die Länge ergibt sich daher von [0] bis an die Stelle des erste \0 oder
nicht?
Das ist aber ein Detail das in Deiner Beschreibung des Formates
enthalten sein muss.
Es steht zwar nicht in der Beschreibung oben, aber im Code ist folgendes:
1 | if (u8UARTEmpfang){ |
2 | UARTString[s] = c; |
3 | s++; // s erhöhen |
4 | |
5 | if (c == STRINGEND){ |
6 | for (s; s<= strlen(UARTString); s++){ |
7 | // restlichen Zeichen "löschen"
|
8 | UARTString[s] = STRINGEND; |
9 | }
|
10 | u8UARTEmpfangAuswerten = TRUE; |
11 | }
|
12 | }
|
Demnach wird bei jedem Ende eines Strings das Flag u8UARTEmpfangAuswerten geestzt und es erfolgt die Auswertung
1 | for (s; s<= strlen(UARTString); s++){ |
2 | // restlichen Zeichen "löschen"
|
3 | UARTString[s] = STRINGEND; |
4 | }
|
Häng einfach EIN \0 Zeichen an deine empfangenen Zeichen hinten drann und du hast einen String. Abgesehen davon macht strlen sowieso nicht das, was du denkst was es macht. Denn um strlen anwenden zu können, müsstest du schon einen String haben. String-Verarbeitung in C
> Der UARTString enthält ja den Wert der Variable und bei jedem \0 > geht der String zur Auswertung und wird dann über einen strcmp > verglichen. Die Länge ergibt sich daher von [0] bis an die > Stelle des erste \0 oder nicht? Kann man machen. Ist aber auf der UART Strecke unpraktisch. "Hour=20" ist viel einfacher. Vor allen Dingen ist es für den Sindern viel einfacher zu erzeugen. Zb. für den ersten Tester, der ein Mensch vor einem Terminal ist. Man kann dein Protokoll so aufbauen, wie du das machen willst. Würd ich aber ehrlich gesagt nicht tun. STX/ETX ist nett, aber eigentlich nicht wirklich notwendig. Denn wenn du das Protokoll so aufbaust, dass du ein sichtbares ASCII Zeichen als Endekennung hast, tust du dich in der Testphase (in der du mit einem Terminalprogramm höchstselbst die Kommandos zum µC schickst) viel leichter. Hour 12<Return> Minu 28<Return> Day 5<Return> Month 12<Return> Year 2012<Return> und du hast perfekt ein Datum übertragen. Und aus Date 5,12,2012<Return> kann man ebenfalls das einzustellende Datum sauber rausholen, ohne dass man auf nicht sichtbare Sonderzeichen angewiesen ist. Durch den <Return> (die Taste kann auf deiner Tastatur auch mit 'Enter' beschriftet sein, es ist die größe winkelförmige am rechten Rand deiner Haupttastatur, ist immer klar, wo ein Kommando aufhört. Und das wird seit mindestens 50 Jahren genau so benutzt.
Guten morgen, ich habe nun mal versucht, die Form (Vorschlag von Karl-Heinz) "Variable=Wert" in Pseudo Code zur Auswertung zu packen. Wäre das so in brauchbar oder gibt es besser Vorschläge? Danke.
1 | char UARTStringVar [20]; |
2 | char UARTStringWert [20]; |
3 | u8UARTFlag = FALSE; |
4 | |
5 | if (u8UARTEmpfang){ |
6 | |
7 | if (!u8UARTFlag){ |
8 | if (c == “=”){ // Zeichen gleich Gleichheitszeichen |
9 | UARTStringVar[s]= STRINGEND; // String abschließen mit Stringende-Zeichen |
10 | u8UARTFlag = TRUE; // Flag umstellen |
11 | s=0; // Array-Stellenzähler nullen |
12 | else{ |
13 | UARTStringVar[s] = c; |
14 | s++; |
15 | }
|
16 | }
|
17 | |
18 | else{ |
19 | if (c == 0x0D){ // Zeichen gleich Return |
20 | UARTStringWert[s] = STRINGEND; // String abschließen mit Stringende-Zeichen |
21 | u8UARTFlag = FALSE; // Flag umstellen |
22 | s=0; // Array-Stellenzähler nullen |
23 | }
|
24 | else{ |
25 | UARTStringWert[s] = c; |
26 | s++; |
27 | }
|
28 | }
|
29 | |
30 | }
|
Ich würd ehrlich gesagt strikt trennen zwischen: Eine Zeile (denn um etwas anderes handelt es sich ja nicht, bei eine String, der mit einem \n (oder \r) aufhört) empfangen und dessen Zerlegung bzw. Auswertung.
Hallo Martin, Wenn Du ein Protokoll zwischen PC und uC entwickelst, damit ein Mensch über den PC mit dem uC sprechen kann, dann solltest Du möglichst auf Steuer-/Sonderzeichen verzichten. Wenn aber eine Software auf dem PC mit einer Software auf dem uC sprechen soll, dann muss ich meinem geschätzten Kollegen Buchegger widersprechen. STX/ETX bieten eine einfache undmit wenig Code zu realisierende Möglichkeit die Kommunikation zweier Systeme zu synchronisieren. Man kann beide Seiten mit wichtigen Dingen beschäftigen und auch beliebige Störungen einstreuen. Ohne STX wird erst einmal alles Verworfen. Ein ETX schließt die Kommunikation ab. Wenn die Anzahl der Daten den Puffer überschreiten, wird die Kommunikation abgebrochen, und man wartet wieder auf STX. Dauert die Kommunikation zu lange (Unterbrechung) wartet man wieder auf STX. Überträgt man nach dem STX zuerst einmal die zu erwartende Länge der nun kommenden Daten, weiß man sowohl die Zeit, bis ETX kommen sollte, also auch die Anzahl Zeichen bis ETX kommt. Auch hier kann man dann einfach wieder auf 'Warte auf STX' zurück fallen, wenn eines der beiden nicht stimmt, bzw. zusätzlich einen Fehlercode an die Gegenstation senden. Ein Fehlercode wäre dann das Steuerzeichen NAK (0x15) vielleicht auch gefolgt von einem Code 1...255 der sagt, welcher Fehler. Der Vorteil ist einfach, dass man mit STX und ETX einen Datenrahmen hat, der sehr einfach auszuwerten ist. Und wenn man Programme wie HTerm einsetzt, dann kann man diese in der Phase der Entwicklung auch sehr gut von Hand erzeugen. Gruß, Ulrich
Karl Heinz Buchegger schrieb: > Ich würd ehrlich gesagt strikt trennen zwischen: > Eine Zeile (denn um etwas anderes handelt es sich ja nicht, bei eine > String, der mit einem \n (oder \r) aufhört) empfangen und dessen > Zerlegung bzw. Auswertung. Hmm, wie soll ich mir das denn vorstellen? Ich dachte es ist recht simpel, wenn ich sowieso die einzelnen Zeichen aus dem Ringpuffer in einen String einlese, dieses dann direkt zu vergleichen und schon vorselektiere, was wie wo zugehört.
Kann mir jemand noch helfen, ich komme einfach nicht weiter, wie ich so einen String auswerte. Ich denke das einfachste wird einfach sein, wie vorgeschlagen Variable=Wert, aber wie zerlege ich das und werte es aus?
Martin schrieb: > Kann mir jemand noch helfen, ich komme einfach nicht weiter, wie ich so > einen String auswerte. > Ich denke das einfachste wird einfach sein, wie vorgeschlagen > Variable=Wert, aber wie zerlege ich das und werte es aus? zb * mit strchr kannst du die Position des '=' suchen lassen. Alles davor ist dann Variablenname, alles dahinter ist Argument * auch strtok ist brauchbar. mit strtok den '=' suchen lassen (falls es überhaupt einen gibt). Wenn es einen gibt, setzt strtok da gleich ein \0 an dieser Stelle ein, so dass der String auch hier gleich mal in 2 Teile (Teilstrings) unterteilt wird. Hast du die Teile, dann kann man zb den Variablennamen in einer Tabelle suchen und je nachdem wie komplex das ganz ist, in dieser Tabelle zb einen Pointer auf die zu verändernde Variable haben. In der Tabelle könnte aber auch zb ein Funktionspointer mit dem Namen verknüpft werden. Oder .... Da gibt es jetzt viele Möglichkeiten.
Hallo Karl Heinz und andere, ich habe jetzt die Suchfunktion nach den Funktionen abgeklappert und ein bisschen über Pointer gelesen. Gesendet wird nun: [STX]Variable=Wert[ETX] Es erfolgt nach erhalt des ETX Zeichen eine Auswertung. Dazu wird geschaut, wo STX steht (normalerweise immer an nullter Stelle, von dort bis '=' der String herausgezogen und in UARTVariable kopiert. Danach wird der Pointer um 1 erhöht um auf die erste Stelle von Wert zu kommen (Pointer steht ja derzeit noch auf '=') und dann Wert bis ETX kopiert. Jeweils an letzter Stelle für die String Terminierung ein \0 angehängt. Nun habe ich folgendes geschrieben bzw. einen Code-Schnipsel aus der Suchfunktion modifiziert (alles mit der Fleury-lib für UART):
1 | char UARTString[20]; |
2 | char Result[20]; |
3 | char* source = UARTString; |
4 | char* dest = Result; |
5 | char UARTVariable[20]; |
6 | char UARTWert[20]; |
7 | uint8_t u8UARTEmpfangAuswerten = FALSE; |
8 | |
9 | // UART
|
10 | c = uart_getc(); |
11 | /*
|
12 | * send received character back
|
13 | */
|
14 | if ( c & UART_NO_DATA ) |
15 | {
|
16 | /*
|
17 | * no data available from UART
|
18 | */
|
19 | }
|
20 | else
|
21 | {
|
22 | UARTString[s] = c; |
23 | s++; // s erhöhen |
24 | if (c == ETX){ // Endzeichen des Strings empfangen? |
25 | u8UARTEmpfangAuswerten = TRUE; // Flag setzen, dass String vollständig empfangen und zur Auswertung bereit ist |
26 | uart_putc( (unsigned char)c ); // sendet auf die Schnittstelle ETX als Bestätigung für den Benutzer |
27 | }
|
28 | }
|
29 | |
30 | if (u8UARTEmpfangAuswerten){ |
31 | source = UARTString; // Pointer source auf den Anfang von UARTString setzen |
32 | dest = Result; // Pointer dest auf den Anfang von Result setzen |
33 | while( *source && *source != STX ) // Anfang suchen durch Suche nach STX im String |
34 | source++; // Zeiger solange erhöhen |
35 | |
36 | if( *source == STX ) { // STX im String gefunden -> STX ist der Anfang |
37 | source++; // da STX aber das Start-Steuerzeichen ist, erst das nächste Zeichen für den String verwenden |
38 | |
39 | while( *source && *source != '=' ) // String umkopieren, bis das Ende (hier =) gefunden wurde |
40 | *dest++ = *source++; |
41 | }
|
42 | *dest = '\0'; // String an der Stelle "=" noch mit \0 abschließen |
43 | strncpy( UARTVariable, Result, sizeof( Result ) ); // String in die dafür vorgesehen Variable schreiben |
44 | uart_puts(UARTVariable); // Variable ausgeben auf UART |
45 | |
46 | dest = Result; // Pointer wieder auf den Anfang von Result setzen |
47 | |
48 | source++; // source steht noch auf dem =, daher um eins erhöhen, um zu Beginn der Zahlenwerte zu kommen |
49 | while( *source && *source != ETX ) // String umkopieren, bis das Ende (hier ETX) gefunden wurde |
50 | *dest++ = *source++; |
51 | //}
|
52 | *dest = '\0'; // String an der Stelle "=" noch mit \0 abschließen |
53 | strncpy( UARTWert, Result, sizeof( Result ) ); |
54 | uart_puts(UARTWert); // Variablenwert ausgeben auf UART |
55 | |
56 | u8UARTEmpfangAuswerten = FALSE; // Flag setzen, dass String ausgewertet |
57 | s=0; // Empfangsstringstelle nullen für nächsten Empfang |
58 | }
|
Ich habe es auf der Schnittstelle mit HTerm getestet und bekomme immer VariableWert zurückgeliefert. Habe auch das eine und das andere auskommentiert, also es scheint so gut zu funktionieren. Auch das mehrmalige Senden hintereinander scheint kein Problem, alles wird wieder ordnungsgemäß genullt. Dennoch wäre es mir lieb, wenn nochmal jemand drüber schauen würde ggf. natürlich Verbesserungsvorschläge immer gerne gesehen. Und zu guter letzt, nochmal danke an alle Beteiligten und die, die es noch werden (wollen).
Hi! Na da liegst Du doch schon recht dicht dran. Ich hätte jetzt noch zwei Dinge gemacht: 1) Trennen was nicht zusammen gehört 2) Übersicht in den Empfänger 1) Die Entschlüsselung des empfangenen Strings ist nicht Aufgabe des Empfängers, sondern des Kommando-Interpreters Daher würde ich das Suchen und von Dingen zwischen STX und ETX einer Funktion überlassen, die nach Beendigung des Empfangs eines Telegramms aufgerufen wird. 2) Warum sschreibst Du nicht eine kleine Statemachine? Der erste State sucht nach einem STX, der zweite empfängt dann Daten bis ETX oder Puffer voll. Der Dritte zieht bei ETX die Prüfsumme und setzt das Flag zuer Weiterverarbeitung. Der Vierte wartet daruf, dass der String verarbeitet wurde und verwirft so lange weitere Daten. Der Fünfte behandelt die Fehler aus den anderen States... Zudem machst Du viel mit Puffern, die alle irgendwie begrenzt und damit vor Überlauf gefährdet sind. Empfange doch erst einmal alles in einen Puffer, der mit einfachen Maßnahmen gegen Überläufe gesichert ist: a) Du zählst mit und blockierst bei Puffer-Ende b) Du benutzt Pointer und rpüfst diese auf Schreiben > Puffer-Ende c) Du baust einen Ringbuffer und schreibst mit Zählern oder Pointern Wenn der gaanze String dann im Puffer ist, dann suchst Du nur mit Pointern nach den Punkten, die Du verarbeiten willst, kopierst aber nie was um. D.h. Du kannst mit *rx_start den String in deinem Puffer finden, der ist dann gleich identisch mit UartVariable. Dann setzt du einen weiteren Pointer *rx_var auf das Zeichen hinter = und hast UartWert. Das = ersetzt Du durch \0 und nun kannst Du per !strncmp( rx_start, Variable[i], strlen(Variable[i])) die Variable finden und dann per atoi(rx_var) den Wert umwandeln und verarbeiten. Wenn es super schnell gehen muss, dann machst Du eben zwei Puffer, der eine wird, wie zuvor beschrieben, dekodiert, der andere steht zum Empfang bereit. Mit zwei Flags schaltest Du hin und her, bzw. blockierst einen noch nicht verarbeiteten Puffer. Gruß uns frohes Fest Ulrich
Hallo Ulrich, mein Beitrag liegt nun schon ein paar Tage her, aber ich habe deine (späte) Antwort und deine Tipps noch gelesen. Im Moment fühle ich mich dadurch zunächst beim ersten lesen etwas "überfordert" als Anfänger oder Semi-Anfänger... Werde aber mal in der Weihnachtszeit das ganze mit etwas mehr Ruhe hier versuchen Schritt für Schritt umzusetzen. Gerade das Arbeiten mit Pointern und mit den String-Funktionen war bis dato Neuland für mich. Demnach bin ich da noch nicht so 100% fit. Danke und ebenfalls frohes Fest. Martin
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.