C Kommandos empfangen und verarbeiten?!

Dieses Thema im Forum "Software" wurde erstellt von Janiiix3, 8. Januar 2018.

  1. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Moin,

    Aktuell arbeite ich empfangene Kommandos so ab..



    CodeBox C und 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.
     
  2. Dirk

    Dirk Administrator
    Mitarbeiter

    Registriert seit:
    28. Januar 2007
    Beiträge:
    4.198
    Zustimmungen:
    119
    Punkte für Erfolge:
    63
    Sprachen:
    C, Assembler, Pascal, C++, PHP, Java
    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.
     
  3. Mikro23

    Mikro23 Mitglied

    Registriert seit:
    2. Januar 2017
    Beiträge:
    274
    Zustimmungen:
    21
    Punkte für Erfolge:
    18
    Sprachen:
    C, Assembler
    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.
     
  4. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.009
    Zustimmungen:
    51
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Sicher...
    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..)...
     
    #4 LotadaC, 8. Januar 2018
    Zuletzt bearbeitet: 9. Januar 2018
  5. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    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.

    Das stimmt, da geht viel mehr Speicher und Rechenzeit drauf, jedoch ist es auch ein wenig flexibler bei der programmierung finde ich.

    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 werde ich wahrscheinlich auch in der "Relase" - Ware so machen.
     
  6. Dirk

    Dirk Administrator
    Mitarbeiter

    Registriert seit:
    28. Januar 2007
    Beiträge:
    4.198
    Zustimmungen:
    119
    Punkte für Erfolge:
    63
    Sprachen:
    C, Assembler, Pascal, C++, PHP, Java
    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.
     
  7. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.009
    Zustimmungen:
    51
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    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:
    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...
     
  8. Dirk

    Dirk Administrator
    Mitarbeiter

    Registriert seit:
    28. Januar 2007
    Beiträge:
    4.198
    Zustimmungen:
    119
    Punkte für Erfolge:
    63
    Sprachen:
    C, Assembler, Pascal, C++, PHP, Java
    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.
     
  9. TommyB

    TommyB Premium Benutzer

    Registriert seit:
    17. Mai 2010
    Beiträge:
    1.726
    Zustimmungen:
    59
    Punkte für Erfolge:
    48
    Sprachen:
    Assembler, LunaAVR, VB.Net, Python, C#
    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.
     
  10. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Es sind bis jetzt 10 Stk. Kannst du mal einen Pseudocode posten? Sprich wie du das mit dem Array und den Funktionspointern meinst.

    So in der Art werde ich es wohl auch letzendlich machen. Das geht deutlich schneller und ist sparsammer als die jetzige Lösung.
     
  11. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    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.
     
  12. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Kurze Frage.: Was meint man mit "Typ" ? Den Datentyp (Int, Char usw.) ?
     
  13. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Eingehende Daten.:

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

    Könnte doch schon so aussehen?
     
  14. Dirk

    Dirk Administrator
    Mitarbeiter

    Registriert seit:
    28. Januar 2007
    Beiträge:
    4.198
    Zustimmungen:
    119
    Punkte für Erfolge:
    63
    Sprachen:
    C, Assembler, Pascal, C++, PHP, Java
  15. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
  16. Dirk

    Dirk Administrator
    Mitarbeiter

    Registriert seit:
    28. Januar 2007
    Beiträge:
    4.198
    Zustimmungen:
    119
    Punkte für Erfolge:
    63
    Sprachen:
    C, Assembler, Pascal, C++, PHP, Java
    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.
     
  17. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Verstehe!
    Wäre das denn Sinnvoller als die ganzen "if - else" ?
     
  18. Janiiix3

    Janiiix3 Aktives Mitglied

    Registriert seit:
    28. September 2013
    Beiträge:
    1.282
    Zustimmungen:
    8
    Punkte für Erfolge:
    38
    Sprachen:
    C, C#
    Vorallem brauche ich für jede Funktion, die andere Parameter haben, einen eigenen Funktionspointer Typ ?



    CodeBox C und C++
    typedef void (*func_ptr_t)([B]parax, paray.. usw[/B]);
     
  19. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.009
    Zustimmungen:
    51
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    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.

    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.
     
    #19 LotadaC, 9. Januar 2018
    Zuletzt bearbeitet: 9. Januar 2018
  20. TommyB

    TommyB Premium Benutzer

    Registriert seit:
    17. Mai 2010
    Beiträge:
    1.726
    Zustimmungen:
    59
    Punkte für Erfolge:
    48
    Sprachen:
    Assembler, LunaAVR, VB.Net, Python, C#
    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

    Unsere immer weiter wachsende Community beschäftigt sich mit Themenbereichen rund um Mikrocontroller- und Kleinstrechnersysteme. Neben den Themen Design von Schaltungen, Layout und Software, beschäftigen wir uns auch mit der herkömmlichen Elektrotechnik.

    Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  • Coffee Time

    Unser makerconnect-Team arbeitet hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und unser eigener makerconnekt-Server regelmäßig gewartet wird. Wir nehmen das Thema Datensicherung und Datenschutz sehr ernst und sind hier sehr aktiv, auch sorgen wir uns darum, dass alles Drumherum stimmt!

    Dir gefällt das Forum und die Arbeit unseres Teams und du möchtest es unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft, unser Team freut sich auch über eine Spende für die Kaffeekasse :-)
    Vielen Dank!
    Dein makerconnect-Team

    Spende uns! (Paypal)
  1. Diese Seite verwendet Cookies, um Inhalte zu personalisieren und die Seite optimal für dich anzupassen. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies.
    Information ausblenden