


IBM @ALLE        de:DG2GTR 20.07.93 20:48 365  13194 Bytes
LPT-Interf.-Doku!!
*** Bulletin-ID: 20731BOE9XPI ***

930720/2021z DK0MWX, 930720/2004z DK0MTV, 930720/2008z DB0HOM
930720/1952z DB0GE , 930720/1917z HB9EAS, 930720/1840z OE9XPI
de DG2GTR @ OE9XPI.AUT.EU
to IBM @ ALLE

Hallo liebe OM's...

In letzter Zeit waren in verschiedenen Rubriken immer mal wieder Fragen zur
Programmierung der parallelen Schnittstellen (LPTx) an IBM-PC's (und
Kompatiblen).

Vor laengerer zeit war zu diesem Thema (und auch zur Seriellen Schnittstelle)
jeweils ein sehr guter Artikel von F6FLV in der Rubrik IBM, der sich vielleicht
in der einen oder anderen Box noch findet.

Da aber das Thema wohl einige OM's nicht mehr los laesst, werde ich versuchen die
Informationen, die ich habe zusammen mit eigenen Erfahrungen nochmals einzuspielen.


Ausfuehrung der parallenen Schnittstelle:

Ueblicherweise wird eine Parallelschnittstelle am PC auf einem 25-poligen,
weiblichen SUB-D-Stecker (oder besser Buchse ??) angeboten. Hier gilt folgende
Pinbelegung:

1  Strobe(*)        14 Autofeed(*)
2  Data0            15 Error
3  Data1            16 Initialize
4  Data2            17 SelectInput(*)
5  Data3            18-25 Ground
6  Data4
7  Data5
8  Data6
9  Data7
10 Acknowledge
11 Busy(*)
12 PaperEnd
13 Select

Elektrische Eigenschaften: Die Signalpegel verstehen sich alle relativ zu
Ground, und sind (Ausnahmen werden spaeter erlaeutert) TTL-Kompatibel.
Das bedeutet: Logisch-0 = 0Volt (niederohmig). Logisch-1 = 5Volt (hochohmig).
Mit der angabe von niederohmig, bzw. hochohmig ist klargestellt, was passiert,
wenn ein Log0 und ein Log1 aufeinandertreffen: Log0 setzt sich durch!!

Mit (*) gekennzeichnete Leitungen sind invertiert. D.h. ein Log1 wird dann
elektrisch durch 0V, und ein Log0 durch 5V repraesentiert.

Die Namen der Leitungen stammen von ihrem Sinn bei der Regelung des Datenflusses
vom Rechner zum Drucker ab. Man kann aber der Einfachheit halber zunaechst
mal in Eingaenge (Daten zum PC) und in Ausgaenge (Daten vom PC) aufteilen.
Das sieht dann so aus:

1 Ausgang (invertiert)       14 Ausgang (invertiert)
2 Ausgang                    15 Eingang
3 Ausgang                    16 Ausgang
4 Ausgang                    17 Ausgang (invertiert)
5 Ausgang
6 Ausgang
7 Ausgang (Eingang)
8 Ausgang (Eingang)
9 Ausgang (Eingang)
10 Eingang
11 Eingang (invertiert)
12 Eingang
13 Eingang

Wie man sieht sind die Leitungen entweder Eingaenge oder Ausgaenge. Eine
Ausnahme machen die Leitungen Data5-Data7, welche sowohl als Eingang, als
auch als Ausgang dienen koennen. (Ich habe dieses Verhalten ein meiner
paralellen Schnittstelle festgestellt. Es kann aber NICHT davon ausgegangen
werden, dass dies immer so ist. Man sollte also annehmen, D5-D7 waeren auch
nur Ausgaenge.)

Es stehen also 12 Ausgaenge, und 5 Eingaenge fuer TTL-Pegel zur Verfuegung.


Programmierung der Schnittstelle:

Der uebliche Schnittstellenbaustein verfuegt ueber 3 8-Bit-Register, ueber die
sich Daten auf Ausgaenge legen lassen, bzw. Daten von Eingaengen lesen lassen.
Diese Register werden in den E-A-Addressraum (nicht in den Speicheraddress-
raum) eingeblendet. (Anmerkung: Man erreicht diese Register nicht, indem man
eine Speicherzelle schreibt, sondern nur ueber die IN und OUT Befehle des
Prozessors).
Die 3 Register liegen auf fortlaufenden Adressesn, so dass es also reicht, die
Adresse des ersten Registers zu wissen. (Bei PC's ist diese ueblicherweise
Hex 0378 fuer LPT1). Um die Adresse Rechner- und Betriebssystemversions-
unabhaengig festzustellen, empfiehlt ein 'Blick' in den BIOS-Datenbereich.
(Dieser Bereich liegt im Segment 0040:xxxx). Unter den Adresses 0040:0008 -
0040:000F findet man die Basisadressen der LPT-Controler LPT1-LPT4. (Ist
0 eingetragen als Adresse, so konnte das BIOS keinen Controler finden, was
aber nicht immer heisst, dass wirklich keiner da ist.)

Hat man sich nun 'Bios-Unterstuetzt' mit der korrekten Adresse bewaffnet,
so koennen die ersten Gehversuche stattfinden.

Schauen wir uns die Funktion der 3 Register mal an:
-Die im BIOS gefundene Adresse wird nun immer als BIOS_ADR referiert.

Datenregister: BIOS_ADR
   Das Datenregister ist 8 Bit breit. Es korrespondiert mit den Leitungen
   Data0 - Data7. (Data0 = Bit0, Data1=Bit1...Data7=Bit7)

   - Schreibzugriffe: Wird ein Byte ins Datenregister geschrieben, so werden
     1 Bits auf der zum jeweiligen Bit gehoerenden Leitung als 5V, und 0-Bits
     auf der zum Bit gehoerenden Leitung als 0V dargestellt.

   - Lesezugriffe: Wird das Register gelesen, so erhaelt man den Wert, der
     zuletzt ins Register geschrieben worden ist. (Das ist praktisch, wenn
     man nur ein einzelnes Bit umsetzen will, aber den Ausgangszustand nicht
     kennt.)

   - Anmerkung zu den Lesezugriffen: Oben ist angedeutet, dass die Leitungen
     Data5-Data7 teilweise auch als Eingaenge verwendet werden koennen.
     Wird von aussen ein Pegel an diese Leitungen angelegt, so kann wird dieser
     Pegel in die oberen 3 Bits des Registers eingelesen. Nun koennte man sich
     fragen, was passiert, wenn man die Leitung D7 einerseits durch einen
     Schreibzugriff auf 1 setzt, andererseits durch einen Pegel von aussen
     auf 0 setzt. (???) -> Hier passiert folgendes: Die Leitungen D5-D7 sind
     nicht 'echt' TTL-Kompatibel. Sie sind liefern sowohl die 5V- als auch
     die 0V hochohmig. Wird also von aussen 0 angelegt, so setzt sich diese 0
     gegen die hochohmige 1 durch.
     Im umgekehrten Fall (0 wird geschrieben, 1 wird angelegt) stellt man
     fest, dass sich immer nocht das Signal von aussen (in diesem Falle die 1)
     durchsetzt. Die Ausgaenge D5-D7 sind also sowohl auf 0V als auch
     auf 5V viel hochohmiger, als es in der TTL-Spec. drinsteht. Dies kann dann
     eben auch Probleme machen, wenn man direkt TTL-Bausteine ansteuern will.

Statusregister: BIOS_ADR + 1
   Das Statusregister ist 8 Bit breit. Es korrespondiert mit den Leitungen
   Error, Select, PaperEnd, Acknowledge, Busy.

   Bit0-Bit2 = unbelegt
   Bit3=ERROR
   Bit4=Select
   Bit5=PaperEnd
   Bit6=Acknowledge
   Bit7=Busy

   - Schreibzugriffe: Zwecklos. Dies ist ein Leseregister.

   - Lesezugriffe: Man erhaelt 1-Bits fuer Leitungen mit TTL-5V und 0-Bits fuer
     Leitungen mit TTL-0V. Bit0-Bit3 sind undefiniert (in der Regel gesetzt.)
     Achtung: die Busy-Leitung wird vom Controller invertiert. D.h. liegt hier
     von aussen 5V an, so bekommt man ein 0Bit, und umgekehrt.

   - Anmerkung zur Acknowledge-Leitung: Obwohl die parallelen Schnittstellen
     normalerweise ohne Verwendung von Hardwareinterrupts betrieben werden, ist
     dies nicht vom Schnittstellendesign, und vom Controller her unmoeglich.
     Der Controller kann (wenn man ihm dies erlaubt) einen Interrupt (IRQ)
     ausloesen. (LPT1 benutzt IRQ7, LPT2 benutzt IRQ5, LPT3 und LPT4 haben
     keinen IRQ zugeordnet.)
     Wird diese Betriebsart gewaehlt (INTERRUPT-ENABLED), so loesst der Controller
     dann einen Interrupt aus, wenn das Acknowledge-Signal von Logisch0 auf
     Logisch1 wechselt. (Beim Uebergang von 1 auf 0 wrd kein Interrupt
     ausgeloest.)

Controll-Register: BIOS_ADR + 2
   Das Controll-Register ist 8 Bit breit. Es korrespondiert mit den Leitungen
   STROBE, Initialize, SelectInput, AutoFeed.

   Bit0=Strobe
   Bit1=Autofeed
   Bit2=Initialize
   Bit3=SelectInput
   Bit4=INTERRUPT_ENABLE (0=NoInterrupts 1=Interrupts on Ackn)
   Bit5-Bit7=unbelegt.

   - Lesezugriffe: Liefern die aktuelle Einstellung des Registers.

   - Schreibzugriffe: Die mit den jeweiligen Bits korresponierenden Leitungen
     werden entsprechen gesetzt. Achtung: Autofeed, SelectInput und Strobe
     sind invertierte Leitungen. Nur Initialize kommt so an, wie man es rein-
     schreibt.

   - Anmerkung zum INTERRUPT_ENABLE-Bit. Wird dieses Bit auf 0 gelassen, so
     wird sich der Controller niemals 'trauen', eine Interrupt auszuloesen.
     Wird das Bit auf 1 gesetzt, so versucht er einen Interrupt auszuloesen.
     (Ob dieser Interrupt auch wirklich ausgeloest wird haengt nun von der
     Programmierung des Interruptcontrollers ab, dazu nacher noch ein paar
     Worte).

-------------------------------------------------------------------------------

Programmierung in Ceh...

#define LPT1 0x378

char byte;

main()
   {
   /* lesen des Status-Registers */
   byte=inportb(LPT1+1);

   /* Schreiben des Daten-Registers */
   outportb(LPT1,byte);

   /* Schreiben des Controllregisters */
   outportb(LPT1+2,byte);
   }

Alles weitere ist nun Fummelei in den Gelesenen (oder zu schreibenden Bits).
--------------------------------------------------------------------------------

Interrupt-Controller: Da ja Ceh in den neueren Versionen ueber das Statement
'interrupt' in der Lage ist, auch Interrupt-Handler zu schreiben, sollten
wir noch nen kleinen Blick auf den interruptController, und die zu den IRQs
gehoerenden Software-Interrupts werfen.

Generell gibt es ja nen Unterschied, zwischen einem Softwareinterrupt (int...)
und einem Hardwareinterrupt (IRQ). Beim Softwareinterrupt gilt zunaechst mal
folgendes: Irgendjemand hat den (via int...) aufgerufen. Ist der Interrupt
fertig, so wird beim Aufrufer weitergemacht. (Die ganze DOS-Schnittstelle ist
ja so realisiert int21).

Beim Hardwareinterrupt hat ein Hardwarebaustein angezeigt, das er Beachtung
haben moechte. Die aktuell laufende Software weiss davon aber nix. Sie ruft
also auch selbst keinen int... auf. Das muss schon jemand anders machen.
Das geht so: Im PC gibt es einen Baustein (in AT's dann 2) den sogenannten
Interruptcontroller (8259). Dieser hat 8 Eingaenge, und einen Ausgang
(vereinfacht). Ueber die 8 Eingaenge koennen andere Bausteine ihm eine
Interruptanforderung uebergeben. Er teilt diese dann ueber seinen Ausgang
dem Prozessor mit. Dieser schaut dann nach, welche der Leitungen betroffen
sind, und verhaelt sich so, dass er fuer jede Leitung einen ganz bestimmten
Software-interrupt aufruft. (IRQ0= int08, IRQ1=int09, ... IRQ07= int0f,
IRQ8=int70, ... IRQ0F = int77).
Dieser aufgerufene Softwareinterrupt ist nun fuer die Behandlung des Ereig-
nisses verantwortlich. Ist er fertig mit der Behandlung, so darf er nicht
vergessen, dem InterruptController mitzuteilen, dass der Interrupt abgehandelt ist,
sonst meldet der InterruptController naemlich keine weiteren Interrupts mehr,
und der PC stuertzt ganz ganz schnell ab.


Der Interruptcontroller1 (der Master, der fuer IRQ0 - IRQ7 verantwortlich ist)
belegt die E-A-Adressen 0x20 und 0x21.

Auf 0x20 koennen Interrupts quitiert werden (Controller ist dann wieder frei).
Auf 0x21 koennen interrupts erlaubt, oder gesperrt werden.

Erlauben und Sperren:

   Port 0x21 ist ein 8-Bit Port. Dieser kann gelesen und geschrieben werden.
   Beim Lesen erhaelt man ein Byte, in dem fuer jeden zugelassenen Interrupt
   ein 0-Bit steht, fuer jeden gesperrten ein 1-Bit.
   Beim Schreiben wird das uebergeben Byte genauso ausgewertet.

  (Bit0 = IRQ0 ... Bit7 = IRQ7)

   ACHTUNG: Man sollte um Abstuertze zu vermeiden tunlichst Port x21 zunaechst
   lesen, und nur das gewuenschte Bit veraendern, und dann wieder Schreiben.
   Ausserdem ist dieser Vorgang mittels enable() und disable() so abzusichern,
   das niemand anders (z.b. ein anderer Interrupthandler) auch mit Port x21
   zur selben Zeit arbeitet. Die Folgen koennen WIRKLICH KATASTROPHAL sein.
   (Kein Scherz). ( Auch die HD arbeitet ueber den InterruptController).

Interrupts Quittieren:
   Nach Eintreffen eines Interrupts, und dessen Weiterleitung zur CPU ist
   der Interruptcontroller zunaechst einmal gesperrt. D.h. er wird eintrffende
   Interrupts wohl benerken, diese aber zunaechst nicht mehr an die CPU weiter-
   leiten. Erst, wenn der Interrupt quittiert wird, wegen dem der Interrupt-
   conteroller blockiert ist, werden die anderen gemeldet.

   Es gibt 2 Arten zu quittieren:

   - Allgemeine Quittung

   - Geziehlte Quittung

   Die Wirkung ist eigentlich bei beiden gleich, jedoch sollte normalerweise
   die geziehlte Quittung verwendet werden.

   - Allg.:

   Schreibe 0x20 nach Port 0x20.  /* outportb(0x20,0x20); */

   - Geziehlt:

   Schreibe 0x60+X nach Port 0x20. (X ist die Nummer des gemeldeten IRQ.)
   z.B. IRQ7 quittieren: 0x67 nach Port 0x20.
        /* outportb(0x20,0x67); */


------------------------------------------------------------------------------

So, nun sollte dem Tatendrang aller LPT-Programmierer nichts mehr im Wege
stehen. Sollte jemand Interesse am Artikel von F6FLV haben, diesen aber in
keiner Box mehr finden koennen, dann bitte MSG an mich. Ich spiele ihn dann
nochmal ein.


73 de Rainer (DG2GTR)


(IBM) DL1ELY de DB0IZ>