 |
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.
Bild 1: Alle Ausgangs-Pins sind aus.
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.
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 ;-)
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.
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.
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/
Anzeigen:
|