Einfaches binäres Protokoll



  • Hallo zusammen,

    eine Kollege von mir und ich denken gerade über ein neues Protokoll für die Übertragung von Daten (Zahlen) von einem Mikrocontroller an einen PC aus.

    Bisher haben wir alle Zahlen char-kodiert und übertragen.
    Typen und Anzahl der übertragenen Zahlen waren festgesetzt und nicht mitübertragen.
    Das hat auch super geklappt.
    Inzwischen merken wir allerdings, dass die Übertragungsrate langsam an eine Grenze stößt und deshalb wollen wir jetzt binär kodieren.
    Außerdem wollen wir direkt noch ein paar zusätzliche informationen über anzahl und typen der Zahlen verschicken.
    Es werden kontinuierlich einige Zahlen nach dem selben Schema rausgeschickt.

    Unser bisheriger Entwurf ist folgendes Schema:

    2003 3 iff XXXX XXXX XXXX 2058

    2003 und 2059: öffnen/schließen einen Block von Zahlen.
    Die Zahlen 2003 und 2058 sind willkürlich gewählt und werden je mit 4 byte kodiert.
    Problem: Falls normale Daten diesen Wert haben, hat man ein Problem.

    3: Anzahl der Zahlen die verschickt werden.
    iff: chars die für die typen stehen.
    i steht z.B. für int, f für float und dann machen wir vll noch u für unsigned int oder so.

    XXXX XXXX XXXX: Die eigentlichen Daten, der Typ entspricht dem des entsprechenden Chars.

    Das größte Problem ist das, was oben schon genannt ist:
    Falls richtige Daten zufällig den Wert 2003 oder 2058 hat (wir können auch andere Zahlen nehmen, wenn euch was sinnvolleres einfällt), dann wird das als anfang/ende eines blocks erkannt.

    Hat jemand Verbesserungsvorschläge?



  • Ich weiß nicht was da noch drumherum ist, aber wenn das wirklich alle Daten sind die übertragen werden sollen:
    iXXXX fXXXX fXXXX <- fertig. Wenn ihr aber wirklich so viele Daten habt, könntet ihr euch mal leichte Komprimierungen anschauen, das kann u.U. schon sehr nützlich sein.



  • Das Problem ist, dass jede Zahl innerhalb eines Blocks je nach Position für etwas bestimmtes steht, deswegen muss man unbedingt wissen, wo ein Block anfängt und wo einer aufhört.

    Wir vermuten, dass der wechsel nach Binär schon genügen wird und ich vermute, dass das wesentlich einfacher ist, als zu komprimieren.

    Beim Komprimieren wird es sosnt vermutlich auch schwierig, wenn man "mitten drin" einsteigt, was bei uns durch senden der anfangs und endcodes für die Blöcke kein Problem ist, weil man einfach auf den Anfangscode wartet.

    Endianess ist mir übrigens bekannt, das bekommen wir geregelt.

    Hat sonst noch jemand eine Idee, wie wir das Problem mit den Start und Endcodes lösen können?



  • Schickt sie doch einfach blockweise mit einem Header?

    struct Header
    {
      std::uint16_t NumElements;
      std::uint16_t ElementType;
    };
    

    Dann wird ein Buffer alloziert, der Platz für NumElements * Größe von ElementType + sizeof(Header) hat und der wird gesendet.

    Der Empfänger liest sizeof(Header) Bytes ein, und liest dann weiter bis er alle Elemente hat. Fertig.

    Ab dann erwartet er den nächsten Header etc

    Etwas PseudoCode:

    // Sender
    int data[5] = { 0, 1, 2, 3, 4 };
    Header header = { 5, TYPE_INT };
    Send(header);
    Send(data);
    
    // Empfänger
    Header header;
    Receive(header);
    buffer = Allocate(header.NumElements * TypeSize(header.ElementType));
    Receive(buffer);
    


  • Q schrieb:

    Das Problem ist, dass jede Zahl innerhalb eines Blocks je nach Position für etwas bestimmtes steht, deswegen muss man unbedingt wissen, wo ein Block anfängt und wo einer aufhört.

    a iXXXX fXXXX fXXXX e

    a und e könnten theoretisch zwar als Zahl vorkommen, aber ihr lest dann quasi so:

    read(b, sizeof(char))
    switch (b)
    {
    case 'a':
      beginBlock(); 
      ...
    }
    

    Jedem Buchstaben könnt ihr dann ein "Steuerzeichen" zuweisen.



  • 1. Sind die XXXX-Zahlen immer 4 Bytes groß?
    2. Wozu braucht ihr Anfangs- und End-Blöcke (vor allem wozu den End-Block), wenn die Anzahl der Bytes für die Zahlen schon vorher durchgegeben wurde? (Entspricht im Prinzip der Anmerkung von Ethon)



  • wxSkip schrieb:

    Wozu braucht ihr Anfangs- und End-Blöcke (vor allem wozu den End-Block), wenn die Anzahl der Bytes für die Zahlen schon vorher durchgegeben wurde? (Entspricht im Prinzip der Anmerkung von Ethon)

    Wenn man zwei Blöcke hintereinander hat, wirds knifflig. 😉

    @Ethon
    So kann man in einem Block ja nur Daten des gleichen Typs verschicken.



  • cooky451 schrieb:

    wxSkip schrieb:

    Wozu braucht ihr Anfangs- und End-Blöcke (vor allem wozu den End-Block), wenn die Anzahl der Bytes für die Zahlen schon vorher durchgegeben wurde? (Entspricht im Prinzip der Anmerkung von Ethon)

    Wenn man zwei Blöcke hintereinander hat, wirds knifflig. 😉

    Warum? Man weiß, wann der erste zu Ende ist und das nächste Byte gibt dann die Größe des zweiten Blocks an.



  • Das Problem ist, der Microkontroller sendet permanent über die serielle Schnittstelle Daten.

    Man kann also zu jedem Zeitpunkt das Kabel einstecken und Daten empfangen.
    Woher weiß man dann, wann der header losgeht, ohne einen speziellen code dafür zu benutzen, was die schon erwähnten Probleme bereitet?

    Edit:

    cooky451 schrieb:

    Jedem Buchstaben könnt ihr dann ein "Steuerzeichen" zuweisen.

    Das macht das Problem doch noch schlimmer, wenn wir noch mehr Steuerzeichen verwenden.

    wxSkip schrieb:

    1. Sind die XXXX-Zahlen immer 4 Bytes groß?
    2. Wozu braucht ihr Anfangs- und End-Blöcke (vor allem wozu den End-Block), wenn die Anzahl der Bytes für die Zahlen schon vorher durchgegeben wurde? (Entspricht im Prinzip der Anmerkung von Ethon)

    1.Nicht unbedingt, aber fürs erste ja. Wir schicken ja Typinformationen mit chars, was die Größe beinhaltet.
    2. Siehe oben.

    Früher hatten wir als Steuerzeichen \n dafür, dass ein neuer Block anfängt und das gab keine Kollisionen, da die Zahlen char-kodiert sind und deswegen niemals \n enthalten konnten.
    Wenn wir jetzt binär kodieren, können die Zahlen aber leider JEDES bytemuster haben, was die Verwendung von Steuerzeichen problematisch macht, ich sehe aber keine wirkliche Alternative.



  • cooky451 schrieb:

    wxSkip schrieb:

    Wozu braucht ihr Anfangs- und End-Blöcke (vor allem wozu den End-Block), wenn die Anzahl der Bytes für die Zahlen schon vorher durchgegeben wurde? (Entspricht im Prinzip der Anmerkung von Ethon)

    Wenn man zwei Blöcke hintereinander hat, wirds knifflig. 😉

    @Ethon
    So kann man in einem Block ja nur Daten des gleichen Typs verschicken.

    Gut, ob das problematisch ist hängt aber vom Typ der Daten ab.
    Floats kann man ja zb problemlos als Integer codieren (was imo sogar anzuraten wäre, aufgrund von Differenzen in den floating-point Codierungen verschiedener CPUs).

    Woher weiß man dann, wann der header losgeht, ohne einen speziellen code dafür zu benutzen, was die schon erwähnten Probleme bereitet?

    Das ist ein Argument. Dann würde ich mit einer möglichst großen MagicNumber arbeiten. Ein

    0xDEADDEADDEADDEAD

    sollte da schon sehr zuverlässig sein. 😉



  • Q schrieb:

    Das macht das Problem doch noch schlimmer, wenn wir noch mehr Steuerzeichen verwenden.

    Hä? Hast du dir eben überhaupt meinen ganzen Beitrag durchgelesen? Ich sehe kein Problem mehr, das funktioniert so.



  • Hä? Hast du dir eben überhaupt meinen ganzen Beitrag durchgelesen? Ich sehe kein Problem mehr, das funktioniert so.

    cooky451 schrieb:

    read(b, sizeof(char))
    switch (b)
    {
    case 'a':
      beginBlock(); 
      ...
    }
    

    Jedem Buchstaben könnt ihr dann ein "Steuerzeichen" zuweisen.

    Das Problem ist, der Microkontroller sendet permanent über die serielle Schnittstelle Daten.

    Man kann also zu jedem Zeitpunkt das Kabel einstecken und Daten empfangen.
    Woher weiß man dann, wann der header losgeht, ohne einen speziellen code dafür zu benutzen, was die schon erwähnten Probleme bereitet?

    Funktioniert das dann immer noch?
    Wenn ja, dann erklär das bitte nochmal.
    Ich denke nämlich das es das nicht tut, wenn man zu jedem beliebigen Zeitpunkt einstecken kann.
    Dann kann es nämlich sein, dass gerade eine Zahl übertragen wird. Das Bytemuster der Zahl enthält dann Steuerzeichen, die fälschlicherweise als Steuerzeichen interpretiert werden und dann hat man den Salat.

    Ethon schrieb:

    Das ist ein Argument. Dann würde ich mit einer möglichst großen MagicNumber arbeiten. Ein

    0xDEADDEADDEADDEAD

    sollte da schon sehr zuverlässig sein. 😉

    Jo sowas werd ich dann auch nehmen, falls wir keine anderen Methoden finden, ist bestimmt sicherer als 2003 oder sowas^^



  • Q schrieb:

    Funktioniert das dann immer noch?

    Klar.

    readBlock()
    {
      TYPE type = 0;
      while (type != 'e')
      {
        switch ((type = readType()))
        {
        case 'e':
          break;
        case 'f':
          readFloat();
          break;
        case 'i':
          readInt();
          break;
        default:
          throw "ALLES_KAPUTT";
          break;
        }
      }
    }
    

    Drüber liegt natürlich eine Funktion, die dann irgendwelche anderen Daten liest, bis ein 'b' kommt, dann wird diese Funktion aufgerufen. Falls eh nur solche Blocks empfangen werden, kann man das natürlich alles in eine Funktion packen etc., ist nur pseudo-Code. Allerdings sollte klar werden, dass das Programm zu jeder Zeit in einem klar definierten Zustand ist und immer weiß, worauf es warten muss bzw. was als nächstes kommt. Ich sehe kein Problem, wann die Daten übertragen werden, oder wie zerstückelt die sind, spielt eigentlich keine Rolle.



  • Ich verstehe leider nicht, warum das funktioniert.

    Ich rufe die funktion auf, der Microkontroller ist gerade dabei, eine Zahl zu senden, kein Steuercode.

    Das char, dass du einlist ist etwas, was weder 'e', noch 'f' noch 'i' ist.
    Und schon fliegt dir die exception um die Ohren.

    Vielleicht habe ich das nicht klar genug gesagt.
    Der Microkontroller sendet IMMER, auch wenn gar kein Computer angeschlossen ist.
    Zu jedem beliebigen Zeitpunkt kannst du den Stecker einstecken und Daten empfangen, aber du weißt erstmal nicht, ob das, was gerade gesendet wird eine Zahl oder ein Steuerzeichen ist.

    Falls das dennoch funktioniert, bitte ich nochmals um eine Erklärung.

    Vielen Dank für deine Hilfe.



  • Q schrieb:

    ..

    Ich dachte bis jetzt ja auch, dass man selbst etwas an den Microcontroller sendet. Natürlich dürfen keine Daten innerhalb eines Blocks verloren gehen. Das kannst du mit keinem Protokoll ausbügeln. Das Protokoll ist ja gerade dafür da, um Regeln für die Übertragung aufzustellen. So wird ja auch gar nicht garantiert, dass du überhaupt Anfangs/End Zeichen erhälst. Wie stellst du dir das vor? Wie hat das denn mit Text geklappt bis jetzt?



  • Mit text wars so, dass '\n' bedeutet, dass ein neuer block kommt, ' '(leerzeichen) heißt neue zahl und dazwischen sind die zahlen asci kodiert.
    Da die Zahlen nur aus 0-9 Punkt und e^ oder sowas bestehen und kein '\n' oder ' '(leerzeichen) enthalten, war das kein Problem.



  • Q schrieb:

    Mit text wars so, dass '\n' bedeutet, dass ein neuer block kommt, ' '(leerzeichen) heißt neue zahl und dazwischen sind die zahlen asci kodiert.
    Da die Zahlen nur aus 0-9 Punkt und e^ oder sowas bestehen und kein '\n' oder ' '(leerzeichen) enthalten, war das kein Problem.

    Aha? Microcontroler sendet die Zahl "5533", Kabel wird nach "55" eingesteckt. Oups, komplett falsche Zahl. :p

    Edit: Und wie habt ihr das mit Blockanfang gemacht? Eben war es doch noch so wichtig, an welcher Stelle die Zahl im Block steht.



  • cooky451 schrieb:

    Q schrieb:

    Mit text wars so, dass '\n' bedeutet, dass ein neuer block kommt, ' '(leerzeichen) heißt neue zahl und dazwischen sind die zahlen asci kodiert.
    Da die Zahlen nur aus 0-9 Punkt und e^ oder sowas bestehen und kein '\n' oder ' '(leerzeichen) enthalten, war das kein Problem.

    Aha? Microcontroler sendet die Zahl "5533", Kabel wird nach "55" eingesteckt. Oups, komplett falsche Zahl. :p

    Edit: Und wie habt ihr das mit Blockanfang gemacht? Eben war es doch noch so wichtig, an welcher Stelle die Zahl im Block steht.

    Na, da hätten sie doch einfach alles bis zum ersten newline verwerfen können.



  • Ein Block fängt nach \n an.

    Das mit 5533 ist auch kein Problem.
    Solange bis das erste mal \n kommt wird alles ignoriert, also kann da nichts passieren.



  • Q schrieb:

    Ein Block fängt nach \n an.

    Das mit 5533 ist auch kein Problem.
    Solange bis das erste mal \n kommt wird alles ignoriert, also kann da nichts passieren.

    Ignorieren ist also erlaubt, ok. Dann braucht man also irgendwelche Steuerzeichen, die nicht in Daten vorkommen können. "Echte" binäre Übertragung fällt somit also weg, ihr müsst euch ein Kodierverfahren einfallen lassen. Base64 würde sich z.B. anbieten. Dann könnt ihr Steuerzeichen von Daten eindeutig unterscheiden.


Log in to reply