Forum: Mikrocontroller und Digitale Elektronik UART -> Erfahrungen zur Auswertung von Strings gesucht


von Ferdinand (Gast)


Lesenswert?

Tag zusammen!

Ich habe mir eine Verbindung PC <-> uC gebastelt und möchte nun gerne 
Kommandos vom PC im Microcontroller auswerten und bestimmte Aktionen 
starten.

Daher hätte ich mal vorab, um nicht jetzt gleich falsch anzufangen, eure 
Erfahrungen damit - also genauer gesagt suche ich ein Beispiel, wie ihr 
das gelöst habt.

Meine Vorgehensweise wäre nun folgende:

Ich habe einen Empfangspuffer, welcher die ankommenden Zeichen speichert 
und jedes Zeichen direkt auf ein '\r' (RETURN) abfragt. Gleichzeitig 
läuft ein Zeichenzähler mit - wird ein RETURN erkannt, so wird erst auf 
den Zeichenzähler geguckt, ob eine Mindestmenge eingegangen ist - das 
wäre dann mehr dafür da, dass nicht jedesmal der Textstring durchsucht 
werden muss.

Sind theoretisch genug Zeichen da, so wird der String von Anfang an mit 
einem vorgegebenen String verglichen und auf Gleichheit überprüft. Liegt 
Gleichheit vor, so wird etwas ausgeführt.

Mit jedem Tastendruck wird zugleich ein Timer gestartet, welcher die 
Eingabe nach einer Zeit verfallen lässt, sollte kein ENTER gedrückt 
werden.


Ist mein Vorhaben so in Ordnung? Hat jemand evtl. ein Code-Beispiel oder 
ein paar Erläuterungen dazu, wie man es ggf. besser machen kann?

von Volker Z. (vza)


Lesenswert?

Ferdinand schrieb:
> Ich habe einen Empfangspuffer, welcher die ankommenden Zeichen speichert
> und jedes Zeichen direkt auf ein '\r' (RETURN) abfragt.

Gut

> Gleichzeitig
> läuft ein Zeichenzähler mit - wird ein RETURN erkannt, so wird erst auf
> den Zeichenzähler geguckt, ob eine Mindestmenge eingegangen ist - das
> wäre dann mehr dafür da, dass nicht jedesmal der Textstring durchsucht
> werden muss.

Hast du mehr als 1000 Kommandos?
Der µC ist schneller als du denkst. Da ich ein Freund von kurzen 
Kommandos bin (s = Status, w = Write, r = read etc.) finde ich die 
Einschränkung eher störend. Kurze Kommandos beschleunigen nicht nur das 
Eintippen, die Auswertung, sondern auch die Übertragung.

Eine Überprüfung ob nicht mehr Zeichen angekommen sind, als der Puffer 
groß ist, hast Du vergessen. Dies ist ein muß.


> Sind theoretisch genug Zeichen da, so wird der String von Anfang an mit
> einem vorgegebenen String verglichen und auf Gleichheit überprüft. Liegt
> Gleichheit vor, so wird etwas ausgeführt.
Ist so Üblich.

> Mit jedem Tastendruck wird zugleich ein Timer gestartet, welcher die
> Eingabe nach einer Zeit verfallen lässt, sollte kein ENTER gedrückt
> werden.

Soll es ein Maschinen-Maschinen-Protokoll werden so ist das sinnvol. 
Eine Prüfsumme ist aber mindestens genauso wichtig.

Ist es eine Mensch-Maschinen-Schnittstelle (Terminal-Programm) so ist 
beides nicht nötig. Hier reicht ein Befehlsinterpreter der falsche 
Kommandos mit einer Fehlermeldung quittiert.

Volker

von Tom M. (tomm) Benutzerseite


Lesenswert?

Du findest CLI Beispiele zu Hauf im Web, hast du schon in die 
Codesammlung hier geschaut?

Habe kürzlich eine CLI implementiert, die Ein-Zeichen-Befehle erkennt 
und ausführt. Dazu verwende ich zwei Arrays:
1
char cli_cmds[] =                {'t'        ,'c'       ,'u'        ,'d'       ,'p'       ,'\0' };
2
int8_t (*cli_funcs[])(char*) =   { cmd_toggle, cmd_clear, cmd_upload, cmd_debug, cmd_print };

Die cmd_* Funktionen liefern int8_t zurück (error code), der übergebene 
Pointer zeigt jeweils auf die Eingabezeile. So kann die cmd-Funktion 
allfällige Argumente auswerten.
1
int8_t cmd_toggle(char *cmdline);

Für komplexere Kommandos könntest du einen Parser schreiben, 
Mustererkennung, strtok(), eine FSM etc. verwenden. Für ein 
"Taschenrechner"-Projekt würde der Ansatz sicher ganz anders aussehen... 
:)

von noips (Gast)


Lesenswert?

Habe das schon mal gemacht und auch hier nach Tipps gefragt. Der Thread, 
der daraus entstand, könnte interessant für dich sein:

Beitrag "Terminal-Befehle aus mehreren Zeichen im Controller erkennen, wie am besten?"

von oldmax (Gast)


Lesenswert?

Hi
Da ich mit Assembler arbeite, hier mal ein Tip, wie ich vorgehe ohne 
dich jetzt groß mit Code zu quälen:
Die Empfangsroutine im Controller ist eine ISR, also Interrupt. Das 
empfangene Zeichen geht auf einen Ringspeicher, das ist einfach ein 
Speicherbereich von ca. 10- 20 Bytes. Mit einem Doppelregister wird der 
Anfang des Bereiches fixiert und ein Schreibzeiger zuaddiert. Dort wird 
das empfangene Zeichen eingetragen. Danach wird der Schreibzeiger 
erhöht, die Grenze des Puffers geprüft und der Schreibzeiger evtl. 
zurückgesetzt. Nun ist zwischen Schreibzeiger und einem Lesezeiger ein 
Unterschied, der im Hauptprogramm erkannt wird. Es folgt das Lesen, auch 
mit einem Doppelregister und einem Lesezeiger, der auf die Adresse 
zuaddiert wird. So wird das aktuelle Zeichen gelesen und anschließend 
der Lesezeiger, wie auch der Schreibzeiger vorher, nachgeführt.
Nun zur Auswertung der Zeichen:
Klar kann man ganze Strings schicken, aber es gibt ja auch die 
Möglichkeit der Groß- und Kleinschrift. So kann "A" bedeuten "Relais A 
ein und "a" Relais A aus.
Willst du das Absichern, also Fehler erkennen und evtl. unerwünschte 
Schaltvorgänge verhindern, so kannst du auch einen Rahmen verwenden und 
eine Chcksumme bilden, die als letztes Byte mitgeschickt wird. Also 
angenommen, ein Befehl für den µC sind 4 Bytes.
1.Zeichen : Anfangserkennung (z.B."/", "-", etc)
2.+3.  sind Nutzdaten ( z.B. "A1", "b1" )
das 4. ist eine Checksumme nach einer math. Regel. Einfach ist z.B. die 
3 Nutzzeichen XOR zu verknüpfen und das Ergebnis als 4. Byte 
mitschicken.
Beispiel:
     1. Zeichen   11100111
xor  2.Zeichen    01101101
                  --------
                  10001010
xor  3. Zeichen   00111101
                  --------
Checksumme:       10110111


Nu kannst du im Controller gleiches ablaufen lassen und wenn das Ergenis 
der Checksumme passt, sollte die Übertragung fehlerfrei sein. Auch bei 
unterschiedlich langen Strings geht das. du mußt dann allerdings die 
Länge mitsenden, logischerweise im ersten Byte. Aber das ist dann schon 
ziemlich kompliziert....
Gruß oldmax

von Ralf (Gast)


Lesenswert?

Aus dem von noips genannten Thread unbedingt beachten:
> Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
> Datum: 24.02.2010 14:30

> Jeder String in C hat IMMER ein \0 Zeichen am Ende! Daran wird das Ende
> eines Strings erkannt. Das ist C-Konvention und du solltest dich
> unbedingt daran halten und nicht dein eigenes Süppchen kochen.

Also ich würde das '\r' nicht senden, sondern stattdessen gleich '\0'. 
Spart ein Byte und du kannst die Stringfunktionen aus der C-Bibliothek 
zum vergleichen nutzen.

von cskulkw (Gast)


Lesenswert?

Hallo Ferninand,

was Du programmieren willst, ist bei mir der Befehlsinterpreter 
geworden.

Kurze Befehle sind in Ordnung solange man nur eine handvoll benötigt.

Du solltest Dir noch Gedanken machen, wie Du vom PC zum Controler 
mitzuteilende Parameter codieren willst.

Ich habe mich bei meinem Vorhaben grob an den AT(+C)-Befehlen für Modem 
und GSM-Modulen orientiert.

Die zu erkennenenden Strings habe ich in den Flash abgelegt, damit nicht 
unnötigt viel RAM verbraucht wird. Solltest Du den GNU-Compiler für den 
AVR benutzen, dann solltest Du Dir die pgmspace.h anschauen und Dich mit 
diesen Funktionen vertraut machen.

Bei den meisten anderen Compilern reicht ein const vor der Definition. 
Dann legt der Compiler die Strings im Flash ab.

Im ersten Schritt habe ich die Befehlsposition ermittelt. Im 
nachfolgenden einen möglichen Parameter separiert.

Beispiel:
SetDate = 01.01.2011\r
^       ^ ^
|       | |
|       | zu interpretierende Daten.
|       Parameterankündigung
Kommando

Sobald die Identifizierung abgeschlossen ist, folgt die Frage, was man 
damit macht.

Im ersten Anlauf habe in im Befehlsinterpreter (switch-case-Konstrukt) 
die Funktionen der anderen Komponenten aufgerufen. Das hat funktioniert. 
Jedoch beim Portieren in anderen Projekte habe ich viel Zeit verloren, 
bis ich alles Notwendige angepaßt hatte, weil meist hier viel zu ändern 
war. (Frust vorprogrammiert)

Mittlerweile bin ich dazu übergegangen die sogenannte späte Bindung zu 
benutzen. Ich habe im Befehlsinterpreter (Komponente in Form eines C und 
H- Files) keine Strings mehr gespeichert, die eigentlich Teil anderer 
Komponenten - meinetwegen kannst Du sie auch Module nennen - sind.

Das oben beschriebene SetDate gehört bei mir zur Komponente 
Realtime-Clock.

Diese wiederum enthält nun in ihrer Datei den String "SetDate". Bei der 
Initialisierung registriere ich diesen String im Befehlsinterpreter. D. 
h. ich übergebe einen Pointer (also die Adresse) im Flash, wo dieser 
String steht.
Im Befehlsinterpreter (nachfolgend BI genannt) habe ich nun ein Array 
von Pointer (eine RAM-Variable - sonst kann sie zur Laufzeit nicht 
verändert werden) auf Flash-Konstanten. Im gleichen Zug wird in einem 
weiteren Array (ebenfalls RAM) des BIs die Funktionsadresse gespeichert, 
die im Fall der erfolgreichen Identifikation ausgeführt werden soll.

Wenn der String SetDate erkannt (simple for-schleife) wird, dann habe 
ich eine Position im String-Pointer-Array, die mit der Postion des 
Funktionspointerarray übereinstimmt.


#define TABMAX 21
/* Instructiontable for Communication issues */
PGM_P InstTab[TABMAX]= {0,0,0,0,0,
                        0,0,0,0,0,
                        0,0,0,0,0,
                        0,0,0,0,0,
                        0};

/* list of Callbackfcts of componments with reaction. */
void (*ptr_CallBack_FctList[TABMAX]) (int param_Item);
So wird ein Funktionspointer aufgerufen.
(*ptrISR_CallBack_FctList[ Iterator ]) (Parameter);


Der Vorteil ist, wenn der BI einmal fertig programmiert ist, er nicht 
wieder angefasst werden muß. Falls die Anzahl der Befehle erhöht werden 
muß, verändert man nur das Define. Der Rest funktioniert, wie erwartet.

Außerdem könnte während der Laufzeit durch Pointermanipulation der 
Programmlauf verändert werden. Das braucht nicht jeder. Aber es 
funktioniert.

Diese Technik verwende ich nun auch, wenn eine UART einen 
Empfangsinterrupt auslöst. Ich betreibe eine grafischen Display von EA 
über UART. Früher habe ich im UART-Treiber die Daten zwischengespeichert 
und dann (später) umkopiert. Das kostete unnötig RAM und war wenig 
performant. Jetzt ruft die ISR über einen Funktionspointer direkt die 
ihr zugewiesene Funktion auf und hängt als Parameter den Inhalt der UDR 
an. Der UART-Komponente ist es vollkommen gleichgültig, was mit dem 
empfangenen Daten passiert, darum kümmert sich jetzt die über einen 
Funktionspointer aufgerufene Funktion des Displays.

Mittlerweile habe ich durch dieser strukturelle Methode wieder mehr 
Überblick, wer wann was bearbeitet.

Meine wesentliche Motivation, das derart kompliziert zu lösen, war, die 
Anforderung, so viele Aufgaben wie möglich quasi parallel im Controler 
zu bearbeiten. Ich betreibe einen CAN-Controler, eine grafischer Dispay, 
diverse Sensoren, die PC-Kommunikation, etc.  parallel. Das geht nur mit 
strikt aufgeräumten Komponenten.

Vielleicht kommt mein Vorschlag als dickes Brett rüber. Aber das sind 
meine Erfahrungen. Sie zu nutzen, bleibt jedem selbst überlassen.

Viel Spaß und Erfolg beim Programmieren.

von Ferdinand (Gast)


Lesenswert?

Volker Zabe schrieb:
> Eine Überprüfung ob nicht mehr Zeichen angekommen sind, als der Puffer
> groß ist, hast Du vergessen. Dies ist ein muß.

Oh...vergessen zu schreiben - die Abfrage ist auch drin, klar!

Danke euch erstmal für die Antworten - es ist eine 
Mensch-Maschine-Schnittstelle: Hyper-Terminal -> uC

noips schrieb:
> Habe das schon mal gemacht und auch hier nach Tipps gefragt. Der Thread,
> der daraus entstand, könnte interessant für dich sein:

Den werde ich mir jetzt erstmal durchgucken.

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.