====== paralleler Port ====== Für den Hobby-Elektroniker ist der Parallelport besonders attratktiv, da er: - sehr leicht zu programmieren ist (keine Baudrate etc.), - direkt LEDs und stromsparende Relais ansteuern kann sowie Taster auslesen kann, - TTL-kompatibel ist, also sowohl 3,3 V- als auch 5 V-Logik angeschlossen werden kann und - die Ansteuerung des Parallelports nur aus einfachen Portzugriffen besteht, die auch in harter Echtzeit und mit einer Genauigkeit von wenigen Mikrosekunden erfolgen kann, sowie - interruptfähig ist. Über einen gewöhnlichen Parallelport können die Pins mit bis zu 400 kHz geschaltet werden und Interrupts ebenso schnell eingelesen werden. ===== Der Parallelport ===== Ein bidirektionaler PC-Parallelportbaustein besitzt drei Register: - Mit dem **Datenregister** (befindet sich an der Basisadresse, z.B. 0x378) können acht Bit gleichzeitig auf die Datenleitung D0-D7 ausgegeben bzw. eingelesen werden (fals Bit 5 im Control-Register gesetzt ist). - Mit dem **Statusregister** (=Basisadresse+1) können die Zustände der Eingangsleitungen abgefragt werden. Allerdings ist der BUSY-Eingang (Pin 11) invertierend, d.h. ein anliegendes Low-Signal hat ein gesetztes Bit 7 im Status-Register zur Folge und umgekehrt. Bei einer Low-to-High-Transition an Pin 10 (/ACK) wird der (per Jumper oder BIOS eingestellte) Interrupt ausgelöst - aber nur unter der Vorraussetzung, dass Bit 4 (Interrupt Enable) im Steuerregisater vorher gesetzt wurde. - Mit dem **Steuerregister** (control register) (=Basisadresse+2) lassen sich weitere vier TTL/CMOS-Ausgänge ansteuern - davon verhalten sich drei invertierend. Ausserdem beherbergt das Steuerregister (wie bereits erwähnt) jene Bits, um die Datenleitung umzukehren bzw. den Interrupt freizuschalten. **Bei allen Experimenten am Parallelport ist höchste Vorsicht geboten weil (bei unsachgemässer Handhabung) lebensgefährliche Kurzschlüsse für den Parallelport oder gar für das gesamte Mainboard entstehen können.** Möchte man Taster oder Schalter direkt abfragen, so müssen die Eingänge über Pull-Up-Wiederstände in einen definierten High-Zustand gebracht werden und können dann gezielt auf Masse kurzgeschlossen werden. Falls keine externen +5V Spannungsquellen für die Pull-Up-Wiederstände zur Verfügung stehen, so kann kurzerhand einer der TTL/CMOS-Ausgänge als Spannungsquelle missbraucht werden. ^ Pin-Name ^ Pin ^ Funktion ^ | Data Register D0 | Pin 02 | Ein/Ausgang Bit 0 | | Data Register D1 | Pin 03 | Ein/Ausgang Bit 1 | | Data Register D2 | Pin 04 | Ein/Ausgang Bit 2 | | Data Register D3 | Pin 05 | Ein/Ausgang Bit 3 | | Data Register D4 | Pin 06 | Ein/Ausgang Bit 4 | | Data Register D5 | Pin 07 | Ein/Ausgang Bit 5 | | Data Register D6 | Pin 08 | Ein/Ausgang Bit 6 | | Data Register D7 | Pin 09 | Ein/Ausgang Bit 7 | | | | | | Status Register S3 /ERROR | Pin 15 | Eingang | | Status Register S4 SELECT | Pin 13 | Eingang | | Status Register S5 PAPEREND | Pin 12 | Eingang | | Status Register S6 /ACK | Pin 10 | Interruptfähiger Eingang (L->H) | | Status Register S7 (inv) BUSY | Pin 11 | invertierender Eingang | | | | | | Control Register C0 (inv) /STROBE | Pin 01 | invertierender Ausgang Bit 0 | | Control Register C1 (inv) /AUTOFD | Pin 14 | invertierender Ausgang Bit 1 | | Control Register C2 /INIT | Pin 16 | Ausgang Bit 2 | | Control Register C3 (inv) /SELECT | Pin 17 | invertierender Ausgang Bit 3 | | Control Register C4 | Pin -- | Interrupt: 0=aus, 1=an Bit 4 | | Control Register C5 | Pin -- | D0-D7: 0=Ausgang, 1=Eingang Bit 5 | | | | | | Masse | Pin 18 | | | Masse | Pin 19 | | | Masse | Pin 20 | | | Masse | Pin 21 | | | Masse | Pin 22 | | | Masse | Pin 23 | | | Masse | Pin 24 | | | Masse | Pin 25 | | ^ Pin No (DB25) ^ Signal name ^ Direction ^ Register - bit ^ Inverted ^ | 1 | nStrobe | Out | Control-0 | Yes | | 2 | Data0 | In/Out | Data-0 | No | | 3 | Data1 | In/Out | Data-1 | No | | 4 | Data2 | In/Out | Data-2 | No | | 5 | Data3 | In/Out | Data-3 | No | | 6 | Data4 | In/Out | Data-4 | No | | 7 | Data5 | In/Out | Data-5 | No | | 8 | Data6 | In/Out | Data-6 | No | | 9 | Data7 | In/Out | Data-7 | No | | 10 | nAck | In | Status-6 | No | | 11 | Busy | In | Status-7 | Yes | | 12 | Paper-Out | In | Status-5 | No | | 13 | Select | In | Status-4 | No | | 14 | Linefeed | Out | Control-1 | Yes | | 15 | nError | In | Status-3 | No | | 16 | nInitialize | Out | Control-2 | No | | 17 | nSelect-Printer | Out | Control-3 | Yes | | 18-25 | Ground | - | - | - | * Spannung: 5,0 V (5,5 V) * Strom: 5-20 mA {{:parallel_db25.gif|}} **Port-Adressen:** - **Datenregister (D0-D7)**: Basis-Adresse (Druckerdaten ausgeben) - **Statusregister (S0-S7)**: Basis-Adresse + 1 Byte (Druckerstatus lesen) - **Steuerregister (C0-C7)**: Basis-Adresse + 2 Byte (Druckerfunktionen steuern) ===== Parallel Port Programmierung mit Pascal ===== => ISBN 3-928051-42-3 Datenregister (D0-D7) ausgeben: Port [BA] := n; Statusregister (S0-S7) einlesen: A := Port [BA+1]; Steuerregister (C0-C7) ausgeben: Port [BA+2] := A XOR 11; Steuerregister (C0-C7) einlesen: B := (Port [BA+2] := A XOR 11) AND 15; ==== sinusgenerator.pas ==== Das Ausgangssignal dieser Schaltung kann z.B. mit dem A/D-Wandler "ZN426" in ein NF-Sinussignal umgewandelt werden. Hiermit kann man den Frequenzbereich bis max. 20 kHz abdecken. Program Sinusgenerator; uses CRT; const BA = $378; var Tabelle : Array [0..255] of Byte; Faktor : Byte; Ch : Char; procedure SinusTabelle; var n: Integer; begin for n:= 0 to 255 do Tabelle [n] := round (127.5 + 127.5 * sin (n / 128 * Pi)); end; procedure Ausgabe (Faktor : Byte); var n: Byte; m: Word; begin repeat Inline ($FA); n := 0; for m := 1 to 65535 do begin; Port [BA] := Tabelle [n]; n:= n + Faktor; end; Inline ($FB); until KeyPressed; Ch := ReadKey; end; begin SinusTabelle; repeat writeln ('Bitte Faktor eingeben'); readln (Faktor); writeln ('Unterbrechen mit irgendeiner Taste'); writeln ('Ende mit ESC'); Ausgabe (Faktor); until Ch = chr(27); end. ===== Parallel Port Programmierung mit C ===== * [[http://www.loetstelle.net/praxis/parallelport/parallelport.php]] * [[http://www.loetstelle.net/content/praxis/parallelport/software/parda2.c]] * [[http://www.asamnet.de/~tobolaan/elektronik/optrex/optrex_0_0.c.html]] ==== Linux ==== ursprüngliche Quelle: (aus dem LINUX-Magazin vom Oktober 1999, Seite 34-36) Bei der Ausführung dieses Beispiels auf der Komandozeile müssen zwei Argumente übergeben werden (z.B. mit **iotest 378 85**). Der erste Wert ist die Basis-Adresse eines freien Parallelports. Das zweite Argument wird als Bitmuster verwendet, welches an die Datenleitungen des Parallelports angelegt wird. In der Headerdatei asm/io.h (heißt jetzt sys/io.h) befinden sich die Deklarationen für die benötigten Funktionen ioperm(), inb() und outb(). In den Zeilen mit "atoi" werden die beiden übergebenen Programmparameter (I/O-Basisadresse und Bitmuster) ausgelegt. Der angegebene I/O-Bereich wird durch den Aufruf von ioperm(base,3,1) freigeschaltet: ab der Basisadresse wird der Zugriff auf die nächsten drei I/O-Ports (z.B. 0x378-0x37a) erlaubt, da der dritte Parameter von ioperm(base,3,**1**) **"1"** ist. In der darauffolgenden Programmzeile wird das angegebene Bitmuster (hier für "85" => "01010101") an die Datenleitungen des Druckeranschlusses ausgegeben. Bei einem angeschlossenen Centronics-Relaisinterface wäre bei unserem obigen Kommandozeilen-Beispiel jedes zweite Relais geschaltet. In der Zeile mit inb(base+1) wird ein Byte eingelesen, welches die anliegenden TTL bzw. CMOS-Pegel an den Eingangs-Pins (Status-Register) repräsentiert. Dadurch können z.B. bis zu fünf Schalterzustände abgefragt werden. Schließlich muss der direkte Zugriff auf die Ports mit Hilfe einer **"0"** als letztem Parameter von ioperm(base,3,**0**) wieder verboten werden. Die Basisadressen (erster Übergabeparameter) der Parallelports ("Standard", muss aber nicht immer so sein!): * LPT1 -> **378** * LPT2 -> **278** * LPT3 -> **3F8** === iotest.c === // Sourcecode for Linux gcc #include #include #include #include int main(int argc, char* argv[]) { if (argc != 2 && argc != 3) { printf("\n%s [Gerätedatei]\n", argv[0]); printf("z.B. Pegel der Status- und Daten-Pins anzeigen: %s 378\n", argv[0]); printf("\n%s [Gerätedatei] [Wert]\n", argv[0]); printf("z.B. alle Daten-Pins an schalten: %s 378 85\n", argv[0]); printf("z.B. alle Daten-Pins aus schalten: %s 378 0\n", argv[0]); exit(1); } int base=atoi(argv[1]); int vbase=0; int nbase=0; int status=0; if (argc == 2 || argc == 3) { // Parallelport öffnen if (ioperm(base,3,1) < 0) { perror("Fehler: Parallelport kann nicht geöffnet werden\n"); } if (argc == 3) { int value=atoi(argv[2]); // Datenregister lesen (Basis-Port-Adresse) => Ein-/Ausgang vbase=inb(base); // Datenregister schreiben (Basis-Port-Adresse) => Ein-/Ausgang outb(value,base); // Steuerregister schreiben (Basis-Port-Adresse + 2 Byte) => Ausgang outb(0,base+2); } // Datenregister lesen (Basis-Port-Adresse) => Ein-/Ausgang nbase=inb(base); // Statusregister lesen (Basis-Port-Adresse + 1 Byte) => Eingang status=inb(base+1); // Parallelport schließen if (ioperm(base,3,0) < 0) { perror("Fehler: Parallelport kann nicht wieder geschlossen werden\n"); } if (argc == 2) { // Bildschirmausgabe printf("%i / %i\n", status, nbase); } if (argc == 3) { // Bildschirmausgabe printf("%i / %i -> %i\n", status, vbase, nbase); } } }; == iotest anwenden == früher (mit asm/io.h): # gcc -O2 -o iotest iotest.c Heutzutage ist für das compilieren ist die Compiler-Optimierungsstufe -O2 ist nicht zwingend notwendig, früher musste aber mindestens "-O" angegeben werden, da sonst die Code-Generierung mit einer lästigen Fehlermeldung abgebrochen wurde. heute (mit sys/io.h): # gcc -o iotest iotest.c Das fertige Binary kann nun so aufgerufen werden: # ./iotest ./iotest [Gerätedatei] z.B. Pegel der Status- und Daten-Pins anzeigen: ./iotest 378 /home/manfred/iotest [Gerätedatei] [Wert] z.B. alle Daten-Pins an schalten: ./iotest 378 85 z.B. alle Daten-Pins aus schalten: ./iotest 378 0 # ./iotest 378 255 / 255 # ./iotest 378 85 255 / 255 -> 85 unter der Voraussetzung dass man als **root** eingeloggt ist. Für den Übergabewert sind Werte von 0 bis 255 erlaubt. Die Zahl vor dem "/" gibt den Inhalt des Status-Registers wieder. ==== FreeBSD ==== === pptest.c === // Sourcecode for FreeBSD clang #include #include #include #include #include #include int ppi_port; int main(int argc, char *argv[]) { if (argc != 2 && argc != 3) { printf("\n%s [Gerätedatei]\n", argv[0]); printf("z.B. Pegel der Status- und Daten-Pins anzeigen: %s /dev/ppi0\n", argv[0]); printf("\n%s [Gerätedatei] [Wert]\n", argv[0]); printf("z.B. alle Daten-Pins an schalten: %s /dev/ppi0 255\n", argv[0]); printf("z.B. alle Daten-Pins aus schalten: %s /dev/ppi0 0\n", argv[0]); exit(1); } char ppi_dev[64]; u_int8_t status=0; u_int8_t vdaten=0; u_int8_t ndaten=0; strlcpy(ppi_dev, argv[1], sizeof(ppi_dev)); // Parallelport öffnen ppi_port = open(ppi_dev, O_RDWR); if (ppi_port < 0) { printf("Could not open %s\n", ppi_dev); exit(1); } if (argc == 2 || argc == 3) { if (argc == 3) { u_int8_t value=0; value = atoi(argv[2]); // Datenregister lesen (Basis-Port-Adresse) => Ein-/Ausgang ioctl(ppi_port, PPIGDATA, &vdaten); // Datenregister schreiben (Basis-Port-Adresse) => Ein-/Ausgang ioctl(ppi_port, PPISDATA, &value); // Steuerregister schreiben (Basis-Port-Adresse + 2 Byte) => Ausgang ioctl(ppi_port, PPISCTRL, 0); } // Statusregister lesen (Basis-Port-Adresse + 1 Byte) => Eingang ioctl(ppi_port, PPIGSTATUS, &status); // Datenregister lesen (Basis-Port-Adresse) => Ein-/Ausgang ioctl(ppi_port, PPIGDATA, &ndaten); // Bildschirmausgabe if (argc == 2) { printf("%u / %u\n", status, ndaten); } if (argc == 3) { printf("%u / %u -> %u\n", status, vdaten, ndaten); } } return 0; } == pptest anwenden == # clang -o pptest pptest.c Das fertige Binary kann nun so aufgerufen werden: # ./pptest ./pptest [Gerätedatei] z.B. Pegel der Daten-Pins anzeigen: ./pptest /dev/ppi0 ./pptest [Gerätedatei] [Wert] z.B. alle Daten-Pins an schalten: ./pptest /dev/ppi0 255 z.B. alle Daten-Pins aus schalten: ./pptest /dev/ppi0 0 # ./pptest /dev/ Could not open /dev/ # ./pptest /dev/ppi0 127 / 0 # ./pptest /dev/ppi0 85 127 / 0 -> 85 # ./pptest /dev/ppi0 127 / 85 # ./pptest /dev/ppi0 0 127 / 85 -> 0 # ./pptest /dev/ppi0 127 / 0 unter der Voraussetzung dass man als **root** eingeloggt ist. Für den Übergabewert sind Werte von 0 bis 255 erlaubt. Die Zahl vor dem "/" gibt den Inhalt des Status-Registers wieder. ===== Testschaltung ===== Hier gibt es eine sehr schöne Testschaltung um mit dem Parallelport rumzuspielen: * [[http://logix4u.net/Legacy_Ports/Parallel_Port/A_tutorial_on_Parallel_port_Interfacing.html]] {{:paralleltest.gif|}}