Real-Time and Embedded Systems

Infrarot

In der Mindstorms-Welt findet die Kommunikation zwischen verschiedenen Systemen per Infrarot statt. Jedes RCX und jeder Tower hat einen Sender und Empfänger eingebaut. Dadurch können Programme in den RCX geladen werden, es ist aber genausogut möglich, ein vollwertiges Kommunikationsnetzwerk zwischen beliebig vielen dieser Komponenten aufzubauen.

RCX 6                              RCX 3

Es liegt in der Natur der Sache, daß alle Daten per Broadcast gesendet werden. Jeder Teilnehmer kann alle gesendeten Daten empfangen, vorausgesetzt daß eine Sichtverbindung zwischen den Transceivern besteht, die Entfernung nicht zu groß ist und kein Störlicht die Verbindung behindert.

Inphost

Es gibt eine Möglichkeit, dass ein Mindstorm RCX über den IR-Tower mit dem PC kommunizieren kann. Dafür wird auf PC Seite eine eigene Implementierung des lnp-Protokolls benötigt. Für BrickOS hat dafür Stephan Höhrmann die C-Bibliothek lnphost entwickelt, welche auch hilfreiche Kommandozeilentools enthält, wie z.B.

 

Beide Programme nehmen als optionalen Parameter -d (für device) auch den Gerätenamen des Lego-Towers, der für die Übertragung verwendet werden soll. Dieser ist bei Linux der selbe, mit welchem man auch die Umgebungsvariablen RCXTTY belegt hat, also z.B. "/dev/ttyS0" oder "/dev/usb/legousbtower0".

Betreibt man unter Windows hingegen einen USB-Tower, so muß "//./legotower1" (erster Windows USB tower) angegeben werden. Andernfalls ist unter Windows der serielle (oder simulierte serielle) Port, wie z.B. "com4" als Parameter zu verwenden.

inp_printf

Jan Lukoschus hat das existierende LNP Protokoll für den RCX erweitert und eine Funktion namens lnp_printf zur Übertragung von Strings, Integern und Fließkommazahlen (anglehnt an die printf Funktion von C aus der stdio.h) entwickelt.

Es gibt zwei Möglichkeiten, die lnp_printf Funktion verfügbar zu machen:

  1. In die BrickOS Firmware einkompilieren: Diese Variante ist zu bevorzugen, funktioniert aber nur, wenn man die BrickOS Quellen zur Verfügung hat, also z.B. BrickOS selbst übersetzt hat und kein fertiges Linux Paket installiert hat. Die Header-Datei (lnpprintf.h) muss sich dabei im /brickOS/include/ Verzeichnis befinden, die Source-Datei (lnpprintf.c) im /brickOS/kernel/ Verzeichnis. Dann muss die Firmware neu kompiliert und auf den RCX übertragen werden.
  2. Lokal mit der Anwendung kompilieren: Header-Datei (lnpprintf.h) und Source-Datei (lnpprintf.c) befinden sich lokal im gleichen Verzeichnis wie das eigentliche Anwendungsprogramm (z.b. rover.c). Das Makefile für die Anwendung muss nun so angepasst werden, dass lnpprintf.c immer mit in das Anwendungsprogramm hineinkompiliert wird. Dazu die Variable DOBJECTS auf lnpprintf.o setzen. Hier ein Beispiel für ein einfachstes Makefile.

 

Zu verwenden ist die Funktion wie printf, mit dem Unterschied, dass die Daten zunächst per LNP auf den Host-Rechner übertragen werden und dann dort mittels dem hier laufenden lnpshow-Programm auf dem Bildschirm ausgegeben werden. Statt stdio.h muss natürlich lnpprintf.h inkludiert werden.

Physikalische Parameter

Die Daten werden zunächst seriell codiert, und zwar mit 2400 Baud, einem Startbit, 8 Datenbits, einem Stopbit und ungerader Parität. Dieses Signal wird auf ein Trägersignal von 38 kHz aufmoduliert. Eine binäre 0 wird als kurzes Signal mit 38 kHz dargestellt, eine 1 als genausolange "Stille". Das resultierende Signal wird an eine Infrarot-Sendediode weitergegeben. Dabei kann man per Software (RCX) oder mit einem Schalter (Tower) die Sendeleistung einstellen.

Bei hoher Sendeleistung verbraucht der Transceiver statt 10 mA einen Strom von 100 mA, dafür erhöht sich die Reichweite aber beträchtlich. So können ohne weiteres 10 Meter überbrückt werden, und das Signal kann im Raum an Wänden und der Decke reflektiert werden. Damit ist es nicht mehr nötig, Sender und Empfänger aufeinander auszurichten.

Das DTR-Signal wird vom Tower als DSR zurückgeschleift, so daß der PC erkennen kann, ob der Tower angeschlossen ist. Es ist auch möglich, die Geschwindigkeit auf 4800 Baud zu verdoppeln. Dabei ist allerdings die Wahrscheinlichkeit von Fehlern höher.

Unterste Protokollebene

Auf unterster Ebene benutzt der RCX ein Protokoll, mit dem Datenpakete transportiert werden können. Die Daten werden so codiert, daß jedes Paket genausoviele Einsen wie Nullen enthält und mit einem 3 Bytes großen Header beginnt. Dadurch kann der Empfänger ein konstantes Störsignal erkennen und durch Subtraktion der Signallevel herausfiltern. Ein Datenpaket, das die Bytes d1..dn enthält, sieht folgendermaßen aus:

55h ffh 00h d1 ~d1 ... dn ~dn c ~c

Dabei ist ~di das binäre Komplement von di, und c die Checksumme über die Datenbytes, also c=(d1+..+dn) modulo 256. Ein gültiges Paket zum Versenden der Bytes f7h 12h sieht beispielsweise wie folgt aus.

55h ffh 00h f7h 08h 12h edh 09h f6h

Das erste Byte der Nutzdaten bestimmt, um was für ein Paket es sich handelt. Ist dessen Bit 7 nicht gesetzt, handelt es sich um einen Request, sonst um den dazugehörigen Reply. Eine Liste der gültigen Codes befindet sich im Internet auf der Seite http://graphics.stanford.edu/~kekoa/rcx/.

LNP Integrity Layer

Das Betriebssystem legOS setzt auf dieser Grundlage ein Protokoll auf, das für den Programmierer einfach zu nutzen ist. Ein Paket mit beliebigen Daten kann mit einem Aufruf von lnp_integrity_write an alle Empfänger in Reichweite verschickt werden (außer dem Sender selbst). Das Betriebssystem auf der Empfängerseite ruft einen Eventhandler des Benutzerprogramms auf, sobald das Paket komplett und fehlerfrei empfangen wurde.

LNP Adressing Layer

Eine Protokollebene höher ist der Adressing Layer angesiedelt. Mit ihm ist es möglich, gezielt einen Teilnehmer und einen Dienst anzusprechen. Das Protokoll arbeitet mit 8-Bit-Adressen, bei denen normalerweise die 4 oberen Bits die Host-Adresse und der Rest die Port-Adresse enthalten. Die Breite der beiden Felder wird über eine Bitmaske eingestellt.

Der Sender schickt per lnp_addressing_write seine Daten gezielt an einen Host und Port. Der Empfänger reagiert nur auf seine Host-Adresse und hat für jeden verwendeten Port einen Event-Handler, der von BrickOS beim Empfang eines Paketes aufgerufen wird. Dem Handler wird vom Absender noch die Hostadresse und der sendende Port mitgeteilt. Durch die zusätzlichen Informationen werden die Pakete auch größer und die Übertragung dauert länger als beim Integrity Layer.

Einfache Beispielprogramme für den LNP Addressing Layer:

 

Zu beachten:

Argumente

Die Funktion lnp_addressing_write nimmt als Argumente die Daten, die Länge der Daten, den Empfänger und den eigenen Port. Der Empfängerparameter ist ein unsigned char, welcher sowohl die host-Adresse in den oberen 4 bits als auch den Empfänger Port in den unteren 4 bits enthält Beispiel:

#define PORT_A  1
#define HOST_1  1
#define HOST_2  2
#define HOST_3  3
[...]
receiver = ((HOST_3 << 4) | PORT_A);
result = lnp_addressing_write(data, LEN_1,receiver,PORT_A);

Der eigene Port ist eigentlich egal, wird dem Empfänger aber mit übermittelt.

Eigene Adresse

Eigene Adresse setzen: Die eigene Adresse (nur 4 bits, also sind nur 16 Adressen möglich) wird entweder direkt im C-Programm über die Funktion lnp_set_hostaddr gesetzt

#define HOST_ME 3
[...]
lnp_set_hostaddr(HOST_ME);

oder über das dll Programm mit dem Parameter -n<num>:

dll -n3

Eigene Adresse herausfinden: Wenn man die Adressierung mit dll vornimmt, kann das C-Programm so geschrieben werden, dass es nicht nur für einen ganz bestimmten Host funktioniert, sondern über eine switch-case Anweisung die eigene Addresse ermittelt und eine Rolle (z.B. Sender oder Empfänger) zugeordnet bekommt. So haben alle Knoten grundsätzlich dasselbe Programm geladen, aber unterschiedliche Adressen. Nachteil: Das Programm ist dann größer und nimmt auf dem RCX mehr Speicher in Anspruch.

Die eigene Adresse ist in der Variablen lnp_hostaddr gespeichert, wobei die eigentliche Adresse hier schon auf die oberen 4 Bit verschoben wurde. Zum Lesen sollte also noch ein shift vorgenommen werden:

my_addr = (lnp_hostaddr >> 4);

Programmdownload mit geänderten Adressen: Es ist zu beachten, dass beim Programmupload mit dll auch immer addressiert übermittelt wird. Normalerweise fällt das aber nicht auf, da die RCX per default die Addresse 0 haben und auch dll mit dieser Defaultadresse arbeitet. Wird die eigene Adresse aber geändert, muss dll als Parameter -r nun diese Adresse mitbekommen:

dll -r3 lnp_addressing1.lx

Allgemeines

Die Protokolle haben allesamt einen sehr großen Overhead. Will man also eine große Datenmenge übertragen, sollten die Pakete nicht zu klein sein. Zu groß ist auf der anderen Seite auch nicht gut, weil ein Paket im Fehlerfall vom Benutzerprogramm nochmals übertragen werden muß. Hier gilt es also, einen guten Kompromiß zu finden.