Darf ich die offensichtliche Antwort verlinken?
Meine Lösung ist relativ simple gehalten und hier beschrieben. Ist etwas von meinem Staubsaugerroboter abgeguckt.
Zusammenfassung bis hierher:
- Ein Modul hat eine bytebasierte Schnittstelle und ist programmiersprachenunabhängig.
- Ein Modul muss unterschiedliche Datenblöcke (Telegramme) empfangen und versenden können. Inhalte und Länge eines "Telegramms" sind telegrammtypbezogen.
- Deshalb muss man ein Telegramm identifizieren können. Eine Kennung (CmdID) muss her.
Für Menschen, die noch nie eine Vernetzung zwischen zwei Controllern (oder Controller und PC) praktisch aufgebaut haben (das ist keine Bildungslücke, viele angewendete Protokolle schmeißen unvollständige Daten einfach weg, ohne den Grund zu nennen. Als Anwender hat man von XML bis Binär nur selten die Gelegenheit, in den APIs die Parse-Bedingungen nachzuvollziehen). Es gibt zusätzliche Anforderungen:
- Übertragbarkeit: Kann ich ein Protokoll zwischen zwei Controllern auswerten? Kann ich das auch z.B. zwischen Controller und PC?
- Störsicherheit: Was passiert bei einer Störung? Was passiert, wenn z.B. ein Sender sendet. während ich den Empfänger flashe oder neu starte? Wie synchronisiert sich ein Empfänger nach einer Störung anhand des Protokollrahmens neu?
Finger hoch: Wer hat so ein Protokoll? Wie sieht der Rahmen aus? Synchronisiert sich das Protokoll nach einer Störung automatisch?
Darf ich die offensichtliche Antwort verlinken?
Meine Lösung ist relativ simple gehalten und hier beschrieben. Ist etwas von meinem Staubsaugerroboter abgeguckt.
Geändert von Defiant (15.11.2020 um 10:50 Uhr)
Übertragbarkeit: Kann ich ein Protokoll zwischen zwei Controllern auswerten? Kann ich das auch z.B. zwischen Controller und PC?
Hierzu kommt mir sofort in den Sinn, Datenblöcke so einfach wie möglich zu gestalten, auch so, dass sie schnell verarbeitet werden können.
Hierzu zähle ich persönlich auch die Datentyp-Größe in Bit. 4 Byte große Fließkommawerte sind schon gewaltig. Ich handhabe das immer so, dass ich schaue, was wirklich benötigt wird (vielleicht noch etwas Reserve). Ein Controller, der einen Schrittmotor ansteuert oder einen Servo, benötigt kein Fließkomma. Das muss erst umgerechnet werden. Das wiederum kostet nicht nur Rechenzeit, sondern am Ende auch Energie. Solche Operationen sollten nur ein Mal an zentraler Stelle, erledigt werden. Bei Universaldaten, wo man nicht sicher ist, ob der Empfänger vielleicht doch zunächst mit Nachkomma arbeitet, ist die Frage immer, wieviele Nachkommastellen sind nötig um ein Ergebnis zu bekommen, dass genau genug ist. Heißt: wenn am Ende eine Nachkommastelle genügen würde, um ein Ergebnis zu bekommen, was genau genug ist (weil ich eine Motorsteuerung doch irgendwo nur mit Werten von 0 bis 255 füttere), warum soll ich dann mehr schicken? I.R. genügen 2 oder 3 Nachkommastellen. Bei 2 Byte / 16 Bit, kann ich Werte von -32767 bis +32767 abbilden. Das könnten auch sein: 3.2767, 32.767, 327.67, 3276.7 Bei 3 Byte sind das -8388607 bis + 8388607 (8.388607 bis 838860.7; bei 3 Nachkommastellen: +/-8388.607). Wie die Werte zu interpretieren wären, hängt dann von der Block-ID (CmdID) ab. So kann man auch unterschiedliche Datentypgrößen, für unterschiedliche Zwecke, verwenden. Ich will nur meine Sicht darstellen, vielleicht lohnt es sich drüber nachzudenken, ich will nicht nörgeln.
Als Grundlage vielleicht: http://152.96.52.69/webMathematica/c...5/node183.htmlStörsicherheit: Was passiert bei einer Störung? Was passiert, wenn z.B. ein Sender sendet. während ich den Empfänger flashe oder neu starte? Wie synchronisiert sich ein Empfänger nach einer Störung anhand des Protokollrahmens neu?
Als Protokollrahmen nehme ich mal die Hardwarekommunikation (per serieller Schnittstelle oder I2C z.b.). Bei Sender und Empfänger verwende ich bisher immer einen Timeout. Ist vielleicht nicht unbedingt die zeitsparendste Variante, aber wenn keine Fehler bei der Übertragung auftreten sollten, durchaus rel. einfach umzusetzen: wenn eine Seite mit der Kommunikation nicht klar kommt, wartet die dann einfach eine bestimmte Zeit, bis die Gegenseite in einen definierten Zustand zurück fällt. Die Kommunikation kann dann neu begonnen werden.
Wer hat so ein Protokoll?
So weit ich das bei meinen Anwendungen sehe, ist dass immer von der jeweiligen Schnittstelle abhängig und wie dann die Kommunikation aufgebaut ist, also individuell.
Ich komme hier mit den Begriffen "Protokoll" und "Protokollrahmen" noch nicht zurecht. Universalprotokolle und -protokollrahmen kenne ich (auf Hardwareebene) nicht, das sind ja dann irgendwie immer Wrapper, die zusätzliche Rechenzeit benötigen. Wrapper auf niedrigen Ebenen führen zu erhöhten Hardwareanforderungen (schnellere CPUs, mehr Speicher, mehr Energiebedarf = "größere" Akkus und / oder geringere Laufzeit, bis zum Nachladen des Hauptenergiespeichers)
Bei dem oben verlinkten Text zu "ROS" steht es so ähnlich auch drin:
"Er skizziert die angestrebten Anwendungsfälle sowie deren Anforderungen und Einschränkungen. Darauf aufbauend wird die entwickelte Middleware-Schnittstelle erläutert."
MfG
Geändert von Moppi (15.11.2020 um 09:36 Uhr)
Natürlich darfst Du das. Und Du darfst auch weiterhin die Vorteile bezüglich ROS lobpreisen.
Der Link löst aber mein Problem nicht. Ich halte ein Stück selbstgebaute Hardware mit UART in der Hand. Da muss ich mich entscheiden, welchen Protokollrahmen ich verwende. Nehme ich vielleicht z.B. sowas (Seite 2)?
Synchronisiert sich dieses Protokoll bei Störungen? Ich sitze hier im Graben und fummel am Code meines Moduls rum. Ich progge den Controller ab und zu neu. Das unterbricht zwar die Daten, aber die Verbindung nicht. Ich hab auch keine Lust, nach jedem Neuproggen die Gegenstelle zu rebooten. Da muss also (auf beiden Seiten) was her, was unterbrochene Telegramme wegschmeißt. Je schlanker, desto besser. Meine Ressourcen auf dem Controller sind begrenzt.
Geändert von Holomino (15.11.2020 um 17:39 Uhr)
Das mit dem "darf" war jetzt nicht so wörtlich gemeint. Ich habe nur das Gefühl ich gehe euch ein wenig damit auf die Nerven.
ok, klingt nach einem Job für rosserial (Nur ROS1). Wobei ich selber allerdings ein eigenes einfaches i2c-Protokoll zur Kommunikation mit meinem µC verwende. Der Neustart der ROS-Node die die I2C-Hardware an das ROS-Ecosystem anbindet muss ich dann zwar manuell neustarten, aber das stört mich nicht hinreichend genug um das zu ändern. Geht ja auch hinreichend schnell, da alle anderen Nodes weiter laufen können. Theoretisch könnte ich auch einen automatischen Restart bei Bus-Fehlern einbauen..
Mir persönlich war rosserial zuviel Overhead zum Einsatz auf einem 8-Bitter.
Der Vollständigkeit halber: Bei ROS2 wurde rosserial durch micro-ROS abgelöst. Das braucht aber wohl "größere" µC wie einen ESP32. Ich denke nicht, dass es auf einem AVR lauffähig ist. Ich habe keine Erfahrungen mit micro-ROS.
Update: Hier gibt es was mit micro-ROS auf Arduino: https://discourse.ros.org/t/micro-ros-on-arduino/17009
Wenn Du eine Hardware hast und nach Protokolllösungen suchst, habe ich hier was gefunden. Die Blockdiagramme enthalten auch den Fall des Abbruchs. Es gibt immer zwei Möglichkeiten: das Protokoll wird nicht eingehalten (es gibt eine Zeitüberschreitung, weil Verbindung getrennt -> Timeout -> Abbruch) und die übertragenen Daten stimmen nicht (Prüfsumme einführen CRC bspw.) -> dann werden die gesendeten Daten, i.R. ein ganzes Datenpaket (s. Protokollrahmen), verworfen/ignoriert und das letzte Datenpaket nochmals angefordert. Dafür brauchst Du mindestens eine Paketnummer und, im Falle der Überprüfung, eine Prüfsumme, die enthalten sein müssen. Außerdem muss der Empfänger mitteilen können, ob das letzte Paket i.O. war. Dafür kann man eine extra Datenleitung verwenden, entweder für Sender und Empfänger eine oder eine gemeinsame Leitung; im Protokoll muss dann festgelegt sein, wann wer von beiden diese Leitung nutzt, den/einen Status mitzuteilen (weil einer muss den Leitungszustand auswerten und einer setzen). Andere Möglichkeit, wenn das Paket nicht i.O. war: der Empfänger tut so, als ob die Leitung unterbochen wurde, es wird dann dieses Prozedere ausgeführt.
MfG
Eigentlich nicht. Solange wir hier ROS als eine Alternative aus mehreren Möglichkeiten behandeln können, kann ich vergleichen und Dir Löcher in den Bauch fragen.
Apropos Löcher:
Im Nachbarthread hattest Du beschrieben, dass auch Du Spannung und Strom vom Akku misst. Magst Du vielleicht einmal beschreiben, wie bei Dir in ROS der Weg dieser Werte bis in die Oberfläche aussieht (Messaufbau brauchste nicht zu beschreiben, aber so ab dem Teil, ab dem die Werte an irgendeinem AD-Wandlerpin landen)?
Naja, so richtig auf der Suche bin ich nicht. Ich dachte nur, es kommen schon praktische Implementierungen von Euch, bevor ich die von mir angewendete Lösung vorstelle.
Wenn ich Nachrichten übertragen oder lesen will, möchte ich dies möglichst benutzerfreundlich in meinem Code gestalten.
Anwendungsspezifisch schreibe ich z.B. eine Struktur von Typ DUMMY auf die UART:
Auf dem Empfängercontroller könnte die Empfangsroutine so aussehenCode://Anywhere from header #define MagicID_DummyTelegram 0x42 typedef struct { uint8_t CmdID; //0 uint8_t Mode; //1 uint16_t Val; //2 }__attribute__ ((packed)) Dummy_t; //Length 4 Byte //Anywhere in declaration Dummy_t dummy; //Anywhere in code dummy.CmdID = MagicID_DummyTelegram; dummy.Mode = 0x80; Dummy.Val = 0xEEFF; SendTelegram((uint8_t*) &dummy, sizeof(dummy));
Ein mögliches Protokoll dazu:Code://Anywhere from header #define MagicID_DummyTelegram 0x42 typedef struct { uint8_t CmdID; //0 uint8_t Mode; //1 uint16_t Val; //2 }__attribute__ ((packed)) Dummy_t; //Length 4 Byte //Anywhere in code void TelegramReceived(uint8_t *data, uint8_t len) { switch (data[0]) // get CmdID { case MagicID_DummyTelegram: { Dummy_t* dummy = (Dummy_t*) &data[0]; //Cast if (dummy->Mode == 0x80) //and do something SetLEDBrightness(dummy->Val); } break; case AnotherMagicID: . . . } }
- Ein Telegramm beginnt mit einem Startbyte (0xFF)
- Ist ein Byte in den Telegrammdaten gleich dem Startbyte, wird es doppelt gesendet
- Ohne Kenntnis über Länge und Bedeutung des Telegrammes benötigt die Empfangsroutine eine Längenangabe.
Über den Draht gehen aus dem obigen Beispiel also:
0xFF (Startbyte)
0x04 (Strukturlänge)
0x42, 0x80, 0xEE, 0xFF, 0xFF (Strukturinhalt, Beachte: 1. Element ist die CmdID, 0xFF in den Strukturwerten wird doppelt gesendet)
Genauere Implementierungsdetails (was bei mir bezüglich FIFO und Parsen zwischen den Endpunkten SendTelegram und TelegramReceived liegt) findet Ihr z.B. im VL53L1X-Thread...
...wobei mich aus meiner beschränkten Sicht heraus z.B. der Vergleich und die Kompatibilität zu ROS, Arduinoprogrammierung oder Bascom interessieren würde.
Geändert von Holomino (16.11.2020 um 14:31 Uhr)
Das geht eben nicht immer bei Dir hervor, da tappt man etwas im Dunkeln. Du formulierst immer so, als ob Du generelle Fragen hast.
Was mich angeht, so verwende ich I2C und serielle Schnittstelle über UART, am ATmega328P-PU und nodeMCU. Alles, was man dort benötigt, sind die Bibliotheken für diese Schnittstellen, um das Protokoll muss man sich weitestgehend nicht selber kümmern. Ansonsten hätte ich noch eine parallele Übertragung, vor allem als Versuch, soll aber auch funktionieren (die verfügbaren Pins gaben das so her), dafür ist kein UART notwendig. Dort steht das Protokoll aber auch fest, als Programmablauf. Ich verwende dazu 3 Datenleitungen und für jeden angebundenen µC zwei Kontrollleitungen (eine führt von einem µC zum andern, und eine andere zurück), jeder µC kontrolliert seine eigene Leitung (Status f. Empfang, senden...). Sollte ein Datenpakettransfer fehlschlagen, bricht der Empfänger nach einer Weile ab, dann ist der Fertig. Wenn der Sender bemerkt, dass der Empfänger nicht antwortet, bricht der auch ab. Beim nächsten Schleifendurchlauf ist der Datenblock nicht versendet und wird daher nochmals übertragen. Das Spiel wiederholt sich dann so oft, bis die Daten übertragen werden konnten, oder bis der Akku leer / die Elektronik kaputt ist.
Du hast aber eine andre Lösung für Dich gefunden, wäre Zufall, wenn das einer schon mal so gemacht hätte und daher fertige Protokolle, Software und Datenpaket-Beschreibungen parat hat.
Was ganz einfaches und aber sicheres (halbiert die Datenübertragungsrate), habe ich mal für sie Serielle Datenübertragung gemacht. Findet man in meinem Fotoalbum, als Flußdiagramme/PAP. Kann man rein schauen, aber das ist sicher nicht, was Du suchst(?).
Im Übrigen fände ich PAPs besser. Ist nicht auf eine Programmiersprache fixiert. Erreicht man meist eine breitere Masse an Leuten.
MfG
Geändert von Moppi (16.11.2020 um 15:10 Uhr)
Natürlich frage ich zuerst ganz unvoreingenommen. Ich bin nicht der brennende Busch. Meine Lösung ist erst einmal nur eine unter vielen.
Wenn Du schreibst, Du verwendest Bibliotheken: Ich kenne Arduinos nur sehr oberflächlich, praktisch gar nicht. Ich kann mir die Serial.h anschauen und daraus erahnen, dass da wohl ein FIFO verwendet wird. Die textuelle Übertragung einer Konsolenausgabe habe ich auch schon mal als Codebrocken gesehen. Aber wo muss ich suchen, wenn ich eine vollasynchrone Datenübertragung zwischen zwei Arduinos (beide Teilnehmer blasen sich ohne Softwarehandshake gegenseitig mit Daten zu. Jede Seite kann die unterschiedlichen Nachrichten im empfangenen Datenstream aufbröseln. Das Aufbröseln darf bei einer Störung Daten verlieren, aber nicht ins Stocken geraten) haben will?
Gibt's da fertige Libs? Wenn ja, welche? Wie fix sind die? Was geht über den Draht? Kann ich das Protokoll mit Bascom o.ä. nachprogrammieren?
Einen PAP zu zeichnen wäre obsolet, wenn sich ein Arduinofreak einmal den Code anschaut und die Übersetzbarkeit prüft. Wenn das hier keiner machen will, bestell ich mir so'n Teil und mache es selber - kann ja wohl nicht so schwierig sein.
Geändert von Holomino (16.11.2020 um 16:00 Uhr)
Spannung & Strom werden von einem A/D-Pin eines AVR gemessen, dort in Volt & Ampere umgerechnet und per I2C an den Einplatinencomputer gegeben. Die ROS-Node holt diese Information aus den entsprechenden I2C-Registern ab. Hier ist der Quellcode für die Implementierung in C und Python. Der ROS-Welt werden die Werte einmal als diagnostics und einmal als BatteryState-Message zur Verfügung gestellt. Letztere lässt sich überall abfragen, z.B. in einem Terminal:
oder grafisch mit Tools wie PlotJuggler. Bei mir allerdings meistens im Browser, vor allem wenn ich nur kurz den aktuellen Wert lesen möchte.Code:user@wildthumper:~> rostopic echo -n 1 battery/voltage 11.470000267 ---
Lesezeichen