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.
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
charUARTStringVar[20];
2
charUARTStringWert[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
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
charUARTString[20];
2
charResult[20];
3
char*source=UARTString;
4
char*dest=Result;
5
charUARTVariable[20];
6
charUARTWert[20];
7
uint8_tu8UARTEmpfangAuswerten=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((unsignedchar)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