==============================================================================
C Scene Issue #04
Parallel Port Programming
Moe Elzubeir
=============================================================================

Parallel port programming is easier than it sounds. The lack of literature on it is surprising, but not a problem. Once you find a few resources of information, you will be set. In this article, I will try to give you the inform ation that you need, without the baffling technicality. Simple does it.

 

 

The Basics:

Access to the parallel port is via a female DB25 on the back of the PC.

 

Pin

Description

Notes

1

/Strobe

PC Output (OC)

2

Data_0

PC Output

3

Data_1

PC Output

4

Data_2

PC Output

5

Data_3

PC Output

6

Data_4

PC Output

7

Data_5

PC Output

8

Data_6

PC Output

9

Data_7

PC Output

10

/ACK

PC Input

11

Busy

PC Input

12

Paper Empty

PC Input

13

Select

PC Input

14

/Autofeed

PC Output

15

/Error

PC Input

16

Init Printer

PC Output

17

/Select_Input

PC Output

18

Ground

 

-25

   

Table 1 - Parallel Port Terminal Designations

 

 


   Every parallel port has three port addresses: Data, Status, and
   Control. The three of them are in sequential order. (I.e., if the Data
   port is 0x378 then the Control port is 0x378+1, and so on).
      
   
   So, how do you find out what the port address of your parallel port
   it? That depends on your platform. If you are under DOS, you can do
   the following:
   
   
   
   C:\>debug
   
   -d 0040:08 L8
   
   0040:0008 78 03 78 02 00 00 00 00
   
   
   
   The above is an example of an output from the debug command d 0040:08
   L8. The port address resides in the memory location 0040:08.
   
   
   
   Outputs:
   
   
   
   Note that there are eight output on the Data Port, and four additional
   outputs on the low nibble of the Control Port (/SELECT_IN, INIT,
   /AUTOFEED, and /STROBE).
   
   
   
   Luckily, all outputs to the Data Port are true logic. However, the
   /SELECT_IN, /AUTOFEED, and /STROBE outputs on the Control Port are
   inverted.
   
   
   
   Let's see some sample code:
   
   
/* codecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecode */   
   
   
   #include <stdio.h>
   #include <unistd.h> /* needed for ioperm() */
   #include <asm/io.h> /* for outb() and inb() */
   
   
   #define DATA 0x378
   #define STATUS DATA+1
   #define CONTROL DATA+2
   

   int main(void)
   {
   int x = 0x32;
   int y = 0x08;
   

   if (ioperm(DATA,3,1)) {
   	printf("Sorry, you were not able to gain access to the ports\n");
   	printf("You must be root to run this program\n");
   	exit(1);
   	}
   
   
   outb(DATA, x); /* Sends 0011 0010 to the Data Port */
   outb(CONTROL, y^0x0b);
   
   /* SELECT_IN = 1, INIT = 0, /AUTO_FEED = 0, /STROBE = 0 */
   
   return (0);
   
   
   }
   
/* codecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecode */
   
   
   The above code will work on a Linux. As far as SunOS is concerned there
   are two files you need to include (sys/ddi.h) and (sys/sunddi.h) for
   the inb() and outb() functions. 

   You also need to compile with -O or -O2 or similar. The reason for that
   is that inb() and family are defined as inline macros, and compiling
   them without optimization enabled will cause unresolved references at
   link time (Someone in #c came and kept on asking about it, and I thought
   I would add this part in). [If people would only read the man pages!].

   
   
   
   Inputs:
   
   
   
   There are five status leads for the Status Port. (BSY, /ACK, PE,
   SELECT, /ERROR). The reasons they are named like that is that it was
   originally designed for a printer only. So, we have things like PE
   (paper empty), etc.
   
   
   
   Beware, the BSY is inverted using hardware, so when receiving input
   from there, make sure you invert this bit so you can have what
   represents a true logic.
   
   
   
   
   
   This is how to read the five most significant bits in true logic:
   
   
   
   Value = ((inb(STATUS)^0x80) >> 3);
   
   
   
   Notice how we inverted the BSY bit using the exclusive-or function.
   Then, we shifted the bits 3 times to the right, resulting in the upper
   five bits ending up in the lower five bit positions.
   
   
   
   0 0 0 BUSY /ACK PE SELECT /ERROR
   
   
   
   
   
   In conclusion, we should have realized by now that there are 12
   outputs (eight on the data port, and four on the lower nibble of the
   control port). There are five inputs, on the highest five bits of the
   status port. Three output bits on the control por t and one input on
   the status port are inverted by the hardware.
   
   
   
   Problems and Solutions:
   
   
   
   It may have occurred to you that there are only five bits on the
   parallel port for input. This could be a real inconvenience when
   interfacing with 8-bit analog to digital converters. One solution to
   this problem is to add external circuitry to store the 8-bit result
   and gate in 4-bits at a time.
   
   
   
   So what can we do about it?
   
   
   
   What I did not mention about the Control Port (other than having four
   outputs) is that they are bi-directional. They can be outputs and
   inputs! Dont you just love how confusing they can get?
   
   
   
   Using the control port bi-directional bits is not as simple as just
   reading them using inb(). We have to force all the four outputs on the
   lower nibble of the control port to logic one. This will result in
   external signals being forced on these inputs and then we can read
   using the inb().
   
   
   
   int i;
   
   
   
   outb(CONTROL, 0x0f^0x0b);
   
   /* inverting to go around the hardware inversion */
   
   i = (inb(CONTROL) & 0x0f) ^ 0x0b;
   
   
   
   
   
   Doing so, we would have solved our little problem there.
   
   
   Other Handy Macros

   Now, let's say you want to send a word to portX and portX+1 instead
   of just sending a byte to portX and then another byte to portX+1.
   outw(value,portX) will do the trick. inw(portX) will return a word
   from portX and portX+1.

   Now usually when sending data to the parallel port you need a slight
   delay to ensure that the data has been sent, and not lost. Instead of
   having extra calls, and more code, there is outb_p() inb_p() outw_p()
   and inw_p() which gives us a delay of about 1 microsecond. If that
   is not enough delay, you can always #define REALLY_SLOW_IO before
   the #include . Those macros use a port output to port 0x80
   for their delay. So you need to give access to that port with ioperm().


   Final Words On Permissions

   In order to use ioperm() you will need to have root privileges. If you
   don't want that, then you can always use setuid.

   
   Programming the parallel port can be a lot fun. The only thing that
   can actually be a pain in it is all this inverting you have to do
   every now and then. For most simple projects, you only need to use the
   8-bits in your data port (ie, the base port).
   
   
   
   Finally, I must warn you that if you intend to do anything with your
   parallel port, make sure it is not integrated with your motherboard.
   Chances are, if you blow something up, it's going to be your
   motherboard. What you should so is buy a 386, or any old machine you
   can mess around with. I say 386 because it's the first of the 32-bit
   processors for the IBM PC (so I can actually run Linux on it). It's a
   free world, use whatever your heart desires, just don't test it on
   your PII!


This page is Copyright © 1997 By C Scene. All Rights Reserved