UART und Datenpakete



  • Dabei musst du halt beachten dass Worst-Case alle Bytes escaped werden müssen. Allerdings kannst du natürlich schlauerweise auch die bereits "un-escapeten" Daten in die Puffer schreiben. Dann müssten die nicht doppelt so gross sein.

    Und ... nur für den Fall dass du es übersiehst...
    Angenommen deine Spezialzeichen sind 0 (Escape), 1 (Start) und 2 (Stop).
    Dann solltest du nicht 0, 1 statt 1 schicken. Sondern z.B. 0, 0x81 oder sowas.
    Weil man sonst wissen muss dass vor dem 1er ein 0er war, damit man weiss dass der 1er nicht für "Start" steht.

    Und du solltest dir Werte aussuchen die du selten verschickst. Also vielleicht nicht gerade 0, 1 und 2 🙂



  • hustbaer schrieb:

    Mir fällt jetzt keine einfachere/effizientere Lösung ein.

    Och, je nach Anforderung kann man da beliebig effizienter werden.

    Zum Beispiel send(char* dta,int len) wird umgesetzt in statt len>>24,len>>16,len1>>8,len,dta[0],dta[1],…,dta[len-1] einfach in dta[0],dta[1],…,dta[len-1],0 mit der einzigen Ausnahmeregel, daß wenn im Eingabestrom 0 kommt, daraus 0 0 gemacht wird. Kräge das der Empfänger sinnvoll mit? Genausoviele waits und locks und Kontextwechsel? Jupp. Nur bei Datenlage: "fast keine 0 in Nutzdaten" sparsamer. Und bei vielen vielen kleine Paketen bis zu echt erheblich besser.



  • @volkard
    Ne. Guggsdu:

    UART newbie schrieb:

    Ich versuche dies nun so, dass jeder Eintrag in den Ringpuffer einen eintrag auf den Anfang des nächsten und vorherigen Elements besitzt. Passt ein Eintrag nicht mehr in den Ringbuffer, wird er nicht zerstückelt, sondern direkt an der stelle 0 eingetragen.

    Gibt es dafür evtl. effizientere Lösungsansätze?

    hustbaer schrieb:

    Mir fällt jetzt keine einfachere/effizientere Lösung ein.



  • hustbaer schrieb:

    Mir fällt jetzt keine einfachere/effizientere Lösung ein.

    Zur Kenntnis genommen.



  • @volkard
    bitte entweder ordentlich mitlesen, oder spar dir deinen Zynismus.

    Mein "mir fällt jetzt keine einfachere/effizientere Lösung ein" war NICHT auf die Sache mit "Länge in der Header mitschicken" bezogen.

    Dein Beitrag macht daher keinen Sinn.



  • Ja, das habe ich alles soweit bedacht.

    Dabei musst du halt beachten dass Worst-Case alle Bytes escaped werden müssen. Allerdings kannst du natürlich schlauerweise auch die bereits "un-escapeten" Daten in die Puffer schreiben. Dann müssten die nicht doppelt so gross sein.

    Damit kann man das ganze auf jeden Fall noch etwas effizienter machen, verkompliziert es jedoch auch ein wenig.
    Beim senden schiebe ich die Daten einfach ein ein Ringbuffer rein, der wann immer ein byte gesendet werden kann von der anderen Seite abgebaut wird.



  • Naja kommt drauf an ob es sich auszahlt.
    Die Vorteile sind

    1. Braucht weniger Speicher

    2. Die Messages in den Puffern können einfacher verarbeitet werden wenn es kein "variable length encoding" im Puffer mehr gibt. D.h. du kannst dann einfach an der Stelle Pufferanfang+X lesen wenn du das Byte mit Offet X braucht. Und das ohne dass du vor dem Verarbeiten der Message nochmal drüberlaufen und "un-escapen" musst. Und da du 2x memcpy aus nem zerstückelten Puffer nicht willst, gehe ich einfach mal davon aus dass du das auch nicht willst.

    Der Nachteil ist natürlich dass der Code im Interrupt-Handler minimal komplizierter wird. Mehr als ein "last byte was escape sequence" bool brauchst du da aber nicht - sollte akzeptabel sein.

    Und ... dass der Throughput durch das Escapen nicht mehr deterministisch ist hast du auch bedacht, oder?



  • Und ... dass der Throughput durch das Escapen nicht mehr deterministisch ist hast du auch bedacht, oder?

    Nein, das hab ich leider nicht bedacht! Aber der Durchsatz ist noch "schwach deterministisch", da sich der Durchsatz im schlimmsten Fall nur halbieren kann. Damit kann ich noch leben 🙂

    Was mir aber sehr wichtig ist, dass das Protokoll einfach zu verwenden ist. Ich setze den Code grade neu auf. Im Prinzip hab ich nurnoch 2 Klassen: Encoder und Decoder.

    Den Encoder fütter ich mit kompletten Datenpaketen (push), und die entsprechenden zu sendenden bytes kann man dann mit einer pop-funktion rausholen.
    Beim Decoder ist das entsprechend andersrum.

    Also fällt der 2. Vorteil beim senden nicht mehr ins Gewicht.



  • Obwohl, das ganze hat beim senden doch ein kleines Problem: Dadurch das die codierten Daten länger werden können als die nicht codierten Daten, kann man nicht mehr zu Begin sagen ob noch genug platz im Ringpuffer vorhanden ist. So kann man erst während des codierens feststellen ob es nicht mehr reicht.

    Ich finds nur grad erstaunlich wie viel Arbeit ein scheinbar so kleines Problem machen kann ... 🙂



  • Naja, ich hab den Eindruck dass du dir die Sache auch ordentlich schwerer machst, indem du versuchst sämtliche "unnötigen" Speicherzugriffe zu vermeiden.

    Wie ist denn das Verhältnis Speichergeschwindigkeit vs. CPU-Geschwindigkeit bei deinen Contollern?

    Bei normalen PCs wäre es heute noch total kontraproduktiv ein einmaliges Umkopieren von ein paar hundert Byte durch komplizierteren Code zu vermeiden. Weil der L1 Cache einfach so sauschnell ist. Bei High-End GPUs ist's wieder anders umgekehrt, da kann man sich einiges an Arithmetik leisten wenn man damit nen Speicherzugriff vermeiden kann.



  • Der Prozessor ist ein Cortex M0+

    Ich mach mir da jedoch grade so viele Gedanken, weil ich diesen Code dann auch für zukünftige Projekte verwenden möchte. Kennst du vllt. fertige Protokolle an denen ich mich orientieren könnte? Ich schiele grad mit einem Auge auf HDLC



  • Mir fällt grad noch was anderes ein:

    Wenn ich die Nachrichten direkt codiere, dann kann ich mir die interrupts sparen und es direkt über den DMA machen.



  • Ich kenne keines das Start-/Stop-Symbole verwendet und gleichzeitig unbeschränkte 8-bittige Binärdaten als Paketinhalt erlaubt.

    Die seriellen Protokolle mit denen ich bereits gearbeitet habe sind cctalk, EBDS und ID003.

    Die verwenden aber alle Längenangaben. Und werden üblicherweise so verwendet dass einer Chef ist, der schickt nen Request, und der andere Antwortet immer nur. Erkennen von fehlerhaft/unvollständig übertragenen Paketen macht man dabei üblicherweise über Timeouts und das Checken der entsprechenden Fehler-Bits des UARTs (Framing-Error, Parity-Error etc.).

    Weiters ist bei diesen Protokollen meist eine maximale Pause vorgeschrieben, zwischen zwei Bytes die zur selben Nachricht gehören. Wenn man länger als diese Zeit nix empfangen hat, dann kann man davon ausgehen, dass das nächste Byte das daherkommt den Anfang eines Pakets darstellt.

    Weiters gibt es dann z.B. noch Prüfsummen (z.B. XOR8, CCITT16).

    Alles zusammen führt zu "sehr guten" Fehlerraten.

    Wobei die Frage nicht unwichtig ist welche Art von Problemen wie oft vorkommen wird. Wenn z.B. die Line furchtbar dreckig ist, und dauernd umgefallene Bits ankommen, dann braucht man was anderes, als wenn man ne sehr saubere Line hat, wo aber z.B. kein exaktes Timing eingehalten werden kann.

    Bei umgefallenen Bits sind Prüfsummen und ECC Mechanismen gut. Da man damit gut erkennen bzw. u.U. korrigieren kann wenn eben Bits falsch angekommen sind. Ebenso hilft hier der UART wenn er z.B. Framing-Fehler gut erkennen kann.

    Bei Timingproblemen helfen dagegen eher reservierte Start-/Stop-Symbole. Da man damit eben gut "re-synchronizen" kann. Also mit sehr hoher Sicherheit sagen welches Byte den Anfang einer Message darstellt.
    (Re-synchronizen geht natürlich auch mit Hilfe von Prüfsummen, aber nicht so sicher, und vor allem wird der Code dazu schnell sehr kompliziert. Speziell wenn man so schnell wie möglich einen neuen Einstiegspunkt finden möchte.)



  • Achtung Achtung, hier fängt mein Posting an!

    hustbaer schrieb:

    @volkard
    bitte entweder ordentlich mitlesen, oder spar dir deinen Zynismus.

    Hab keine Zeit mehr, immer sogar auch alle Wörter Deiner Beiträge zu lesen. Da kanns schon mal zu einem kleinen Mißverständnis kommen, sorry.



  • UART newbie schrieb:

    Bisher gehe ich dabei so vor, dass ich am Anfang jedes Datenpakets 2 Bytes mit der Größe des Datenpakets mitschicke.

    oder bloß ein Byte für die Größe und sowas wie Zahlen <=127 sind ok und >127 sind das negierte hi-Byte und das low-Byte folgt gleich.
    https://ja.wikipedia.org/wiki/可変長数値表現



  • fenrayn schrieb:

    Den Encoder fütter ich mit kompletten Datenpaketen (push), und die entsprechenden zu sendenden bytes kann man dann mit einer pop-funktion rausholen.
    Beim Decoder ist das entsprechend andersrum.

    Ähm, dann darfste ja quasi *alles* machen.
    RLE https://de.wikipedia.org/wiki/Lauflängenkodierung
    LZ4 https://de.wikipedia.org/wiki/LZ4



  • Ich habe ein Protokoll gefunden, das dem was ich vor habe sehr ähnlich ist:
    https://de.wikipedia.org/wiki/Serial_Line_Internet_Protocol

    Dort wird sogar auf das Startbyte verzichtet.

    Danke Hustbaer und Volkard für die sehr hilfreichen Tipps 🙂



  • volkard schrieb:

    Achtung Achtung, hier fängt mein Posting an!

    hustbaer schrieb:

    @volkard
    bitte entweder ordentlich mitlesen, oder spar dir deinen Zynismus.

    Hab keine Zeit mehr, immer sogar auch alle Wörter Deiner Beiträge zu lesen. Da kanns schon mal zu einem kleinen Mißverständnis kommen, sorry.

    Und ich habe keine Zeit dir zu erklären wie Beiträge wie diese deine z.T. unglaubliche Überheblichkeit zum Ausdruck bringen.



  • // Edit
    Hat sich erledigt 😛


Anmelden zum Antworten