interfacing the parallel port

Or the easy way to make computer peripherals 

The Parallel Port is Easy

It's true.  When you deal with the parallel port, you don't have to worry about deserializing a signal, or synching oscillators with the sender.  It gives you data in parallel, and you can write in in parallel.  It can be used synchronously, or asynchronously, and it even supplies enough current to power a few ICs. Because of these features, the parallel port is ideal for hobbyists.

The parallel port sucks too, mainly because the average computer only has one.  If you build all of your peripherals to use the parallel port, then you can only have one plugged in at a time (well, there are standards for multiplexing the parallel port, but they destroy the simplicity, which was why we chose the parallel port to begin with). 

Perhaps the best reference to the parallel port is Ian Harries' Page

Pinouts

The parallel port plug that you want to use is called D-sub 25 (or DB-25).  It has 25-pins: (1) nSTROBE, (2-9) DATA 0-8, (10) nACKNOWLEDGE, (11) BUSY, (12) PAPER_END, (13) SELECT, and (14) nAUTO_FEED, (15) nERROR, (16) nINITIALIZE, (17) nSELECT_IN, (18-25) GROUND. (diagram). The pins whose names begin with 'n' are inverted (read "not STROBE").  You probably want to get a female connector and a straight-through DB25 Male to DB25 Male cable.

These pins can be grouped into three categories: Data pins (2-9), Status pins (10-13, 15), and Control pins (1, 14, 16, 17).  I group them this way because the hardware treats them independantly--that is, each of them is represented by an independant register in the computer's hardware.  Thus you can, for instance, set the data register, and then modify the control register to create a strobe.  This also means that once you write the data, the parallel port will hold the value for you (one fewer register).

In compatibility mode, the 8 data pins and 4 control pins can be used for output (from the computer to the peripheral), and the 5 status lines can be used for input (from the peripheral to the computer).  There are other ways to use a parallel port, but this is the easiest.

Power

You can draw a small amount of current from the parallel port to power your device.  By small, I mean less than 14mA. This is enough power for some integrated circuits (especially the 74hc series) or a few LEDs, but is not enough for motors.  I often use pin 14 (nAUTO_FEED) as a Vcc for my circuits.  For any application that requires more power, you will need a power supply.

Levels

The parallel port operates at somewhere between 2.3V and 6V; on newer computers, expect it to be closer to 2.3V.  Also, on some older machines, you might want a pull-up resistor on all inverted outputs as I've heard (but not witnessed) that some of them operate in an open-collector fashion on some older machines.

If you are using an external power supply for your peripheral, you then run into a level conversion problem.  For instance, the parallel port might send a logic true on pin 2 (DATA 0), which would bring pin 2 to 2.3V above pins 18-25 (GROUND).  But, if your circuit is running off of a 5V supply, then your logic might not recognize the 2.3V as true.  You need level conversion.  This is an annoying aspect of the parallel port.

Programming with the Parallel Port under Linux

Under linux, you will access the parallel port via the ports interface.  This unfortunately requires that your application run as root to gain access, though it can drop permissions immediately afterwards and operate as nobody for the remainder of its execution.

Finding the Base Address 

First, you need to find the parallel port's base address.  This is usually 0x378, but it may not be on your machine, so check.  Under linux, you can easily do this with the /proc/ioports file:

grep parport /proc/ioports

The first hexadecimal number listed is your base address.  This piece of code will find it automatically for you:

int find_parallel_port()
{

        int baseport;
        FILE *fin = fopen("/proc/ioports","r");
        if( !fin )
        {
                perror("Failed to open /proc/ioports");
                return -1;
        }

        while( !feof(fin) )
        {
                char buffer[128];

                buffer[0] = '\0';
                fgets(buffer,127,fin);
                if( buffer[0] == '\0' )
                        break;

                if( strstr(buffer, "parport0") )
                {
                        sscanf(buffer, " %4x", &baseport);

                        fclose(fin);
                        return baseport;
                }
        }

        fclose(fin);
        return -1; // failure

}

Gaining Access

Next, you want your program to gain access to the port using the ioperm(baseport,3,1) call from <sys/io.h>, which returns non-zero on failure.  This call must run as root, but all subsequent parallel port access can be any user (drop privs with setuid()).

Reading and Writing 

You can use the  outb(byte, baseport) call to write a byte to the data register.  The value of byte will appear on pins 2-9.  You can use the byte=inb(baseport+1) to read the status lines (more on that below).  And you can use the outb(byte,baseport+2) call to write to the control register.

Decoding the Status Lines 

When you read the status lines, you must realize that the lines are packed into the byte in a funny fashion.  Here is how you decode it:

                 int sample = inb(baseport+1);

                int nACK = (sample >> 6) & 1;
                int BUSY = ((sample >> 7) & 1)^1;
                int PE   = (sample >> 5) & 1;
                int SEL  = (sample >> 4) & 1;
                int nERR = (sample >> 3) & 1);

Synchronous Output / Sending a Strobe 

The nSTROBE line will not pulse by default upon write.  If you wanted to write a byte, and then send a pulse on the nSTROBE line (for instance, to clock a register), you would do this:

                // send the byte
                outb(byte, baseport);

                // strobe on, then off
                int control = inb(baseport+2);
                outb(control |  0x1, baseport+2);
                outb(control & ~0x1, baseport+2);

Examples

  • My simple logic analyser uses the status pins to send data in parallel to the computer, and is powered by the parallel port.
  • My CNC Router also reads bytes synchronously, and is powered by the parallel port.
  • My PWM modulator has a parallel port interface, reading bytes from the parallel port in a synchronous fashion.  It is powered by an external supply, and must do level conversion.