Inhaltsverzeichnis
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
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
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 <sys/io.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
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 <fcntl.h>
#include <dev/ppbus/ppi.h>
#include <dev/ppbus/ppbconf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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:


