Digitale I/O-Ports verwenden

Der Beispiel-Code, mit dem wir zeigen werden, wie man Port-I/O von einem Gerät aus durchführt, arbeitet auf allgemeinen digitalen I/O-Ports; solche Ports gibt es in den meisten Computersystemen.

Ein digitaler I/O-Port ist in den meisten Fällen eine ein Byte breite I/O-Position, die entweder in den Speicher oder als Port abgebildet wird. Wenn Sie einen Wert an diese Stelle schreiben, wird das elektrische Signal an den Ausgabe-Pins entsprechend den geschriebenen Bits geändert. Wenn Sie einen Wert aus der Eingabe-Position auslesen, wird das aktuelle logische Niveau an den Eingabe-Pins als individuelle Bit-Werte zurückgegeben.

Die aktuelle Implementation und die Softwareschnittstelle solcher I/O-Ports unterscheidet sich von System zu System. Meistens werden I/O-Pins von zwei I/O-Positionen gesteuert: Eine erlaubt die Auswahl, welche Pins für Eingaben und welche für Ausgaben verwendet werden, und eine dient dem eigentlichen Lesen oder Schreiben der Daten. Manchmal ist die Sache aber noch einfacher, und die Bits sind entweder als Eingabe oder als Ausgabe fest verdrahtet (in diesem Fall kann man aber nicht mehr von “Universal-I/O” sprechen. So ein Fall ist der Parallel-Port, den es in allen PCs gibt. Auf jeden Fall sind die I/O-Pins aber mit dem gleich gezeigten Beispiel-Code verwendbar.

Grundlagen des Parallel-Ports

Da wir davon ausgehen, daß die meisten Leser eine x86-Plattform in Form eines “Personal-Computers” verwenden, lohnt es sich, kurz zu beschreiben, wie der PC-Parallel-Port aufgebaut ist. Der Parallel-Port ist eine sehr geeignete Peripherie-Schnittstelle, wenn es um Beispiel-Code für digitale I/O auf einem PC geht. Die meisten Leser werden wahrscheinlich die Spezifikationen des Parallel-Ports vorliegen haben, aber wir fassen sie hier trotzdem noch einmal zusammen.

Der Parallel-Port besteht in seiner minimalen Konfiguration (wir werden hier nicht auf die ECP- und EPP-Modi eingehen) aus drei 8-Bit-Ports. Im PC-Standard fangen die I/O-Ports der ersten parallelen Schnittstelle bei 0x378 an, die der zweiten bei 0x278. Der erste Port ist ein bidirektionales Datenregister, das direkt mit den Pins 2 bis 9 der physikalischen Schnittstelle selbst verbunden ist. Der zweite Port ist ein nur lesbares Statusregister. Wenn der Parallel-Port als Druckeranschluß verwendet wird, können aus diesem Register Aspekte des Druckerstatus wie etwa online, fehlendes Papier, beschäftigt usw. ausgelesen werden. Der dritte Port ist ein nur beschreibbares Register, mit dem unter anderem gesteuert wird, ob Interrupts erlaubt sind.

Die Signalniveaus, die in der parallelen Kommunikation verwendet werden, sind die Standard-Transistor-Transistor-Logik-(TTL-)Niveaus: 0 und 5; die Schwelle liegt bei etwa 1,2 Volt; Sie können sich darauf verlassen, daß die Ports zumindest die Standard-TTL-LS-Anforderung erfüllen[1], auch wenn die meisten modernen Parallel-Ports sowohl bei der Stromstärke als auch bei der Spannung besser sind.

Warnung

Der parallele Steckverbinder ist nicht von den internen Schaltkreisen des Computers isoliert, was nützlich ist, wenn Sie logische Gatter direkt mit dem Port verbinden wollen. Aber Sie müssen darauf achten, die Verkabelung korrekt vorzunehmen; es ist leicht, den Parallel-Port zu beschädigen, wenn Sie eigene Schaltkreise aufstecken (sofern Sie keine Opto-Isolatoren in Ihrem Schaltkreis verwenden). Sie können auch Parallel-Ports auf Steckkarten verwenden, wenn Sie Angst haben, Ihre Hauptplatine zu beschädigen.

Die Bit-Spezifikationen sind in Abbildung 8-1 wiedergegeben. Sie können auf zwölf Ausgabebits und fünf Eingabebits zugreifen, von denen einige auf ihrem Signalpfad invertiert werden. Das einzige Bit ohne zugeordneten Signal-Pin ist Bit 4 (0x10) an Port 2, mit dem Interrupts vom Parallel-Port eingeschaltet werden. Wir werden dieses Bit in Kapitel 9 verwenden.

Abbildung 8-1. Die Pinbelegung des Parallel-Ports

Ein Beispiel-Treiber

Der Treiber, den wir hier einführen werden, heißt short (Simple Hardware Operations and Raw Tests). Er liest und schreibt lediglich auf ein paar 8-Bit-Ports, beginnend bei dem, der beim Laden angegeben wurde. Defaultmäßig verwendet er den Port-Bereich der parallelen Schnittstelle des PCs. Jede Geräteknoten (mit einer eindeutigen Minor-Nummer) greift auf einen anderen Port zu. Der short-Treiber macht nichts Nützliches; er kapselt nur zur weiteren Verwendung eine einzige Anweisung, die auf einem Port arbeitet. Wenn Sie sich mit Port-I/O nicht auskennen, dann können Sie short verwenden, um sich damit vertraut zu machen; beispielsweise können Sie die Zeit messen, die es dauert, Daten durch einen Port zu transportieren, oder andere nette Dinge tun.

Damit short auf Ihrem System funktioniert, muß der Treiber freien Zugriff auf die zugrundeliegende Hardware (per Default die parallele Schnittstelle) haben; deswegen darf kein anderer Treiber die Hardware verwenden. Die meisten modernen Distributionen richten die Parallel-Port-Treiber als Module ein, die nur bei Bedarf geladen werden, so daß es normalerweise nicht zu einem Kampf um die I/O-Adressen kommen sollte. Wenn Sie aber die Fehlermeldung “can't get I/O address” von short bekommen (auf der Konsole oder im Systemprotokoll), dann hat sich wahrscheinlich ein anderer Treiber den Port geholt. Ein kurzer Blick in /proc/ioports sagt Ihnen normalerweise, welcher Treiber da im Wege ist. Das gleiche gilt auch für die anderen I/O-Geräte, wenn Sie die parallele Schnittstelle nicht verwenden.

Wir werden ab jetzt der Einfachheit halber immer von “der parallelen Schnittstelle” sprechen. Sie können aber den Modulparameter base beim Laden setzen, um short auf andere I/O-Geräte umzuleiten. Mit diesem Feature kann der Beispiel-Code auf jeder Linux-Plattform laufen, auf der Sie eine digitale I/O-Schnittstelle haben, auf die via outb und inb zugegriffen werden kann (selbst wenn die eigentliche Hardware auf allen Plattformen außer x86 in den Speicher abgebildet wird). Weiter unten, in “the Section called I/O-Speicher verwenden” werden wir Ihnen zeigen, wie man auch generische, in den Speicher abgebildete digitale I/O verwenden kann.

Um zu beobachten, was auf der parallelen Schnittstelle passiert (und wenn Sie daran interessiert sind, ein wenig mit Hardware zu arbeiten), sollten Sie jetzt ein paar LEDs an die Ausgabepins löten. Jede LED sollte mit einem 1KΩ Widerstand, der auf den Masseanschluß führt, in Serie geschaltet sein (sofern der Widerstand nicht schon in Ihre LEDs eingebaut ist). Wenn Sie einen Ausgabepin mit einem Eingabepin verbinden, können Sie eigene Eingaben erzeugen, die vom Eingabeport gelesen werden können.

Beachten Sie, daß Sie nicht einfach einen Drucker an den Parallelport anschließen und die an short geschickten Daten beobachten können. Dieser Treiber implementiert einen einfachen Zugriff auf die I/O-Ports und führt keinen Handshake durch, den Drucker benötigen, um mit den Daten zu arbeiten.

Wenn Sie parallele Daten visualisieren wollen, indem Sie LEDs an einen D-Stecker löten, würden wir vorschlagen, daß Sie Pin 9 und 10 zunächst frei lassen, weil wir diese später verbinden werden, um den Beispiel-Code aus Kapitel 9 auszuprobieren.

Was short angeht, so schreibt und liest /dev/short0 den 8-Bit-Port an der Position base (0x378 ist der Default). /dev/short1 schreibt auf den 8-Bit-Port an der Position base + 1 und so weiter bis base + 7.

Das von short bereitgestellte Gerät /dev/short0 schreibt Daten mit einer kurzen Schleife, die Byte für Byte Benutzerdaten mittels outb auf den Ausgabe-Port kopiert. Eine Speicherbarriere wird verwendet, um sicherzustellen, daß die Ausgabeoperation auch wirklich stattfindet und nicht wegoptimiert wird:


 
while (count--) {
    outb(*(ptr++), address);
    wmb();
}

Sie können mit dem folgenden Programm Ihre LEDs zum Leuchten bringen:



echo -n "beliebiger String" > /dev/short0

Jede LED überwacht ein einzelnes Bit des Ausgabe-Ports. Denken Sie daran, daß nur das letzte Zeichen so lange auf dem Ausgabepin bleibt, daß Sie es überhaupt mit den Augen wahrnehmen können. Aus diesem Grund ist es ratsam, das automatische Einfügen eines Zeilenumbruchs durch die Option –n von echo zu unterdrücken.

Das Lesen wird durch eine ähnliche Funktion erledigt, die um inb anstelle von outb herumprogrammiert ist. Um “aussagefähige” Werte vom Parallel-Port zu bekommen, müssen Sie Hardware an die Eingabepins anschließen, die Signale erzeugt. Wenn kein Signal vorliegt, lesen Sie einen endlosen Strom identischer Bytes. Wenn Sie von einem Ausgabe-Port lesen, bekommen Sie höchstwahrscheinlich den letzten auf den Port geschriebenen Wert zurück (dies gilt für die parallele Schnittstelle und die meisten anderen digitalen I/O-Schaltkreise, die allgemein verwendet werden). Diejenigen von Ihnen, die davor zurückschrecken, den Lötkolben hervorzuholen, können den aktuellen Ausgabewert an Port 0x378 auch mit folgendem Befehl lesen:


dd if=/dev/short0 bs=1 count=1 | od -t x1

Um Ein- und Ausgabe vollständig abzuhandeln, gibt es drei Variationen jedes short-Gerätes: /dev/short0 vollführt die gerade gezeigte Schleife, /dev/short0p benutzt outb_p und inb_p anstelle der “schnellen” Funktionen, und /dev/short0s benutzt die String-Anweisungen. Es gibt jeweils acht dieser Geräte, short0 bis short7. Die PC-Schnittstelle hat zwar nur drei Ports, aber Sie brauchen möglicherweise mehr, wenn Sie Ihre Tests mit einem anderen Gerät durchführen.

Der short-Treiber führt nur ein absolutes Mindestmaß an Hardware-Steuerung durch, ist aber trotzdem schon ausreichend, um vorzuführen, wie I/O-Port-Anweisungen verwendet werden. Interessierte Leser können sich den Quellcode der Module parport und parport_pc anschauen, um zu sehen, wie kompliziert dieses Gerät im wirklichen Leben werden kann, wenn es darum geht, unterschiedliche Geräte (Drucker, Bandlaufwerke, Netzwerkkarten) an der parallelen Schnittstelle unterstützen.

Fußnoten

[1]Low-Power-Schottky-TTL-Glieder (TTL-LS) nehmen nur ein Fünftel der Leistung eines Standard-TTL-Glieds auf.