|
Schnittstelle der LibraryDie Library setzt sich aus mehreren Modulen zusammen, die jeweils einer C-Datei entsprechen. Im Allgemeinen sind die Module relativ abgeschlossene Einheiten, die dem Programmierer den Zugriff auf einer bestimmten Ebene oder auf eine eng umrissene Funktionalität erlauben. Sie werden in den folgenden Abschnitten einzeln kurz skizziert. Viele Eigenschaften sind bei allen Funktionen gleich. Zeiten werden immer
in Mikrosekunden angebeben, die meisten Referenzparameter können auf
Modul firmwareAuf unterster Ebene enthält das Modul
Der Benutzer kommt mit dieser Funktionalität kaum direkt in Kontakt, sind für ihn vor allem die Konstanten für das Timing oder die Adressen der Fehlerzähler relevant. Modul nodelinkDie gemeinsame Schnittstelle aller Nodelinks wird in dem gleichnamigen Modul definiert. Eine Struktur dient als Handle und enthält die nötigen Daten für alle Typen von Nodelinks. Das Modul definiert selber keine Routine zum Öffnen der Verbindung und Initialisieren der Struktur, dafür sind die spezialisierten Varianten in anderen Modulen zuständig. Der einmal erstellte Nodelink kann von den übrigen Funktionen dieses Moduls benutzt werden, um unabhängig von der Art des Links zu kommunizieren und die Verbindung wieder schließen zu können. int nodelink_close(struct nodelink *link); int nodelink_send(struct nodelink *link, unsigned char *request, unsigned size); int nodelink_receive(struct nodelink *link, unsigned char *reply, unsigned *size, unsigned opcode, unsigned timeout); int nodelink_command(struct nodelink *link, unsigned char *request, unsigned requestsize, unsigned char *reply, unsigned *replysize); Die Funktion Häufig wird die Applikation nacheinander senden und empfangen
wollen. Dafür existiert als Abkürzung die Funktion
Auf einigen Netzwerken wie dem CAN-Bus sind nur sehr kleine Pakete erlaubt. Wenn eine serielle Schnittstelle auf der anderen Seite geöffnet werden soll, ist ihr vollständiger Name meistens zu lang, um in ein Paket zu passen. Daher wird statt des Namens ein entsprechender Code übermittelt. Zwei Funktionen bilden Code und Name aufeinander ab, die Liste der definierten Namen ist statisch im Modul gespeichert. int nl_devicecode(char *device, unsigned char *code); char *nl_devicename(unsigned char devicecode); Es werden mit Module nltty, nludp und nlcanDie Kommunikationsroutinen der unterschiedlichen Nodelinks sind jeweils in eigenen Modulen implementiert. Diese enthalten neben Funktionen zur Ansteuerung des Mediums jeweils die vier Basisroutinen des Nodelinks (open, close, send und receive). Jede Kommunikation beginnt mit dem Öffnen der Verbindung. struct nodelink *nodelink_tty_open(char *device); struct nodelink *nodelink_udp_open(char *hostname, char *remotedevice); struct nodelink *nodelink_can_open(struct pcan_interface *iface, unsigned remotenode, char *remotedevice); Die Parameter hängen von dem Medium ab. Bei lokalen seriellen Schnittstellen (tty) muß nur der Gerätename übergeben werden, Ethernet-Verbindungen (udp) erfordern zusätzlich den Namen des entfernten Rechners. Nodelinks über CAN können nur geöffnet werden, nachdem ein Handle für das lokale CAN-Interface zur Verfügung steht, zusätzlich werden die Nummer des entfernten Rechners und der Name der seriellen Schnittstelle auf der anderen Seite übergeben. Die so geöffneten Nodelinks können für die weitere Kommunikation benutzt werden. int nodelink_*_close(struct nodelink *link); int nodelink_*_send(struct nodelink *link, unsigned char *request, unsigned size); int nodelink_*_receive(struct nodelink *link, unsigned char *reply, unsigned *size, unsigned opcode, unsigned timeout); Es ist egal, ob die Funktionen zum Senden, Empfangen und Schließen
des eigenen Moduls oder die Gegenstücke aus dem Modul
Kommunikation auf den NetzwerkenDie Verwaltung der Verbindungen auf den Bussystemen Ethernet und CAN verläuft nach einem einfachen Schema. Zum Öffnen und Schließen einer seriellen Schnittstelle auf der anderen Seite wird ein Paket mit dem Code des Devices gesendet. Der betreffende Rechner antwortet mit einem Resultatcode, der den Erfolg oder eventuelle Fehler signalisiert. Die eigentlichen Datenpakete werden unverändert und nicht eingekapselt in beide Richtungen übertragen. Tritt auf dem entfernten Rechner ein Fehler auf, sendet er ein Paket mit einem entsprechenden Fehlercode. Das Protokoll unterscheidet Nachrichten anhand des ersten Bytes, Datenpakete beginnen mit dem Magic-Wert 0xF0, andere mit entsprechenden eigenen Nummern. Die Kommunikation über Ethernet basiert auf UDP, der Server auf den PC104-Rechnern ist über Port 8250 erreichbar. CAN arbeitet bei 250 KBit/s, Verbindungen zu Rechner n werden durch Befehle an die CAN-ID n geöffnet und geschlossen. Der Server weist jeder Verbindung eine freie ID zu, unter welcher der Austausch der Datenpakete erfolgt. Module nlsim und nllogWie anpassungsfähig das beschriebene Konzept ist, zeigen zwei
spezielle Module. Die von struct nodelink *nodelink_sim_open(struct nodesim *sim, unsigned delay); Die Ausführungszeit der Befehle wird bei der Erstellung des Nodelinks angegeben. Sie beträgt in der Regel 10000 Mikrosekunden plus die Laufzeit durch eventuelle nachzuahmende Netzwerke. Ein ähnlich wichtiges Werkzeug bietet das Modul struct nodelink *nodelink_log_open(char *logfile, char *infotext, int mode, struct nodelink *tolog); Die Ausgabe erfolgt in eine Textdatei, in der jedes Paket in einer Zeile dargestellt wird. Davor stehen ein Zeitstempel und ein frei wählbarer Infotext. Der Detailgrad kann auf mehreren Stufen bis hin zu textuell interpretierten Inhalten eingestellt werden. Modul nodeapiDas Erstellen von Befehlspaketen und das Interpretieren der Antworten
sind lästige Aufgaben, die das Modul int node_connect_send(struct nodelink *link, int reset); int node_connect_recv(struct nodelink *link, unsigned *channel, unsigned timeout); int node_connect(struct nodelink *link, int reset, unsigned *channel); Die erste Funktion erzeugt ein Befehlspaket und sendet es über den
Nodelink. Der Parameter Diese Aufteilung erlaubt es Programmen, auf zwei unterschiedliche Arten zu kommunizieren. Zunächst können sie mit einem Aufruf der dritten Funktion einen kompletten Befehl ausführen lassen. Dies ist vor allem dann interessant, wenn nur eine Leistungselektronik angesprochen werden soll. Bei vielen Nodelinks dauert es zu lange, den Vorgang mehrmals zu wiederholen. In solchen Fällen ist es sinnvoller, erst alle Befehle zu senden und dann die Antworten zu sammeln. Für diese Aufgabe sind die ersten beiden Funktionen konzipiert, die nebenläufige Ausführung mehrerer Befehle dauert kaum länger als ein einzelner Befehl. Das Modul übernimmt intern auch die Verwaltung der Sequenznummern. Ein Eintrag in der Nodelink-Struktur ist ein Zähler, der mit jedem gesendeten Paket inkrementiert wird. Beim Empfang der Antwort wird der gespeicherte Zähler als Referenz benutzt. Die Anwendungen müssen daher die Befehle zum Senden und Empfangen alternierend benutzen, was auch genau der intuitiven Reihenfolge entspricht. Zu jedem der elf API-Befehle existieren in dem Modul die drei Funktionen, dazu kommen einige Konstanten für Signalfarben, Motormodi und ähnliches, mit denen Programme lesbarer gestaltet werden können. Module kicking und ovalDie bisher vorgestellten Module reichen für eine einfache Steuerung der Bahn bereits aus. Das Programm kann einen Nodelink zu jedem beliebigen Steuerrechner unter der Bahn aufbauen und über die entsprechenden Befehle die Peripherie kontrollieren. Dabei stellt sich allerdings die Frage, welche Bauteile auf welchem Weg zu erreichen sind, denn deren Verkabelung folgt keinem berechenbaren Schema. Die Leitungen von der Oberseite der Bahn sind immer mit dem erstbesten Anschluß der nächstgelegenen Leistungselektronik verbunden.
Die Tabelle gibt die
Zuordnungen der Anschlüsse auf die Peripherie an und zeigt auch, welche
serielle Schnittstelle welches Rechners jeweils verantwortlich zeichnet. Sie
liegt in Form einer CSV-Datei vor und kann so automatisch ausgewertet
werden. Ein Skript generiert aus den Beschreibungen der Anlage und des
Testovals die beiden Module Die kleinsten Bestandteile der Beschreibung sind die Mapping-Arrays.
Jeder Eintrag in ihnen entspricht einer Zeile der CSV-Datei und damit einem
Peripherieteil. Die Felder enthalten die Nummern der Leistungselektronik und
des Anschlusses ( struct railway_mapping { unsigned node; unsigned connector; int block; int device; }; Die Header-Dateien der Module deklarieren Konstanten mit den Blocknamen, im Programmcode wird eine Struktur zur Beschreibung der Peripherie in mehreren Schritten definiert. Ihre Felder geben einige Eckdaten wie die Anzahl der Leistungselektroniken und die textuellen Namen der Blöcke an. Wichtiger sind jedoch die Variablen, welche die Anzahlen der Peripheriegeräte und die dazugehörenden Mapping-Arrays enthalten. struct railway_hardware { unsigned numnodes; unsigned numsignals; struct railway_mapping *signalmapping; unsigned numcontacts; struct railway_mapping *contactmapping; unsigned numtracks; struct railway_mapping *trackmapping; unsigned numpoints; struct railway_mapping *pointmapping; unsigned numlights; struct railway_mapping *lightmapping; unsigned numgates; struct railway_mapping *gatemapping; unsigned numgatesensors; struct railway_mapping *gatesensormapping; unsigned numbells; struct railway_mapping *bellmapping; unsigned numgatesignals; struct railway_mapping *gatesignalmapping; unsigned numblocks; char **blocknames; }; Will ein Programm beispielsweise auf das Signal am Ausgang von OC_ST_1
zugreifen, sucht es dazu im Mapping-Array Modul railwayIn der höchsten Hierarchiestufe der Library ist schließlich
das Modul Verwaltung des SystemsVor der Benutzung der Routinen muß eine Applikation die
Datenstrukturen initialisieren. Dabei wird die Hardwarebeschreibung
übergeben, hierbei handelt es sich um einen Zeiger auf eine der
Strukturen struct railway_system *railway_initsystem(struct railway_hardware *hardware); Als nächstes werden die Nodelinks zu allen an der Steuerung beteiligten Leistungselektroniken geöffnet und eingetragen. Dies kann manuell geschehen, dann müssen die Nodelinks nur noch mit der folgenden Funktion registriert werden. int railway_setlink(struct railway_system *railway, unsigned node, struct nodelink *link); Das Modul kann diese Aufgabe aber auch automatisch durchführen,
solange nur ein Bussystem für die Steuerung benutzt werden soll. Eine
Funktion stellt Nodelinks über den CAN-Bus her, es müssen nur der
Name des lokalen CAN-Interfaces und der Name der verwendeten seriellen
Schnittstelle auf den PC104-Knoten übergeben werden.
int railway_openlinks_can(struct railway_system *railway, char *candevice, char *device); int railway_openlinks_udp(struct railway_system *railway, char *hostformat, char *device); Wurden alle Nodelinks erfolgreich angelegt und registriert, startet
int railway_startcontrol(struct railway_system *railway, unsigned mincycle, unsigned maxcycle); Ob die Steuerung normal arbeitet oder durch nicht behebbare Fehler
zusammengebrochen ist, läßt sich mit einem Aufruf der Funktion
int railway_alive(struct railway_system *railway); Nachdem das Programm seine Aufgabe beendet hat, fährt
int railway_stopcontrol(struct railway_system *railway, int reset); Die noch offenen Nodelinks kann der Aufrufer mit int railway_closelinks(struct railway_system *railway); Zum Abschluß wird die Datenstruktur des Interfaces aufgelöst und der Speicher freigegeben. int railway_donesystem(struct railway_system *railway); Ansteuerung der HardwareEine Vielzahl von Funktionen steuert die Peripherieteile der Modellbahn. Dieser Abschnitt greift die wichtigsten exemplarisch heraus, im Modul sind noch wesentlich mehr vorhanden. Zum Setzen eines Signals werden die Adresse in Form von Blockname und
Nummer angegeben, dazu kommt eine Bitmaske mit den Signalfarben. Sie setzt
sich aus den drei Konstanten void setsignal(struct railway_system *railway, int block, int signal, int lights); Ähnlich wird der Fahrstrom für einen Block geschaltet. Der
Parameter void settrack(struct railway_system *railway, int track, unsigned mode, unsigned target); Die Kontakte werden über zwei Funktionen abgefragt. Mit
unsigned getcontact(struct railway_system *railway, int block, int contact, int clear); Die Funktion unsigned scancontact(struct railway_system *railway, int *block, int *contact, int clear); Die Kontakte dürfen nicht zu selten abgefragt werden. Ereignisse
werden zwar lange gepuffert, dafür speichert das System aber nur die
Richtung der aktuellsten Auslösung. Ältere liefern als Richtung
grundsätzlich nur
Weichen kennen zwei Zustände, void setpoint(struct railway_system *railway, int point, int state); Ob sich ein Triebwagen auf einem Gleisabschnitt befindet, ermittelt die
Funktion int trackused(struct railway_system *railway, int track); unsigned getspeed(struct railway_system *railway, int track); Die meisten Funktionen unterstützen Wildcards, um mehrere Bauteile gleichzeitig zu verändern. So schaltet beispielsweise der folgende Befehl alle Signale in allen Blöcken auf Rot. setsignal(railway,-1,-1,RED); DiagnosefunktionenEine Gruppe von Funktionen des APIs beschäftigt sich mit der
Diagnose von Systemfehlern, dazu werden die Fehlerzähler der
Leistungselektroniken im Rahmen eines Diagnosezyklus gelesen und später
ausgewertet. Den Zyklus leitet int railway_diagnostics(struct railway_system *railway, int function); Als Diagnosefunktionen kommen int diagstuckcontact(struct railway_system *railway, int *block, int *contact, int clear); int diagfailedcontact(struct railway_system *railway, int *block, int *contact, int *first, int *second, int clear); int diagshutdowntrack(struct railway_system *railway, int *block, int *count, int clear); int diagresetnode(struct railway_system *railway, int *node, int *mclr, int *wdt, int *bod, int clear); Alle von ihnen durchsuchen die im Diagnosezyklus ermittelten Daten nach Fehlern und ordnen diese Peripheriebauteilen auf der Modellbahn zu. Die Anwendung kann die Informationen dem Benutzer ausgeben und im Puffer wieder löschen. Der EEPROM kann anschließend durch einen eigenen Diagnosezyklus gelöscht werden. Weitere InformationenDie vorgestellten Funktionen stellen nur einen Ausschnitt aus dem API
dar, die vollständige Dokumentation aller Routinen befindet sich im
Quellcode. Erwähnenswert ist noch, daß im laufenden Betrieb
auftretende Fehler von Modul progbarDie Library benutzt eine Fortschrittsanzeige für einige Aufgaben wie
das Öffnen einer Gruppe von Nodelinks. Anwendungen können diesen
Code in Form des Moduls Progress [oooo................] Bei der Initialisierung werden die Breite der Anzeige in Zeichen sowie Minimum und Maximum der Werte angegeben, welche angezeigt werden sollen. void progbar_init(struct progbar *bar, unsigned width, int min, int max); Danach kann die Anzeige mit void progbar_show(struct progbar *bar, int value); Das Modul benutzt das Backspace-Zeichen, um zuvor ausgegebenen Text zu
überschreiben. Daher dürfen keine anderen Ausgaben zwischen zwei
Aufrufen von |