Wann sollte man von ASCII auf erweiterte serielle Protokolle umsteigen?

Alle meine Mikrocontroller-Geräte, die über UART mit dem PC kommunizieren, verwenden ASCII-Strings zum Senden von Befehlen und Empfangen von Daten (wie in Arduino implementiert). Das habe ich gelernt, als ich anfing, mich mit Elektronik zu beschäftigen, und ich fand es immer ausreichend, blanke Saiten zu senden. Mir ist jedoch aufgefallen, dass die meisten Geräte, auf die ich gestoßen bin, hochentwickelte Binärprotokolle verwenden, die Funktionscodes, Adressen und CRC-Fehlerprüfung enthalten.

Wann ist eine einfache ASCII-Kommunikation akzeptabel und wann sollte ich etwas Fortgeschritteneres wie Modbus in Betracht ziehen? Verwenden kommerzielle Geräte ein solches ASCII? Industriell?

Kurze Antwort: Wenn Ihre Anwendung es erfordert. Ja, kommerzielle Geräte verwenden ASCII. Nehmen Sie als Beispiel GPS NMEA. (Und noch einmal, ich werde meine eigene Frage hier verweisen )
Modbus hat einen ASCII-Modus. Siehe Modicon Modbus-Protokoll-Referenzhandbuch
@EugeneSh.: Es ist erwähnenswert, dass NMEA über ein Prüfsummenfeld verfügt und das Verwerfen eines einzelnen Sample-Bursts aufgrund eines Prüfsummenfehlers (was häufiger vorkommt, als Sie vielleicht denken) im Allgemeinen kein kritischer Fehler ist. Das ist bei anderen Protokollen möglicherweise nicht der Fall ... und es werden viele binäre GPS-Protokolle verwendet (z. B. Garmin) für Anwendungen, bei denen dies tatsächlich kritisch sein kann (oder bei denen eine Abtastrate höher als 1 Hz ist). erforderlich, wofür NMEA zu ausführlich ist). Obwohl dies wirklich nur Ihren Standpunkt festigt.

Antworten (8)

  1. ASCII und CRC schließen sich nicht gegenseitig aus. ASCII ist eine Kodierung und CRC dient der Fehlerprüfung.

  2. ALLES kann als ASCII gesendet werden. Wir Alten erinnern uns sicherlich an UUEncoding, das alles in einen ASCII-String verwandelt.

  3. A) Für mich ist es meistens eine Frage der Schnelligkeit und Effizienz. Das Senden einer großen 32-Bit-Zahl per ASCII kann lange dauern, aber es dauert nur 4 Bytes, um sie als Binärdatei über ein serielles Protokoll zu senden.

    B) Das Senden von NUMBERS über ASCII bedeutet, dass Sie die Zahl in ASCII konvertieren müssen, was ein klarer zusätzlicher Schritt ist (dies ist Teil dessen, was "printf" tut).

  4. Wenn Sie irgendwie Ihren Platz verlieren, es vermasseln, das Format verlieren, das falsche Endian erhalten usw., kann ein binäres Kommunikationsprotokoll sicherlich vermasseln. Wenn Sie ASCII senden, kann es einfacher sein, sich von Fehlern zu erholen, indem Sie einfach hineingehen und sich den Datenstrom ansehen.

+1 für "ASCII ist eine Codierung". Es ist kein Protokoll; Protokolle können auf ASCII aufbauen.
Die automatische Wiederherstellung nach einem Fehler ist für ein textbasiertes Protokoll an sich nicht einfacher als für ein binäres Protokoll, aber die Inspektion und Fehlerbehebung kann sicherlich sein.
@NickJohnson - absolut. Sobald Sie an dem Punkt angelangt sind, eine Datei in einem Hex-Editor zu öffnen, um zu sehen, was Sie wiederherstellen können, sind Sie in Bezug auf SOP bereits bei FUBAR
@nickjohnson das stimmt nicht wirklich. ASCII bietet Ihnen viele Out-of-Band-Framing-/Delimiter-Optionen zur Unterstützung der Synchronisierung und Wiederherstellung, die zusätzliches Escape, Bit-Stuffing, Zeitintervall oder andere Tricks erfordern würden, wenn der Kanal für Binärdaten in voller Breite verwendet wird.
@ChrisStratton Aber das bekommst du nicht automatisch; Sie müssen Ihren Code schreiben, um darüber Bescheid zu wissen.
Dinge wie das Aufteilen von Zeilenumbrüchen sind in vielen Funktionen für den seriellen Zugriff praktisch kostenlos und selbst dort, wo dies nicht der Fall ist, trivial. Das Einfügen und Entfernen von Framing in 8-Bit-Binärdaten ist dagegen schwieriger und bedeutet auch, dass die codierte Größe einer Datenmenge von ihrem Inhalt abhängt.
Ich bevorzuge ASCII beim Schreiben von Protokollen wegen all der offensichtlichen Vorteile (Lesbarkeit, Protokollierbarkeit usw.). Es gibt zwei Fälle, in denen Binärdateien sinnvoller sind: Erstens, wenn Geschwindigkeit ein Problem ist und Sie Binärdateien benötigen, um so viele Daten wie möglich in den Stream zu stopfen, und zweitens, geringfügig, wenn Sie absichtlich versuchen, die Daten zu verschleiern oder sogar zu verschlüsseln Stream, um Reverse-Engineering zu verhindern oder zu verhindern. Dabei habe ich binäre Protokolle rückentwickelt und es hat mich größtenteils nur mehr irritiert, als die Tat tatsächlich verhindert.
@J ... Ich füge hinzu, dass Speicherplatz auf der Empfängerseite einmal ein echtes Problem war, aber das ist es wirklich nicht mehr.

Hier sind einige Gedanken dazu:

  • ASCII ist nett, weil Sie einen seriellen Monitor verwenden können, um manuell nachzusehen, was gesendet wird.
  • Wenn Ihre Verbindung nicht zuverlässig ist, müssen Sie mit Übertragungsfehlern rechnen und sollten eine CRC verwenden, um die Integrität jeder empfangenen Nachricht zu überprüfen. Dies kann auch bei ASCII-Nachrichten erfolgen.
  • Wenn Ihre Verbindung zu langsam ist, können Sie die Größe Ihrer Nachrichten reduzieren, indem Sie auf ein Binärformat umschalten
  • Ein spezialisiertes Binärformat kann auf der Empfängerseite einfacher zu dekodieren sein als ASCII

Auf der einfachsten Ebene könnte man sagen, dass ein einfaches Kommunikationsprotokoll drei Schichten hat: physisch, Transport und Anwendung. (Es gibt Modelle mit mehr wie OSI mit 7 oder TCP/IP mit 4. Die Anzahl der Schichten ist im Zusammenhang mit dieser Frage nicht besonders wichtig.)

Die Anwendungsschicht ist die Schicht, mit der Sie sich direkt in Ihrem Code befassen, und der Fokus der Frage. Was die Transportschicht betrifft, ist das Byte, das Sie ihr in send_data übergeben haben, nur ein binäres Muster, aber Sie können es in Ihrem Anwendungscode als den Buchstaben „A“ interpretieren. Die CRC- oder Prüfsummenberechnung ist die gleiche, unabhängig davon, ob Sie das Byte als 'A', 0x41 oder 0b01000001 betrachten.

Die Transportschicht ist die Paketebene, auf der Sie Ihre Nachrichtenkopfzeilen und die Fehlerprüfung haben, sei es CRC oder eine einfache Prüfsumme. Im Zusammenhang mit Firmware haben Sie möglicherweise eine Funktion wie send_data, bei der Sie ihr ein zu sendendes Byte übergeben. Innerhalb dieser Funktion wird ein Paket eingefügt, das besagt: "Hey, das ist eine normale Nachricht, erfordert eine Bestätigung, und die Prüfsumme ist 0x47, die aktuelle Zeit ist X." Dieses Paket wird über die physikalische Schicht an den empfangenden Knoten gesendet.

Auf der physikalischen Schicht werden die Elektronik und die Schnittstelle definiert: Anschlüsse, Spannungspegel, Timing usw. Diese Schicht kann von ein paar Spuren mit TTL-Signalen für einen einfachen UART auf einer Leiterplatte bis zu einem vollständig isolierten Differenzialpaar wie in einigen reichen CAN- Implementierungen.

Am empfangenden Knoten kommt das Paket auf der physikalischen Schicht herein, wird auf der Transportschicht entpackt und Ihr binäres Muster steht dann der Anwendungsschicht zur Verfügung. Es liegt an der Anwendungsschicht des empfangenden Knotens, zu wissen, ob dieses Muster als „A“, 0x41 oder 0b01000001 interpretiert werden soll, und was damit zu tun ist.

Zusammenfassend lässt sich sagen, dass es so ziemlich immer akzeptabel ist, ASCII-Zeichen zu senden, wenn die Anwendung dies erfordert. Das Wichtigste ist, Ihr Kommunikationsschema zu verstehen und einen Fehlerprüfmechanismus einzubauen.

ASCII-Protokolle können auch Prüfsummen enthalten. Ich bin auf Hex-as-ASCII-Variationen gestoßen, die die ASCII-Darstellung von Zahlen verwenden.
@EugenSch. Klärte diesen Punkt
Keine Spitzfindigkeit, aber TCP besteht nicht aus vier Schichten; es wird als in Schicht vier des OSI-Modells passend angesehen. Serielle Kommunikation passt nicht wirklich gut zum OSI-Modell.
@batsplatsterson Das ist Spitzfindigkeit und für den Punkt, den ich mache, ziemlich irrelevant.

Ein noch nicht erwähnter Punkt ist, dass unabhängig davon, ob Sie ASCII- oder ein Binärprotokoll verwenden, das Senden eines Rubbelzeichens vor jedem Paket sicherstellt, dass selbst dann, wenn Leitungsrauschen oder Framing-Fehler vor dem Start eines Pakets auftreten, alle Zeichen nach dem Rubbelzeichen out wird ohne weiteres Rauschen korrekt gerahmt. Andernfalls, wenn man kontinuierlich Pakete sendet und keine Zeichen enthält, die garantiert eine Resynchronisation erreichen, ist es möglich, dass ein Fehler alles Folgende bis zur nächsten Übertragungspause beschädigt. Das 0xFF-Zeichen ist nett, weil es garantiert, dass jeder Empfänger auf das folgende Zeichen resynchronisieren kann.

(*) 0xFF – Rubout genannt, weil jemand, der ein falsches Zeichen eintippt, während er Daten auf ein Papierband tippt, die Taste „Band rückwärts“ drücken und Rubout drücken kann, um das irrtümlich gestanzte Zeichen durch 0xFF zu ersetzen, was geschieht von den meisten Empfängern ignoriert werden).

Ein Vorteil des Sendens von ASCII-Strings besteht darin, dass die Steuercodes dann verwendet werden können, um den Beginn / das Ende der Nachricht zu signalisieren. zB STX (Zeichen 2) und ETX (Zeichen 3) können den Beginn der Übertragung und das Ende der Übertragung signalisieren. Alternativ können Sie einen einfachen Zeilenvorschub hinzufügen, um das Ende der Übertragung zu markieren.

Beim Senden von Binärdaten wird dies komplizierter, da kein bestimmtes Bitmuster für einen Steuercode reserviert werden kann (ohne zusätzlichen Overhead oder Komplexität), da ein gültiges Datenbyte dasselbe Muster haben kann.

Viele Binärprotokolle reservieren ein oder mehrere Bitmuster als Steuercodes, enthalten aber auch einen Escape-Mechanismus, um diese Codes zu handhaben, wenn sie in den Daten erscheinen.
Sie können jedes Muster reservieren, um alles, was Sie wollen, in Binärform zu kennzeichnen. Zum Beispiel bin ich in einem Projekt mit einem schnellen Datenstrom und einem langsamen Datenstrom, die denselben Uart ausgeben. Ich habe das größte negative int32 als Flag für meine langsamen Daten reserviert und meine negativen Daten einfach mit dem größten negativen + 1 gesättigt.
Einverstanden. Ich habe dies in der bearbeiteten Antwort klargestellt, hoffe ich.

ASCII ist in Ordnung, ich verwende es in fast allen Projekten. Es macht das Debuggen für die Überwachung des Ports viel einfacher und würde nur dann zu einem Problem, wenn viele Daten zu senden wären.

Ein weiterer Bonus: Ich verwende serielle Funkgeräte, um Nachrichten zwischen Arduinos zu erhalten, und ich kann einen an meinen Laptop angeschlossenen seriellen Monitor verwenden und Nachrichten einspeisen, um bestimmte Dinge zu bewirken. Super zum Testen.

Auch das Senden von Dingen als Binärdatei ist nicht unmöglich zu debuggen, und abhängig von Ihren Tools können Sie die Binärdatei extrahieren und in etwas für Menschen Lesbares konvertieren lassen. Oder wenn Sie wissen, wonach Sie suchen, können Sie den Datenstrom visuell inspizieren und Werte dort erkennen, wo sie sein sollten, und Fehler so finden, wenn auch nicht so einfach. dh Sie werden die Muster von Bytes erkennen und die erwarteten Werte erkennen

Anstelle von Modbus ziehen Sie HDLC in Betracht . Sie erhalten eine Fehlererkennung (was bei lauten seriellen Leitungen wichtig ist). Synchronisation ist robust, Escape ist robust.

Ich habe HDLC in RS-485-Netzwerken ohne Probleme verwendet und PPP verwendet es ebenfalls.

Wäre nett, wenn Sie darauf hinweisen würden, warum Sie es über Modbus vorschlagen.

ASCII über UART ist unter anderem deshalb am beliebtesten, weil:

  • Es ist beim Debuggen für Menschen lesbar (ich habe noch keinen Logikanalysator gesehen, der ASCII nicht dekodiert).

  • Es ist sehr einfach zu implementieren, Sie haben eine ASCII-Tabelle über schnelles Google, die gut standardisiert ist.

  • Es hat eine eingebaute Synchronisation mit den Start-/Stopp-Bits.

  • So ziemlich die gesamte Hobbywelt hat sich mit ASCII über Seriell eingerichtet, also müssen alle neuen Methoden damit umgehen, und das ist keineswegs einfach.

Dann geraten Sie in eine Situation, in der Sie beginnen, eine bestimmte Codierung zu senden, z. B. das Senden der In-Memory-Darstellung eines Floats im Vergleich zum Konvertieren eines Floats in ASCII, das Senden über eine serielle Verbindung, die weit mehr als 4 Bytes sein kann, und das dann zurückkonvertieren zu einer In-Memory-Darstellung auf dem Host. Stattdessen senden Sie einfach jedes Mal die 4-Byte-Darstellung. Sicher, Sie können mit der Codierung selbst beginnen, aber dann müssen Sie Start-/End-Tags, Reihenfolge usw. einrichten.

Stattdessen können Dinge wie Protobuf verwendet werden. Dies wurde tatsächlich in einem Projekt verwendet, an dem ich arbeitete, und es war äußerst nützlich, es macht Nachrichten mit variabler Länge, handhabt Endian für Sie und ein paar andere coole Funktionen. Es ist auch nicht so groß in der Codegröße, und Sie können alles so angeben, dass es beim Start statisch zugewiesen wird. Sie müssten jedoch selbst eine Prüfsumme einwerfen, wenn Sie sie brauchen.