

        Your PC's Printer Port: a Window on the Real World
                    Tom Dickinson  tsd@bnl.gov
 
     The printer port or parallel port on your IBM computer is 
well suited for hardware control, data input, or other 
interfacing.  If you just send (write or output) a number to the 
correct processor I/O port the corresponding bits will appear as 
ones and zeros (5 volts and 0 volts) on the connector pins on the 
printer adapter.  It's as simple as that.  It's nearly as simple 
to get signals back, but there are only five bits of input -- the 
status signals that the PC expects from the printer.  
 
     The printer port works well as a hardware interface because 
it is a "real-time" device.  When you output a number to the 
port, voltages appear on the connector pins, and when you input 
you get the real time status of the bits (voltages) in the input 
pins.  This is in contrast to the serial port which handles 
things by the RS232 protocol and sends and receives strings of 
pulses which have to be 're-assembled' into meaningful numbers.  
RS232 also uses 'handshaking' signals to figure out when to start 
and stop strings of pulses and when to expect another batch.  
When the printer adapter is feeding a printer it also uses 
handshaking, hardware interrupts and so forth, but for our simple 
interfacing we can ignore these. 
 
     IBM compatible computers keep in touch with the outside 
world mostly through processor I/O ports.  These are addressed 
though the same address lines as memory, but use different 
read/write lines, so they don't occupy memory address space.  The 
Intel processors can address 64 k I/O ports, but the IBM machines 
implement only about 1000 of these, which is plenty.  They are 
used for keyboard, com and parallel ports, disk I/O, video, and 
other things.  The processor I/O ports consist of logic built 
into the processor and address assignments which permit the 
processor to pass data back and forth in 8 bit chunks to 
different destinations and sources.  They should be distinguished 
from the connectors on the back of your computer for the serial 
port, parallel port, etc.  Each of these uses several processor 
I/O ports to carry out its functions.  More on this below.  
Reading and writing to the I/O ports is slower than to memory, 
and as computers have gotten faster and more sophisticated, some 
of these functions have been changed to use Direct Memory Access 
(DMA), in particular the newer video and disk interfaces.  
 
     IBM compatible computers accommodate three printer adapters, 
that is, three sets of processor I/O ports are assigned to this 
function.  When the computer boots up, the POST program detects 
any printer adapters and assigns 'logical' ports to them, LPT1, 
LPT2, and LPT3.  Which port addresses are assigned to LPT1, 2, or 
3 depends on whether the printer adapter is part of the video 
card, but for hardware interfacing we don't care, since the 
logical designations are mostly used by applications programs.  
 
     Each printer adapter uses three processor I/O ports.  The 
first is for data output, the second for input of status signals 
from the printer, and the third is for commands that go to the 
printer.  The three are known as the data, status, and control 
ports.  You can execute a 'read' to the first and third ports, 
but all that you'll see is the state of the latches set by the 
last 'write'.  Doing a read on the second port returns the state 
of the five status signals on the printer connector, while 
writing to that port doesn't do anything.  The physical address 
of a printer adapter card is determined by how the computer 
address lines are connected up to the logic on the card, and most 
adapters have a jumper plug so you can select 378h or 278h.  The 
first (data output) port is the base address, and the status and 
control ports mentioned above are base+1 and base+2.  The three 
base addresses available for printer adapters are 378h, 278h, and 
3BCh.  These are hexadecimal numbers, indicated by the 'h' after 
each one.  
 
     You will need to know the physical addresses of your printer 
adapter in order to send and get signals, so here's how to figure 
it out.  If your printer connector is on your mono or CGA video 
card, then the base address is almost certainly 3BCh.  That means 
that the data output address is 3BCh, the status input address is 
3BDh and the control port is 3BEh.  (Base address, base+1, and 
base+2.  Get the picture?)  If the printer connector is not on 
the video adapter, then the address is probably 378h, and if not 
that then it will be 278h.  If you can't figure it out, run the 
program on the next page and check for the pattern of voltages on 
the printer connector.  If it doesn't work, try again with a 
different base address.  
 
     You can also use the same technique for finding the printer 
adapter address that the computer does.  During the Power On Self 
Test, the computer looks for adapter cards, keyboard etc, and 
stores address and other configuration information in the BIOS 
Data Area of memory.  The printer adapter base addresses are 
stored at 40:0008h, and can be accessed by using DEBUG.  The 
DEBUG program is probably on your DOS disk, so change to that 
disk or directory and type debug and hit enter.  You will get a 
minus sign (not blinking) at the left edge of the screen which is 
the debug prompt.  Type the following (with no spaces): 
- d40:0008      and hit return.  You will be rewarded with: 

0040:0008                    78 03 78 02 00 00 00 00  x.x..... 

and several more lines of similar numbers and letters.
     This is a "hex dump" of the contents of the eight memory 
locations starting with 40:0008h.  The first six number pairs are 
the base addresses of the installed printer adapters.  In the 
example above, the first adapter is at 0378h, the second is at 
0278h, and there is no LPT3 installed.  The number pairs are 
reversed because Intel stores such numbers in a 'low byte-high 
byte' format.  If you only have one adapter installed, this tells 
you all you need to know.  If you have more than one, you still 
have to figure out which 25 pin printer connector is which, 
either by opening up your computer and checking the address 
selection jumpers on the adapter cards, or by doing the 
experiment on the next page.  After you write down the addresses 
displayed, you can exit DEBUG by typing q. 
 
     You can buy a printer adapter card for $10 to $20 and set it 
to a different address than the one already in your computer so 
you can play around with hardware I/O and still have your printer 
plugged in and ready to go.  In fact, if your printer port is on 
a multi I/O card or on your motherboard you should certainly do 
this, so if you slip up and burn the port out, the damage is 
limited.  
 
     Let's make something happen:  If you write a number to the 
base address I/O port, pin 2 on the 25 pin printer connector on 
the back of your pc corresponds to bit 0 and pin 9 corresponds to 
bit 7 and the ones in between are in order.  So if you output 
binary 10101010 (=hex AA =dec 170) on port 378h, and then take a 
meter to the pins on the printer connector, you will find 5 volts 
on pins 9, 7, 5, and 3, and zero on pins 8, 6,4, and 2.  When I 
say 5 and 0 volts, I mean whatever is an LS-TTL high and low, 
which may be between 2.4 and 5.0 volts for a high and 0.8 and 0.0 
volts for a low.  Most printer adapters now days are made from 
CMOS LSI chips, and the unloaded signals will be close to 5 and 0 
volts.  These outputs are latched, so you don't need a software 
loop or anything, they will stay there until a new number is 
output to the printer port.  
     In BASIC the relevant statement is OUT p,n  where p is port 
and n is data, ie the number you want to output.  Try this 
program. 
 
10 INPUT "Enter a number 0 to 255   ", N%
20 OUT &H378, N%
     (the &H tells BASIC that the number is hexadecimal, 
      otherwise it expects decimal numbers)
If you run this program, you should then find the pattern of 
voltages on the printer connector corresponding to the bits of 
the number you entered.  If your printer adapter is set to 378h, 
that is.  If it doesn't work, try &H278 or &H3BC in line 20.
The assembly language equivalent to the BASIC OUT statement is:
 
MOV DX,p
MOV AL,n
OUT DX,AL
 
     Inputing data to the PC is a little more complicated.  Bits 
3,4,5,6,and 7 correspond to connector pins 15, 13,12, 10, and 11 
(note that they are not in order).  When you input a byte from 
the status port at 379h, bits 3 through 7 will be set according 
to what voltage is on the connector pins listed above.  I don't 
know what is on bits 0, 1, and 2, but they are not determined by 
connector voltages.  Another complication is that the input from 
pin 11 is inverted, so a ground on the pin gives a one bit and 5 
volts gives a zero bit.  The other inputs are the right way 
round.  By the way, these are printer signals Error, Select, 
Paper out, Acknowledge, and Busy. 
     In BASIC to read a port you use the INP function.  To be 
useful, you need to assign the number that you read to a 
variable.  For example: 
 
10 K% = INP(&H379)
20 N% = K% AND &HF8   rem F8h=binary 11111000. Strip last 3 bits
30 PRINT N%
 
     If you run this with nothing connected to the printer 
connector, you should get 120 (= 78h = 01111000 bin).  This is 
because an open TTL input usually reads high.  Remember that the 
top bit is inverted, and the last three bits are not connected 
and are zero'ed out in line 20 of the program.  Now ground pins 
15, 13, 12, 10, and 11 one at a time to see the effect on the 
number returned.  Use a 2000 ohm resistor to do the grounding, 
and you won't burn anything out if you stick it in the wrong 
hole. 
 
The assembly equivalent of INP is:
 
MOV DX,p
IN AL,DX
 
Where p is the input port (base+1).
        
     The control port at base+2 (37Ah to follow the examples 
above), can be used for four more output bits if you need them.  
Pins 1, 14, 16, and 17 correspond to bits 0 through 3 of a byte 
output to the control port and bits 0, 1, and 3 of this are 
inverted.  In addition, bit 4 of this control byte is not 
connected to the printer connector, but if it is set, then the 
next time pin 10 (the Acknowledge signal) goes from high to low, 
the processor is interrupted.  This will invoke the interrupt 
service routine for the Centronics protocol, and who knows what 
will happen if you are trying to control a robot or something at 
the time.  
     You can also use this port for input.  This is complicated, 
but may be worth it if you need a full 8 bit byte of input.  The 
penalty comes in lines of programming needed to do it since you 
first need to set the bottom three control outputs high, read 
from the control and status ports, mask off the unused bits, 
assemble the two sets of bits into a byte, then flip the inverted 
bits.  The control port can be used for input because it has open 
collector output circuits, and if you set the outputs high then 
an external signal can determine the voltage on pins 1, 14, 16, 
and 17 on the parallel connector.  Bits 0, 1, and 3 are inverted 
on input as well as output. 

     To summarize:  on the 25 pin female 'D' connector on the 
printer adapter, pins 2 through 9 correspond to bits 0 through 7 
of a byte output to the adapter base address.  The voltages on 
pins 15,13,12,10, and 11 determine bits 3 through 7 of the byte 
input from base+1, and bit 7 will be inverted.  Pins 1, 14, 16, 
and 17 are driven by bits 0 through 3 output to the control port 
at base+2, and bits 0, 1, and 3 are inverted.  If the outputs on 
these pins are set high by writing binary 00000100 to base+2 
(remember the inverted bits) then external signals can be input 
on these pins.  Pull-up resistors of 4.7k are built into the 
printer adapter.  Pins 18 to 25 of the parallel connector are 
ground.  Unfortunately, 5 volts does not appear on the connector, 
so if you need power for interface circuits you will need an 
external supply or to get it from somewhere inside the computer.  
Be careful since you can cause expensive damage by putting 5 
volts in the wrong place or shorting it to ground. 
 
     So what can we do with this?  The data bits can source a 
couple of milliamps, so you could light (dimly) up to eight LED's 
in patterns of your choice.  (This is one way to burn out your 
printer adapter, so be sure you use the right size resistor to 
limit the current to 2.6 mA if you are going to attach LED's). 
Or, with an external power supply, you could drive transistors to 
operate relays to control up to eight devices.  The inputs could 
be used to keep track of whether five devices were on or off.  
Not too exciting so far.  More possibilities are opened up with 
an A to D converter, so you can measure things and perhaps make 
decisions based on the measurements.  The ADC0831 is a nifty 
eight bit A-D converter which can be operated with two output and 
one input lines, leaving lots of I/O lines still available for 
doing stuff.  Description of this is left for another time. 
 

    How to Input 8 bits Through a Unidirectional Parallel Port
                   Tom Dickinson  tsd@bnl.gov

     The scheme I suggest for 8 bit input uses the three lowest 
bits of the control port and the five high bits from the status 
port.  This is also described in Zhahai Stewart's "Parallel Port 
FAQ" in section 16, mode 3B.  We evidently thought of this 
independently, but it is pretty obvious.  This leaves one control 
bit (bit 3) unused, and it could be used as a bidirectional 
control or strobe bit as mentioned in the faq.  
     Here are some code examples:
8 bit output through the data port:

     OUT &H378, N  'where N is the number you want to output

In assembly this is:
     MOV DX,p
     MOV AL,n
     OUT DX,AL     ' p is port and n is data

Here is an example of an 8 bit input using 3 control bits and 5 
status bits.  This is in basic to show the operations involved.
  (I haven't tried this code, so it may have bugs)

The bits to be input are connected to the following pins:
     BIT  7  6  5  4  3  2  1  0
     PIN  11 10 12 13 15 16 14 1

     OUT &H37A, &H04 'set output of bits 0-3 of cont. port high
     LO = INP(&H37A) AND &H07     'input and mask bottom 3 bits
     HI = INP(&H379) AND &HF8     'input and mask top 5 bits
     BYTE = LO OR HI              'combine LO and HI into 8 bits
     RITEBYTE = BYTE XOR &H83     'flip bits 0, 1, and 7
Ritebyte is the number (0 to 255) represented by the 8 bits input 
on the connector.  

     The first line above may be a bit confusing, but remember 
that bits 0, 1, and 3 are inverted, so to get 1111 on the pins, 
you need to send out 0100.  04h = 00000100, so we also are 
zeroing the top 4 bits.  This insures that the interrupt enable 
on bit four is not set, which might cause trouble.  In basic this 
routine is apt to be pretty slow, and if you need speed, you 
should code in assembly.  These are byte wide logical operations 
which are very fast in machine language.  You would then pass the 
byte to a high level language to use as an integer variable.  I 
understand that C can do this kind of stuff very fast, if 
optimized.  I have no experience with this.

------------------------------------------------------------------
Thomas S. Dickinson                     Internet: tsd@bnl.gov 
National Synchrotron Light Source       (516) 282-7196  (work)
Brookhaven National Laboratory          (516) 282-3238  (fax)
Upton, NY 11973 USA                     
                           also  dickinso@ls7501.nsls.bnl.gov
                           and   dickinso@bnlls1.nsls.bnl.gov
-----------------------------------------------------------------


