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?
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
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:
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_tcmd_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...
:)
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
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.
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.
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.