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:
- Data register - controls 8 bit data bus which could be configured for input or output
- Status register - mapped to 5 pins of the parallel port connector which can be read only
- 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:
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):
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 |
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 |
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:
- No IRQ support
- 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:
- 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.
- 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.
- 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:
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:
- General infromation on the parallel port: http://www.beyondlogic.org/spp/parallel.htm
- ppdev information: http://people.redhat.com/twaugh/parport/html/parportguide.html
Please contact me if you have any cool ideas or suggests or corrections.
This page was last revised on an unknown date.