C Kommandos empfangen und verarbeiten?!

Janiiix3

Aktives Mitglied
28. Sep. 2013
1.333
10
38
Hannover
Sprachen
  1. ANSI C
  2. C#
Moin,

Aktuell arbeite ich empfangene Kommandos so ab..



CodeBox C
  if(cmd.Search(hcBuff,"STATE?",0)==0)
  {
   uart1_puts("**State**\r\n");
   uart1_puts(convert.decHex16(app.result,hcBuff));
   uart1_puts("\r\n");
   app.result = 0x0000;
   RETURN_OK;
  } 
  if (cmd.Search(hcBuff,"INFO?",0)==0)
  {
   uart1_puts(hw.madeBy);
   uart1_puts(buildVer());
   uart1_puts("\r\n");
   RETURN_OK;
  } 

  usw..


/*
* Die Funktion Search sucht einfach nur aus einem Empfangspuffer
* den im Parameter 2 angegeben String.
*/

Bei nen paar Kommandos sieht das ja vill. noch ganz übersichtlich aus.
Werden es aber ein paar mehr, so verliert man mal ganz schnell die Übersicht (finde ich..).

Wie steht ihr zu dem Thema?
Wie würdet ihr das realisieren?
Gibt es andere Möglichkeiten?

Würde mich über Vorschläge sehr freuen.
 
Hallo Jan,

wenn du Kommandos als String überträgst, ist es recht aufwändig, da du hier ja ein Stringvergleich machen musst.
Falls möglich (wahrscheinlich gehts aber nicht), übertrage die Kommandos als ein Byte, so dass der Vergleich einfacher oder kürzer wird.

Soweit ist es ja recht übersichtlich.

Verwende auf jedenfalls "if-else if" Blöcke. Im Moment machst du immer einen Stringvergleich, auch wenn ggf. vorher schon ein Kommando erkannt und abgearbeitet wurde.

Je nachdem vieviele Sachen pro Kommando abzuarbeiten sind, könntest du diese Aufgaben in eigene Funktionen verlagern, so dass die if-else Blöcke übersichtlicher werden.
 
Wenn möglich, würde ich beim Sender schon bei der Eingabe parsen und in einen Byte-Wert umwandeln, um dann nur diesen zu übertragen. Beim Empfänger reicht dann eine switch-Anweisung.
 
Gibt es andere Möglichkeiten?
Sicher...
Wie würdet ihr das realisieren?
Kommt immer auf die Situation (Controllerbelastung/Timing/Komplexität/...) an.

Ich vereinbare mir gern ein Protokoll. Also zB erstmal, wie ein Telegramm auszusehen hat (Deins scheint zB mit einem CrLf zu enden.).
Komplexe Protokolle dann ggf mit einem speziellenTelegramm-AnfangsCharacter, Adressat, Absender,... Länge (alternativ zum Endcharacter (insbesondere wenn echte Bytes empfangen werden sollen)).
Ggf Timeout ab begin des Telegrammes.
Kommandobytes/words baue ich dann gern binär auf.
Derzeit habe ich für die ESRF einen Tiny25 als 2-Kanal-High-Speed-PWM eingesetzt. das ganze soll via (soft-) UART angesteuert werden.
Es gibt (bisher nur) drei Kommandos:
  • setze PWM-Frequenz auf xy ( 0|1|B1|B0|D3|D2|D1|D0 binär) - Das eigentliche Kommando steckt in den beiden höchstwertigen Bits. B repräsentiert die Basisfrequenz des Timers (genauer: B1 legt fest, ob die interne PLL auf den Timer gelegt werden soll(=1) oder der interne 8Mhz-Oszi verwendet wird (=0) -> Das Bit kann direkt in Bit PCKE des PLL-Controlregisters geschrieben werden, B0 legt fest, ob die PLL mit 32 oder 64 MHz laufen soll (LSM-Bit im selben Register), D3..D0 entsprechen dem Prescaler des Timers
  • setze Pulsverhältnis für Kanal A und/oder B auf xy (0|0|0|1|0|0|B|A binär) - Kommando ist das obere Nibble. Bit drei und zwei sind Platzhalter für die Kanäle C und D. Erwartet danach das eigentliche Parameterbyte (Timeout via Watchdog inklusive WDRF-Auswertung), welches auf die entsprechenden Kanäle (A=1, B=1) abgewendet wird.
  • übernehme derzeitigen Zustand ins Eeprom (0|0|0|0|0|0|0|1 binär) - natürlich erst nach Bestätigung (mit Timeout)
Die entsprechende State-Machine befindet sich dann direkt in der UART-Empfangsroutine (ISR), es wird quasi das Byte geschoben, und dabei (Stufenweise) auf das rausgeschobene Bit reagiert.

Dirk deutet was ähnliches an (if..then..else..if..)...
 
Zuletzt bearbeitet:
Verwende auf jedenfalls "if-else if" Blöcke. Im Moment machst du immer einen Stringvergleich, auch wenn ggf. vorher schon ein Kommando erkannt und abgearbeitet wurde.

Okay, dass war jetzt nur ein Ausschnitt. Habe in der Funktion die bei mir heißt "handleSlave" entsprechende "returns" verbaut. Wenn ein Kommando erkannt wurde, wird am ende dann wieder zurück gesprungen so das die anderen Anweisungen nicht mehr in betracht gezogen werden.

wenn du Kommandos als String überträgst, ist es recht aufwändig, da du hier ja ein Stringvergleich machen musst.
Das stimmt, da geht viel mehr Speicher und Rechenzeit drauf, jedoch ist es auch ein wenig flexibler bei der programmierung finde ich.

könntest du diese Aufgaben in eigene Funktionen verlagern
Das wollte ich ja eben nicht tun. Hinter jedem Funktionsaufruf, verbergen sich doch auch "Maschienenzyklen" oder etwa nicht? Okay bei größren Code könnte man das wirklich machen, bei so kleinen Anweisungen auch schon?.

Wenn möglich, würde ich beim Sender schon bei der Eingabe parsen und in einen Byte-Wert umwandeln, um dann nur diesen zu übertragen. Beim Empfänger reicht dann eine switch-Anweisung.
Das werde ich wahrscheinlich auch in der "Relase" - Ware so machen.
 
Das wollte ich ja eben nicht tun. Hinter jedem Funktionsaufruf, verbergen sich doch auch "Maschienenzyklen" oder etwa nicht? Okay bei größren Code könnte man das wirklich machen, bei so kleinen Anweisungen auch schon?.
Das musst du selber entscheiden ab wann es für dich sinnvoll ist, etwas in eine Funktion auszulagern. Ich würde sagen, bei so wenig Code lohnt es nicht, ausserdem nutzt man Funktionen/Routinen ja eher dann, wenn der Code öfter und von unterschiedlichen Positionen in deinem Projekt aufgerufen werden soll.

Bezüglich der Maschinenzyklen, dein Stringvergleich und übertragen der Kommandos macht ggf. mehr aus ;) Ausserdem ist Einsparen von Maschinenzyklen nicht immer sooo wichtig. Du kannst dir auch überlegen, wie oft pro Sekunde solch ein Funktionsaufruf (zB) vorkommt und was dein Programm in der restlichen Zeit sinnvolles macht. Manchmal werden dann ein paar Maschinenzyklen schnell seeeehr relativ ;)

Typische Kriterien für eine Programmstruktur ...
  • Speicherplatz (Programmspeicher und Arbeitsspeicher)
  • Ausführungszeit (Maschinenzyklen)
  • Übersichtlichkeit (Wartbarkeit)
  • Portierbarkeit (Verwendung in anderen Projekten und anderen Platformen)

Hier musst du immer entscheiden, was für dich wichtig ist.

Wenn möglich, würde ich das mit den Kommandostrings vereinfachen, wie ich schon mal geschrieben habe (Damit du dir die Kommandos besser zuordnen kannst, verwende aussagekräftige #define). Dann wäre eventuell auch ein switch-Anweisung (Mikro23) möglich oder auch verlagern der Funktionspointer in ein Flash Memory Array.
 
Hinter jedem Funktionsaufruf, verbergen sich doch auch "Maschienenzyklen" oder etwa nicht?
Jede Instruktion kostet Zeit. Aber das war Dir (sicher) klar. Die Frage ist. wieviel.

Ein Funktionsaufruf (Assembler bzw Maschinencode) ist eigentlich nur ein Sprung, bei dem nebenbei (automatisch) die Rücksprungadresse auf den Stack gelegt wird. Kostet etwa fünf Takte (ein "normaler" Sprung etwa 3-4). Ein komplexerer Funktionsaufruf muß aber auch irgendwelche Parameter übergeben (entweder direkt, oder als Adresse) - das kostet dann natürlich extra Zeit. Der Rücksprung (Return) kostet vier Takte (zuzüglich etwaiger Ergebnisübergabe. )

Auf der anderen Seite... Was ist denn eine If.. then .. else-Abfrage?

Da wird erst die Bedingung geprüft. Bei unwahr erfolgt ein (bedingter) Sprung zum else (Else-Codeblock). Bei wahr wird der wahr-Codeblock abgearbeitet, anschließend ein Sprung zum end if (bzw zur Klammer-Zu).
Switch ist ja letztlich nur 'ne if-then-else-Kette - also entsprechend viele Hops-Anweisungen.

Du hattest ja gefragt:
Gibt es andere Möglichkeiten?
Neben Sprüngen mit fester Sprungdistanz (fester Sprungadresse) gibt es auch Sprünge mit dynamischer Sprungdistanz/-Adresse. Man könnte(!) also auch einen Kommandowert (Zahl) in die Berechnung des Sprungzieles miteinfließen lassen. In Hochsprachen quasi nicht machbar, aber unter ASM kannst Du so mit ganz wenigen Takten dasselbe erreichen, wie mit'ner sehr komplexen switch-Anweisung...
 
Neben Sprüngen mit fester Sprungdistanz (fester Sprungadresse) gibt es auch Sprünge mit dynamischer Sprungdistanz/-Adresse. Man könnte(!) also auch einen Kommandowert (Zahl) in die Berechnung des Sprungzieles miteinfließen lassen. In Hochsprachen quasi nicht machbar, aber unter ASM kannst Du so mit ganz wenigen Takten dasselbe erreichen, wie mit'ner sehr komplexen switch-Anweisung...

Das ist schon machbar. Wie schon geschrieben, Funktionspointer in ein Array und diese dann über Kommando als Index aufrufen. Das ist interessant, wenn es viele Kommandos gibt, da man hier immer gleich lange benötigt, eine Funktion zu einem bestimmten Kommando aufzurufen. Wieviele Kommandos gibts?

Bei Assembler kann man dies sicher noch mit unterschiedlichen Lösungen machen (bestimmte Adressierungsarten, bestimmte Register) und hier und da noch Maschinenzyklen "rausholen".

Aber ob es jetzt nur darum geht us einzusparen, hmmm.

Da würde ich eher bei der aktuellen Lösung mit dem Kommandostring ansetzen und überlegen, ob es nicht mit einem Byte geht oder ausreicht. Die Übertragung (CAN, UART ...) und Auswertung sind kürzer.
 
Man könnte auch ein nicht-String-basierendes Protokoll verwenden, wie TLV.
Das Prinzip wird häufig verwendet. Falls sich noch wer an ICQ erinnert, das hat es im OSCAR Protokoll auch genutzt.
Ist ganz einfach (da Microcontroller gehe ich von Byte aus):
Byte 0 = Type, also der Typ der Nachricht
Byte 1 = Length, also die länge der folgenden Daten
Byte 2... = Value, also die eigentlichen Daten

Type 0x00 sollte man reservieren, dann kann der andere Rechner / Controller einfach ein paar 0x00 senden um die Verbindung zurück zu setzen.

Vorteil hierbei:
Sehr sparsam (der Controller wird nicht so sehr belastet wie bei Stringvergleichen).
Man kann wie LotadaC schon sagte mit Sprungtabellen arbeiten.
Der Controller kann Pakete gleich ablehnen die zu groß sind.

Nachteil:
Binäre Verbindung, also mit Putty oder ähnlichen Terminals wird's schwierig.
 
Wieviele Kommandos gibts?
Es sind bis jetzt 10 Stk. Kannst du mal einen Pseudocode posten? Sprich wie du das mit dem Array und den Funktionspointern meinst.

Byte 0 = Type, also der Typ der Nachricht
Byte 1 = Length, also die länge der folgenden Daten
Byte 2... = Value, also die eigentlichen Daten
So in der Art werde ich es wohl auch letzendlich machen. Das geht deutlich schneller und ist sparsammer als die jetzige Lösung.
 
Byte 1 = Length, also die länge der folgenden Daten
Das würde ja letzendlich bedeuten, dass ich hier mit einem dynamischen Speicher arbeiten müsste? Oder ich lege halt einen Speicherbereich fest bsp. 30 Bytes. Man könnte dieses Byte auch weglassen und die angekommenen Bytes zählen bis das "Ende Zeichen" im Buffer liegt und das als Anzahl auswerten.
 
Eingehende Daten.:

Start Zeichen | Datentyp | CRC_8[Option] | Anzahl Bytes[0-30] | Bytes[0-30] | Ende Zeichen

Könnte doch schon so aussehen?
 
Was ist der Sinn dahinter? Verstehe ich noch nicht ganz..

Du hast ein Array mit deinen Funktionen (Routinen), welche bei bestimmten Kommandos ausgeführt werden sollen.

Du kannst eine Funktion so ausführen:

uint8_t commando;
commando = 0;
cli_func_list[commando](); // execute

Das wäre die erste Funktion in der Liste, da Index 0 ist.

Wenn dein empfangenes Kommando einen Wert 0 bis n-1 hat, bei n Kommandos und n Funktionseinträgen in dem Array,
kannst du sofort eine bestimmte Funktion ausführen, indem du den Wert des Kommandos als Index-Wert nutzt.

Vorteil ist, dass du hier keine if-Abfragen oder switch-case Blöcke durchlaufen musst, sondern sofort die gewünschte Funktion ausführen kannst.
 
Verstehe!
Wäre das denn Sinnvoller als die ganzen "if - else" ?
 
Vorallem brauche ich für jede Funktion, die andere Parameter haben, einen eigenen Funktionspointer Typ ?



CodeBox C
typedef void (*func_ptr_t)([B]parax, paray.. usw[/B]);
 
Wäre das denn Sinnvoller als die ganzen "if - else" ?
Das kommt auf die Anzahl der Kommandos (und deren Struktur) an. Salopp gesagt wird von der Adresse (im Flash) "Kommando" eine weitere Adresse (im Flash) geladen, und diese als Sprungziel eines Funktionsaufrufes genutzt (ICALL). Bei einer If.. then.. else-Kaskade (oder eben auch switch..case hast Du hingegen für jeden Fall 'nen Vergleich, und 'nen Funktions-Call. Die Anzahl der Takte variiert hier, je nachdem, nach wievielen "if"s der passende Vergleich gefunden wurde.
Die Tabellenvariante ist von der Zeit her konstant - folglich also ab einer bestimmten anzahl Kommandos schneller.

Funktionspointer in ein Array und diese dann über Kommando als Index aufrufen
Ich meinte, man kann auch das Array weglassen, und das Kommando selbst als Sprungziel nutzen. Erfordert natürlich noch mehr Planung bei der Strukturierung des Programmes im Flash und den Kommandobytes.
 
Zuletzt bearbeitet:
Type: Definiert was für ein Typ an Daten folgt, quasi der Befehl. Als Beispiel:
0x00: Datenspeicher resetten
0x01: Display anschalten (keine Daten notwendig)
0x02: Daten auf das Display schreiben (Datentyp: ASCII String)
0x03: Display ausschalten (keine Daten notwendig)

Sagen wir es ist das Protokoll für ein LCD.
Für ein Hallo würde also sowas gesendet werden:
0x01 0x00 // Display an
0x02 0x05 0x48 0x61 0x6C 0x6C 0x6F // Schreibe "Hallo"
(warte 10 Sekunden)
0x03 0x00 // Display aus

Type Length Value halt.

Bei Neustart (Power On) des Senders so viele 0x00 senden wie groß der Puffer ist (optional). Der Empfänger setzt bei Type 0x00 den Pointer wieder auf den Anfang des Buffers zurück. Somit hast du keine Probleme sollte mal mitten im Datenpaket die Verbindung abreißen und du willst dich später erneut verbinden. Ist aber nur zur Sicherheit.

Dein Empfangspuffer muss natürlich groß genug sein für das Datenpaket.
Aber du kannst ja gleich abbrechen wenn mehr Daten gesendet werden als du speichern kannst.

Die Datentypen kannst du denn ja je nach Type selbst festlegen.
0x04: Horizontale Position (Int16)
0x05: Vertikale Position (Int16)
...
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)