Forum: Mikrocontroller und Digitale Elektronik UART Auswertung


von Martin (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Dietrich L. (dietrichl)


Lesenswert?

- 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

von Martin (Gast)


Lesenswert?

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?

von Hmm (Gast)


Lesenswert?

>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.

von Martin (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Martin (Gast)


Lesenswert?

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
}

von Karl H. (kbuchegg)


Lesenswert?

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.

von Ulrich P. (uprinz)


Lesenswert?

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

von Martin (Gast)


Lesenswert?

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.

von Martin (Gast)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Martin (Gast)


Lesenswert?

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).

von Ulrich P. (uprinz)


Lesenswert?

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

von Martin (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.