Die Programmierung des Parallelports
Programmierung des Parallelports und Basteleien dafür

Einleitung

Der Parallelport ist eine parallele Schnittstelle, die hauptsächlich zum Anschliessen von Druckern verwendet wird.
Er eignet sich auch für andere Hardware, beispielsweise auch für JTAG-Adapter, mit denen in Mikrocontroller die Firmare programmiert wird und über den auch ein Debugger einen Mikrocontroller steuern kann.

Für den Hobby-Elektroniker ist der Parallelport besonders attratktiv, da er a) sehr leicht zu programmieren ist (keine Baudrate etc.), b) direkt LEDs und stromsparende Relais ansteuern kann sowie Taster auslesen kann, c) TTL-kompatibel ist, also sowohl 3,3 V- als auch 5 V-Logik angeschlossen werden kann und d) die Ansteuerung des Parallelports nur aus einfachen Portzugriffen besteht, die auch in harter Echtzeit und mit einer Genauigkeit von wenigen Mikrosekunden erfolgen kann, sowie e) interruptfähig ist.

Der Parallelport im Detail

Der Parallelport hat verschiedene Modi, die zudem unter mehreren Namen bekannt sind.
Die komplizierteren Modi, ECP, EPP, ECP+EPP usw., die Features wie Datenkompression und DMA verwenden, werden hier ebenso wenig betrachtet wie andere Plattformen als PC.
Stattdessen wird nur der so genannte Bidir-Modus (bidirektionale Modus, kurz Bi-direktional) und der Standard-Modus (Standards Parallel Port, kurz SPP) betrachtet, wobei der SPP nur der Spezialfall des Bi-direktionalen ist, der das Daten-Register nur als Ausgang verwendet. In Bi-direktionalen Modus sieht der Parallelport so aus:

Parallelport Anschlussbelegung


Die Register des Parallelports, über die der Parallelport gesteuert wird und über welche die an den Pins anliegenden Daten gelesen/geschrieben werden, sind 8-Bit-Register.

Bei der I/O-Basisadresse, gewöhnlich 888 = 0x378 (hexadezimal 378), ist das Register, über das ein Byte parallel geschrieben/gelesen werden kann. Daher werden die zugehörigen Pins Nummer 2 bis 9 auch als Daten-Port des Parallelports bezeichnet.
Es gibt also für den Datenport sowohl zum Lesen als auch zum Schreiben nur dieses Register, während andere Ports, insbesondere in Mikrocontrollern, mit einem separaten Register zum lesen und einem anderen separaten Register zum Schreiben realisiert sind.
Ob über dieses Register Daten von aussen eingelesen werden oder die Register stehenden Bits nach aussen gegeben werden sollen, entscheidet das Bit5 (=32=0x20) im Control-Register; ist es 1, dann ist der Daten-Port Eingang und ansonsten Ausgang.

Das Status-Register ist ein Eingang, zu dem die 5 Pins Nummer 10-13 und 15 gehören. Es befindet sich direkt über dem Daten-Register, also gewöhnlich bei der I/O-Adresse 889=0x379. Da nur fünf Pins hiermit verbunden sind, sind die unteren drei Bits ungenutzt.
Beim Pin 11, also Bit7, ist zu beachten dass der Wert invertiert ist, d. h. 0 V wird dort als 1 (gesetztes Bit7) ausgegeben und 2-5 V wird als 0 ausgegeben. Der Pin 10 ist interruptfähig, d. h. darüber kann ein Interrupt ausgelöst werden, der ein Hardware-Signal ist, das zum asynchronen Benachrichtigen dient.

Das Kontroll-Register ist ein Ausgang, zu dem die Pins 1, 14, 16 und 17 gehören. Es befindet sich direkt über dem Status-Register, also gewöhnlich bei der I/O-Adresse 890=0x37A Bis auf Pin 2 sind alle anderen Ausgangs-Pins invertiert, d. h. ein gesetztes Bit wird als 0 V ausgegeben. Über Bit4 (=0x10) wird gesteuert, ob der Parallelport einen Interrupt auslösen kann oder nicht.
Wie oben geschrieben, wird über Bit5 gesteuert, ob der Daten-Port als Eingang oder als Ausgang verwendet wird.

Die Pins können mit ca. 10 mA belastet werden und sind meist durch Reihenwiderstände kurzschulssfest, aber darauf sollte man sich nicht verlassen und vorsichtig vorgehen, also die Pins mit maximal 5 mA belasten, da ein defekter Parallelport meist nur durch den Tausch des Mainboards repariert werden kann.
Deshalb ist es empfehlenswert an jedem verwendeten Eingangs-Pin als Verpolungs- und Überspannungs-Schutz eine Zener-Diode (5,1 V, mind. 1 W) nach Masse anzuschliessen und an jedem verwendeten Ausgangs-Pin einen Reihenwiderstand von mindestens 100 Ohm zu verwenden.

Ansteuerung des Parallelports mit Software

Nach der Beschreibung der Hardware geht es nun um die Verbindung mit der Software.
Die Register des Parallelports befinden sich im so genannten I/O-Space, also nicht an gewöhnlichen Speicheradressen sondern in einem separaten Bereich, welcher der Kommunikation mit der Hardware dient. Deshalb werden diese Register auch Ports genannt. Diese Ports sind ebenso wie der gewönliche Speicher linear; d. h. statt zwei benachbarten 8-Bit-Zugriffen kann man einen 16-Bit-Zugriff machen usw.. Hierdurch kann man den Parallelport mit einem 32-Bit-Zugriff komplett beschreiben/auslesen. Dies spart Zeit, wenn über mehrere 8-Bit-Ports gleichzeitig Daten gelesen/geschrieben werden. Um die technischen Details wie das Zerlegen in mehrere kleine Zugriffe braucht man sich hierbei nicht zu kümmern; d. h. die 32-Bit-Zugriffe funktionieren auch bei 8-Bit-ISA-Karten.

Der Zugriff auf die Ports erfolgt mit speziellen Funktionen, beispielsweise in C für den Compiler gcc mit

in_byte = inb(port_adresse1);
outb(out_byte, port_adresse2);

zum Einlesen eines Bytes von Adresse port_adresse1 und zum Schreiben des Bytes out_byte an Adresse port_adresse2 unter Linux. Für 16-Bit-Zugriffe wird entsprechend inw bzw. outw verwendet und für 32-Bit-Zugriffe inl bzw. outl.
Diese Funktionen sind Compiler-spezifisch und auch Betriebssystem-spezifisch, auch bezüglich der Reihenfolge der Argumente beim Schreiben auf die Ports, denn Port-Zugriffe sind nicht standardisiert; sie fehlen beispielsweise beim ANSI-C.
Beispielsweise lauten die obigen Zeilen in Turbo-Pascal unter DOS/MS-Win

in_byte := port[port_adresse1];
port[port_adresse2] := out_byte;

Daten-Protokolle

Nach der physikalsichen Üertragung der Daten stellt sich die Frage nach dem Protokoll, mit dem die Daten eingelesen oder ausgegeben werden, denn dies ist im OSI-Schichtmodell die nächste Schicht.

Polling und Interrupt-Steuerung

Weil der Parallelport kein Taktsignal besitzt, muss die Datenübertragung mit Modusspezifischen Signalen synchronisiert werden. Beispielsweise verwendet der SPP zur Daten-Ausgabe an Drucker eine fallende Flanke an Pin 1. Die Daten werden dann vom Drucker über Polling oder interruptgesteuert eingelesen. Im Ersten Fall wird einfach der Eingang regelmässig und häufig abgefragt und im zweiten nur auf die Flanke reagiert. Das Empfangen der Daten kann ebenso erfolgen, wobei das Polling den Nachteil hat, dass es die CPU auch in den Wartezeiten deutlich belastet.

Gray-Code

Neben den beiden genannten Übertragungsarten gibt es zusätzlich noch die asynchrone Datenübertragung mittels Gray-Code, die ebenso wie die interrupt-gesteuerte Datenübertragung die CPU wenig belastet. Zunächst eine Einführung in den Gray-Code:
Der Gray-Code hat die Eigenschaft, dass sich von einem Code-Wort zu den beiden benachbarten (mit Wert +1, Wert -1), unten daneben) nur ein Bit ändert.
Hierdurch können langsam veränderliche Werte wie Zählerstände, Entfernungen oder Drehwinkel asynchron und exakt übertragen werden, da im Worst case während des Einlesens eines Gray-Code-Wertes ein Bit kippt. Weil ein während des Einlesen kippende Bit nicht definiert ist, ist das Ergebnis des Einlesens in jedem Fall korrekt und genau der Wert, der entweder unmittelbar vor oder nach dem Kippen vorhanden war. Schnell veränderliche Werte können mit dem Gray-Code aber nicht immer exakt übertragen werden; da ist allgemein nur eine Approximation möglich.

In der Praxis kann der Gray-Code daher beispielsweise zum Einlesen von AD-Wandler-Werten oder ausgeben von DA-Wandler-Werten verwendet werden, die sich nicht schnell ändern. Nicht schnell bedeutet beim Einlesen, dass sich während der einen Mikrosekunde, in der die Daten physikalisch eingelesen werden, maximal ein Bit ändert. Beim Ausgeben bedeutet nicht schnell maximal ein kippendes Bit in 2,5 Mikrosekunden, da die Daten mit (bis zu) 400 kHz ausgegeben werden können. Das "nicht schnell" ist also relativ und reicht beispielsweise noch zur Sprachaufzeichnung und Sprachwiedergabe aus.

Pin-Wackeln

Als letztes Übertragungs-Protokoll bleibt noch das einfache Pin-Wackeln zu nennen, bei dem die Pins asynchron, also ohne Synchronisations-Signal geschaltet werden, also "gelegentlich". Das Einlesen erfolgt ebenso asynchron. Dies ist die einfachste Daten-Übertragung.
Verwendet wird das Pin-Wackeln beispielsweise um über Status-LEDs Informationen auszugeben und den Zustand eines Schalters (offen/geschlossen) gelegentlich abzufragen.

Beispiel 1: DA-Wandlung mit 12 Widerständen

Während zum Einlesen von Analog-Werten ein AD-Wandler mit Stromversorgung usw. nötig ist, ist das Ausgeben von Analog-Werten sehr einfach, wenn 13 Analog-Stufen, also 13 DA-Wandler-Ausgabewerte, und ein paar Prozent Ungenauigkeit reichen. Dafür wird hier der Daten-Port auf Ausgang geschaltet und zwischen dem Ausgang und jeden Daten- und jeden Kontrollport-Pin ein Widerstand von 1 kOhm geschaltet (Bilder unten). Hierdurch ist der Ausgangswiderstand konstant und beträgt rund 100 Ohm, reicht also beispielsweise zum direkten Ansteuern einer LED (rote LED in der Mitte der Bilder unten).
Bei dem einfach DA-Wandler unten ist zusätzlich an jedem der 12 Ausgangs-Pins eine LED mit integriertem Vorwidertand für den Betrieb an 5 V, um so anzuzeigen, welche Pins auf high und welche auf low geschaltet sind.

DA-Wandler mit LEDs, alle LEDs aus
Bild 1: Alle Ausgangs-Pins sind aus.

DA-Wandler mit LEDs, 6 LEDs ein
Bild 2: Die Hälfte der Ausgangs-Pins ist ein. Hier, und auch beim nächsten Bild, sieht man dass die Kontroll-Pins, angeschlossen an die 4 rechten LEDs, bei diesem Parallelport deutlich schwächer als die anderen (=Daten-Pins) sind.

DA-Wandler mit LEDs, alle 12 LEDs ein
Bild 3: Alle Ausgangs-Pins sind ein.

Das Programm hierzu, osci.c, ist ein C-Programm für Linux, kann aber leicht portiert werden, da nur die Funktionen für die Port-Zugriffe angepasst werden müssen.

Das Programm osci.c im Detail

Nach einige Makros und einer Union-Deklaration zur Vereinfachung folgt der eigentliche Sourcecode, der nur in main steht.
Zuerst wird dort der eine Parameter, die Basis-Adresse des Parallelports (meist 888), eingelesen und eine Fehlermeldung ausgegeben, wenn dies fehlschlägt. Zum Einlesen wird strtol genommen, damit sowohl dezimale also auch hexadezimale Zahlen eingelesen werden können.
Als nächstes kommt die Abfrage der User-ID, da generelle nur root ausreichende Rechte hat, um auf die I/O-Ports direk zugreifen zu können. Hiernach werden die Zugriffe auf die I/O-Ports mittels iopl(3) freigeschaltet. Nach diesen Vorbereitungen wird nun auf den Parallelport zugegriffen: Zunächst wird über setzten der Bits 4 und 5 auf 0 der Daten-Port auf Ausgang ohne Interrupt geschaltet.
Anschliessend werden in einer Endlos-Schleife die Bits 0..7 des Datenports gesetzt und die Bits 0..4 des Kontroll-Ports gesetzt, bis alle 12 Pins auf 1 (=high) geschaltet sind. Hierfür müssen wegen den invertierten Ausgängen am Kontroll-Port die Bits noch mit dem Invertierungs-Bitmuster exklusiv verodert werden.
Hiernach werden die Bits in umgekehrter Reihenfolge gelöscht, so dass sich hier zusätzlich zur DA-Wandlung auch noch ein Lauflicht ergibt. Als letztes ist mit sleep(1) eine Wartezeit von einer Sekunde eingebaut, damit man das Setzen und Löschen der Bits direkt sehen kann und kein Oszilloskop braucht.
Auf den Parallelport wird in der Endlos-Schleife mit einem 32-Bit-Schreibzugriff zugegriffen, da hier ja nur geschrieben wird und nur je ein Schreibzugriff nötig ist. Hierfür wird eine 32-Bit-Variable genommen, die mit den schon vorhandenen Werten des Parallelports initialisiert wird, damit es nicht passieren kann, dass versucht wird read-only-Bits zu kippen.

Es gibt seit Längerem auch Sound-"Karten", die so ähnlich Sound ausgeben und DA-wandeln. Wegen der schlechten Qualität und hohen CPU-Belastung haben sie aber keine weite Verbreitung erlangt.
Für einen einfachen Funktionsgenerator reicht das Prinzip aber allemal aus.

Beispiel 2: Ergometer- und Sportler-Simulator

Bei diesem Beispiel wird für das Steuerteil eines Ergometers sowohl das Ergometer als auch der Sportler simuliert.
Hierfür wird mit dem Programm, parda2.c, sowohl das Umdrehungs-Signal (RPM, rounds per minute) als auch das Herzschlag des Trainierenden (PPM, pulses per minute) simuliert und zudem gleichzeitig das Puls-Pause-Verhältnis und die Frequenz bestimm, mit der die Wirbelstrombremse des Ergometers angesteuert wird. Hierzu werden Posix-Threads genommen, die den Daten-Port, den Status-Port und den Kontroll-Port jeweils separat verwenden, um so den Code orthogonal zu halten und die jeweiligen Frequenzen völlig unabhängig von einander wählen zu können.
Allgemein kann man natürlich auch je Bit/Pin einen Thread verwenden und so bis zu 12 Ausgangs-Thread und bis zu 13-Eingangs-Threads je Parallelport verwenden, und so ein halbes Dutzend Ergometer über einen Parallelport simulieren. Mit mehrenren Dual-Parallelport-Karten kann man so mit nur einem PC über Hundert Ergometer billig simulieren.

Da diese simulierten Daten bei einer guten Simulation, also etwas aufwendiger als mit dem Programm parda2.c, nicht von echten Traninigs-Daten unterschieden werden können, kann man so bei einem unüberwachten Training einfach vom Programm trainieren lassen. Eine Anwendung sind Wettrennen mit einer Datenverbindung über das Internet; da kann man auch einen Jan Ulrich leicht abhängen, selbst wenn man das Wettrenn-Programm auf dem PC nicht manipulieren kann und ein Original-Ergometer benötigt wird. In das Simulations-Programm muss dafür natürlich auch eine realistische Begrenzung eingebaut werden, denn Radfahren mit Überschall ist leicht simuliert, aber unrealistisch ;-)
Steuerteil eines Ergometers mit ein paar Kabeln
Bild 4: Die Rückseite eines Steuerteils (Cockpits) eines Ergometers von www.ergobike.de, das einen MSP430 als zentralen Mikrocontroller hat.
Bei anderen Ergometern sind nur andere Anschluss-Punkte zu nehmen, die man mit entweder direkt oder mit einem Digitalmultimeter (mit Frequenzmessung) leicht findet. Bei viel komplizierteren Geräten findet man im Notfall mit einem Oszilloskop heraus, welche Signale wo anliegen.
Die Stromversorgung erfolgt hier über einen Adapter und eine USB-Kabel (Kabel unten) vom USB-Bus, um so ein Netzteil einzusparen und die Massen-Verbindung zum PC herzustellen. Zur Sicherheit sind in die Stomversorgungsleitung eine Zenerdiode und ein Kondensator (rot) eingebaut.
Die drei simulierten Signale, Herzschlag (weiss), Umdrehungszahl (grün) und PWM-Ausgang (gelb), werden über Schnellverbinder zum Parallelport geleitet. Zudem wurde für den PWM-Ausgang ein Pullup-Widerstand auf die Platine gelötet (blauer Widerstand Mitte unten) und der Funkempfänger für das Pulssignal wurde oben links ausgelötet und unten rechts abgelegt. Der geöffnete Parallelport-Stecker, oben links am Verlängerungs-Kabel, zeigt, dass der Ausgang 2 an den Parallelport aus je einem Schutzwiderstand von 330 Ohm zwischen Ausgang und Kontroll-Port-Pin besteht.
Der Ausgang 1 ist am Daten-Port, gleich konstruiert und mit grünem Gewebe-Klebeband isoliert. Die Eingänge, am Status-Port, sind mit einem Kabel zu weissen Lüsterklemmen-Leiste geführt. Zur Anzeige der Ausgangs-Signale sind an der schwarzen Lüsterklemmen-Leiste zwei LEDs angebracht, zusammen mit Pull-Down-Widerständen, für saubere Low-Pegel, da einige Steuerteil-Eingänge Pullup-Widerstände haben.
Die simulierten Trainings-Daten werden in den Anzeigen der Vorderseite angezeigt und über die serielle Schnittstelle (grosser schwarzer Stecker in der Mitte) zum PC geleitet.

Das Programm hierzu, parda2.c, ist ein C-Programm für Linux, kann aber leicht portiert werden, da nur die Funktionen für die Port-Zugriffe angepasst werden müssen.

Das Programm parda2.c im Detail

Das Einlesen der Parameter, Freischalten der I/O-Ports und Einstellen des Daten-Ports auf Ausgang und ohne Interrupt erfolgt wie beim vorherigen Beispiel. Anschliessend wird von main ein Thread für den Kontroll-Port zum Ausgeben und ein Thread für den Status-Port zum Einlesen gestartet.
Anschliessend arbeiten diese Threads parallel auf dem Daten-, Status- und Kontroll-Port, wobei main als dritter Thread und auf dem Daten-Port arbeitet.
Die drei Threads arbeiten jeweils in einer Endlos-Schleife, in die mittels usleep eine Wartezeit eingebaut ist.

Der main-Thread schaltet hierbei die Pins abwechselnd auf high und low, so wie auch der Thread func_control, der auf dem Kontroll-Port die Pins schaltet. Zudem wird über einen Paramter eine Bit-Maske der Pins angegeben, die auf 1 geschaltet werden; alle anderen Pins bleiben auf 0. Hiermit wird die Ausgangs-Amplitude gesteuert.
Der Main-Thread erzeugt das RPM-Signal, während func_control das PPM-Signal erzeugt.

Der Thread func_status liest den Status-Port häufig aus, um über die Statistik der eingelesenen Nullen und Einsen an Pin 15 das Puls-Pause-Verhältnis zu bestimmen. Zudem wird die Frequenz ganz einfach als Anzahl der steigenden Flanken je Sekunde berechnet. Diese beiden Werte werden, zusammen mit der Sample-Frequenz, mit welcher der Status-Port im Mittel ausgelesen wird, sekündlich ausgegeben.

Ein Beispiel-Aufruf des Programms für den Onboard-Parallelport und 106 RPM sowie 175 PPM ist:

./parda2 888 255 20000 15 169000 0

Die sechs Paramter sind im Programm-Header erläutert.

Links

I/O-Ports unter Linux:
http://www.linux-magazin.de/Artikel/ausgabe/1999/10/IO/io.html

Linux I/O port programming mini-HOWTO:
http://www.tldp.org/HOWTO/IO-Port-Programming.html

ANSI/ISO-C:
http://webstore.ansi.org/ansidocstore/product.asp?sku=INCITS%2FISO%2FIEC+9899%2D1999

Serial Programming HOWTO:
http://www.faqs.org/docs/Linux-HOWTO/Serial-Programming-HOWTO.html

Serial Programming Guide for POSIX Operating Systems:
http://www.easysw.com/~mike/serial/serial.html

Buch "Linux-Treiber entwickeln, Eine systematische Einführung in Gerätetreiber für den Kernel 2.6, kostenlos und komplett online:
http://ezs.kr.hsnr.de/index.php?id=35&type=1

Buch Linux Gerätetreiber (Engl. Linux Device Drivers):
http://www.linux-magazin.de/Service/Books/Buecher/HW-Treiber/book1.html

Harte Echtzeit-Erweiterung RTAI für den Linux-Kernel:
http://www.rtai.org/

Linuxmagazin 3/2003: Artikel "Hexapoden-Steuerung: Kernelmodul für den Prüfstand":
http://www.igh-essen.com/pdf/hexapod-linuxmagazin.pdf

Posix-Threads: Buch "Pthreads Programming"

Posix-Threads-Tutorial:
http://www.llnl.gov/computing/tutorials/pthreads/

Ausblick

Bisher wurden nur einfache Anwender-Programme behandelt, also Programme im User-Space.
Geräte-Treiber befinden sich aber meist beim Betriebssystem, im so genannten Kernel-Space. Dies hat mehrrere Gründe, beispielsweise den, dass generell nur dort Interrupts bearbeitet werden können und auch dass meist ein Treiber einige Funktionalitäten exportiert und importiert. Beispielsweise braucht ein Treiber für eine Uhr an einem I2C-Bus die Funktionen des I2C-Busses und diese müssen vom Betriebssystem kontrolliert werden, damit z. B. nur ein Treiber auf die Uhr am I2C-Bus zugreift, also kein Chaos entsteht.
Zu den Kernel-Space-Treibern findet man weitere Informationen bei den Links.

Mit einem Betriebssystem mit harter Echtzeit, z. B. Linux+RTAI (www.rtai.org), kann der Parallelport mit einer Genauigkeit von einigen Mikrosekunden angesteuert werden. Damit sind viele Anwendungen wie z. B. die genaue Ansteuerung eines Servos und das Zählen von TTL-Pulsen/-Frequenzen über den Parallelport-IRQ möglich.
Bei dem Beispiel mit der Ergometer-Simulation ist dies sehr nützlich, da a) die Schwankungen der simulierten Signale sehr klein gehalten werden können und b) die Signale quarzgenau, also mit einer Genauigkeit von 1:100.000 ausgegeben und eingelesen werden. Ohne harte Echtzeit zeigen sich nämlich Schwankungen von mehreren Prozent, je nach Auslastung des Rechners.

Neueste Artikel
Anzeigen:
Aktuelle Newsbeiträge
Sie sind Besucher Nr. 1139541