Parallel Port Control for Linux

Project started on 2009-11-21

Project completed on 2009-11-28

Shortcuts

Download C++ parallel port class and diagnostic utility

Read documentation for C++ parallel port class

Motivation

I built myself a new computer recently and it eventually dawned on me that there was no parallel port on it! Well, not quite. The motherboard (Gigabyte EP45-UD3L) had headers to which you could connect parallel and serial adapters, but there was no parallel or serial connectors on the motherboard. This was a massive problem since all my microcontroller programmers and IO is done through parallel and serial ports. Luckily, in the motherboard manual was a page dedicated to the pinout of the headers. I was able to find some adapters (from an old Pentium 133 machine) that connected to these headers and hoped for the best.

The pinout for the parallel port header from the manual was slightly different from the adapters when I tested it with a continuity meter. It looked like from the pinout table that there was a typo where two rows were transposed. This really irked me and I wanted to see if it was really the case. So, I figured, the best way to test this would be to make a utility that could read and set the pins on a parallel port to test this out.

I remember playing around with the parallel port in high school in DOS and Windows. We did quite a bit with the parallel port from driving stepper motors to reading ADCs to implementing TSRs and even a multiplayer 3D shooter which communicated over parallel port. With the incredible power of Linux, this should be a breeze. I learned quite a bit about the Linux ppdev driver and have summarized it on this page.

The Parallel Port

The parallel port has historically been known to interface printers and hence been also colloquially called the printer port. On the computer side, it has 25 pin female plug to which you can attach a cable to.

The 25 pins of the parallel port are controlled by three 8 bit registers:

  1. Data register - controls 8 bit data bus which could be configured for input or output
  2. Status register - mapped to 5 pins of the parallel port connector which can be read only
  3. Control register - mapped to 4 pins of the parallel port connector which can be read or written

Specifically, this is the pinout of the parallel port and its registers:

Parallel port D Sub 25 female

Pinout of parallel port as seen on the computer (25 pin D subminiature female connector)

Pins of the parallel port
Pin Signal name Register (bit) Direction Hardware inverted Active low
1 nStrobe Control (0) I/O Yes Yes
2 Data 0 Data (0) I/O No No
3 Data 1 Data (1) I/O No No
4 Data 2 Data (2) I/O No No
5 Data 3 Data (3) I/O No No
6 Data 4 Data (4) I/O No No
7 Data 5 Data (5) I/O No No
8 Data 6 Data (6) I/O No No
9 Data 7 Data (7) I/O No No
10 nAcknowledge Status (6) I No Yes
11 Busy Status (7) I Yes No
12 Paper end Status (5) I No No
13 Select Status (4) I No No
14 nAuto line feed Control (1) I/O Yes Yes
15 nError Status (3) I No Yes
16 nInitialize Control (2) I/O No Yes
17 nSelect printer Control (3) I/O Yes Yes
18 to 25 Ground

For the sake of completeness, here is the table rearranged by register (note that there are some bits which do not correspond to any pins):

Data register of the parallel port (all pins have I/O capabilities)
Bit Pin Signal name Hardware inverted Active low Description
0 2 Data 0 No No Least significant bit of 8 bit data bus
1 3 Data 1 No No
2 4 Data 2 No No
3 5 Data 3 No No
4 6 Data 4 No No
5 7 Data 5 No No
6 8 Data 6 No No
7 9 Data 7 No No Most significant bit of 8 bit data bus
Status register of the parallel port (all pins have input capabilities only)
Bit Pin Signal name Hardware inverted Active low Description
0 Reserved For forwards compatibility, do not change the contents of this bit
1 Reserved For forwards compatibility, do not change the contents of this bit
2 nIRQ Yes Asserted (i.e. brought low) when an IRQ has occured
3 15 nError No Yes
4 13 Select No No
5 12 Paper end No No
6 10 nAcknowledge No Yes
7 11 Busy Yes No
Control register of the parallel port (all pins have I/O capabilities)
Bit Pin Signal name Hardware inverted Active low Description
0 1 nStrobe Yes Yes
1 14 nAuto line feed Yes Yes
2 16 nInitialize No Yes
3 17 nSelect printer Yes Yes
4 Enable IRQ No Enables triggering of IRQ when asserting nAcknowledge pin
5 Enable data input No When set, data pins are pulled high and act as inputs
6 Reserved For forwards compatibility, do not change the contents of this bit
7 Reserved For forwards compatibility, do not change the contents of this bit

By now, you might be wondering, what is this active low and hardware inversion business. It may seem confusing but is really quite simple. I will draw the distinction between these two terms in the following two paragraphs.

Active low denotes that the physical voltage on a line is opposite of its intended meaning. For example, a printer attached to the parallel port for most of its operation would not encounter any errors and leave the nError pin high. If you measured the voltage on the nError pin when no error has occured, you would read a TTL true level around 3V. Likewise, when an error has occured, the printer will set this pin low (false). Whether an error has occured or not is opposite of the signal that is measured from the nError line. Typically, active low signals are prefixed with a lower case n such as nError. There is no requirement for a peripheral that you build to treat any pin as an active low signal. The pin names given in the tables above are there for legacy reasons and is the convention adopted for printers. You can choose any signally convention that you find appropriate for a device that you build. It is important to note that it is up to the peripheral (such as the printer) to choose to whether or not it wants to adopt an active low signalling scheme.

Hardware inversion denotes that there is an inverter (i.e. NOT gate) between the pin of the parallel port and what is read by the CPU. For example, if you applied 5V to 11 of the parallel port, you will read a logical false from pin 7 of the status register. It is important to note that the hardware inversion is built into the computer and you must compenste for it in software. An easy way to selectively toggle bits is by using the exclusive or (XOR) function.

So why bother with active lows and hardware inversions? Wouldn't it be smarter to leave everything as positive logic? In a way, I agree with you, but active low signals have their advantages. For example, it tends to be that pull down transistors (NPN or NMOS) are stronger then their pull up counterparts (PNP and PMOS). Hence, it is beneficial for the sake of efficiency and noise margin to activate pull down transistors to assert a signal. Like it or not, this has been around for a while and it will be here to stay.

Linux Support for the Parallel Port

There are multiple ways to access the parallel port under Linux. Two popular ways are by direct IO (i.e. inb and outb calls) and by use of the ppdev driver. The direct IO method requires the knowledge of port addresses and requires root priveleges. The ppdev method does not require any of the above. We will talk about the ppdev method on this page.

There are a few limitations to using the ppdev method. So far, I've noticed the following:

  1. No IRQ support
  2. Control register is write only

Steps to Access a Parallel Port

Accessing the parallel port in Linux is quite easy. Follow these steps and modify these steps as necessary to suite your needs:

  1. Find the name of the parallel port device

    Linux, being a file oriented operating system maps all devices to files on your file system. You can probably find the name of the file that your parallel port device maps to by typing:

    dmesg | grep parport
    

    You may get the following lines:

    [   13.265525] parport_pc 00:0a: reported by Plug and Play ACPI
    [   13.265525] parport0: PC-style at 0x378 (0x778), irq 7 [PCSPP,TRISTATE]
    [   48.673384] lp0: using parport0 (interrupt-driven).
    

    In the example given above, the parallel port maps to /dev/parport0.

  2. Give yourself priveleges to access the parallel port

    The parallel port device may have permissions set to allow access by a certain group. You should find out the name of this group and add yourself to it. Following the example, you should do the following. To find out your user name, you can do:

    whoami
    

    whoami should print your user name. Let say your user name is user. Now find out which group can access the parallel port:

    ls -l /dev/parport0
    

    You may see

    crw-rw---- 1 root lp 99, 0 2009-11-29 10:15 /dev/parport0
    

    Which indicates that the group lp has access to the device. Now, add yourself to the group:

    sudo adduser user lp
    

    If that didn't work you can try these instructions:

    su
    adduser user lp
    

    If that doesn't work, get your system administrator to help you out. If all went well, log out and log back in again.

  3. Write your code

    Now, you are set to run code that accesses the parallel port. For example, try this:

    #include <fcntl.h>
    #include <unistd.h>
    #include <linux/parport.h>
    #include <linux/ppdev.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    
    int main(void) {
    	int		PortFD;
    	char	Data;
    
    	PortFD = open("/dev/parport0", O_RDWR);
    	ioctl(PortFD, PPCLAIM);
    	for (;;) {
    		ioctl(PortFD, PPRDATA, &Data);
    		Data = ~Data;
    		ioctl(PortFD, PPWDATA, &Data);
    	};
    
    	return 0;
    };
    

    Don't like using ioctl calls? No problem! Just read the next section to get a nicely packaged C++ class to control the parallel port with.

Parallel Port Class

Instead of keeping track of file descriptors and ioctl calls, I have wrapped up these function calls in an easy to use ParallelPort class. I must apologise ahead of time for using exceptions in the code. Functions are available to read and write various registers of the parallel port. The library has only 2 files (.cpp and .h) which you can use in your projects.

Download the library (includes diagnostic utility)

Read documentation (generated by doxygen)

Quick Start

Here's a small example which will help you get start with the ParallelPort library. Instead of writing:

#include <fcntl.h>
#include <unistd.h>
#include <linux/parport.h>
#include <linux/ppdev.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(void) {
	int		PortFD;
	char	Data;

	PortFD = open("/dev/parport0", O_RDWR);
	ioctl(PortFD, PPCLAIM);
	for (;;) {
		ioctl(PortFD, PPRDATA, &Data);
		Data = ~Data;
		ioctl(PortFD, PPWDATA, &Data);
	};

	return 0;
};

You can now write:

#include "ParallelPort.h"

int main(void) {
	ParallelPort	Port;

	Port.Open("/dev/parport0");
	Port.DataOut(true);
	for (;;) {
		Port.Data(~Port.Data());
	};
};

Doesn't that make things so much wonderfully simpler?

Diagnostic Utility

Finally, after all that investigation, I got around to writing the diagnostic utility. Being the fan of OpenGL and GLUT that I am, I quickly wrote this utility with OpenGL and GLUT. To whet your appetite, here's a screenshot:

Screenshot

Screenshot of parallel port diagnostic utility

Using the keyboard, you can view and change the pins on your parallel port. You will need to install the GLUT development packages to compile this (e.g. freeglut3-dev).

By default, this program uses /dev/parport0. However, you can specify any other device to use as the parallel port as a command line argument (i.e. argc and argv). If there are any problems setting up the port for access, this program will tell you so.

Download the sources (includes ParallelPort library).

References

Here are a few links which I found incredibly useful during my search for information:

Please contact me if you have any cool ideas or suggests or corrections.


This page was last revised on 2010-09-21.

My logo (y^2) Valid XHTML 1.0 Strict Valid CSS!