Interfacing to a parallel port

Connecting electronics to the parallel port of a PC is great way to have some interaction with the real world - whether you are flashing LEDs, reading switches or controlling far more complicated systems. However, since the days of DOS it has become more tricky as the operating system actively gets in the way of you manipulating the port(s) directly.

There are pieces of software for various OS's, such as Windows, that can circumvent these restrictions in one way or another. I wanted to get this capability on the cheap and without devious fiddling - which means using:

C++

I decided on C++ for several reasons - mostly personal preference. It is a superset of C, with better string handling, support for complex numbers, object orientation, the list goes on. I like the features it has over C; it still lets you get 'close to the machine' and is fast. These are distinct advantages when doing work with external hardware.

Because I am using a Linux OS, I chose the GNU Project's free C/C++ compiler, gcc. The C++ compiler is actually called g++, but it's all part of the same package. If you are using Linux, it is almost certainly installed by default.

If you are new to C++, may I recommend some books that you will find useful.

Parallel port basics

Pinout

The PC parallel port has the following pinout and register assignment.

Pin number Pin name Signal direction Description Associated register Register bit number Register bitmask
1 C0/ Output Strobe Control 0 0x01
2 D0 Output Data bit 0 Data 0 0x01
3 D1 Output Data bit 1 Data 1 0x02
4 D2 Output Data bit 2 Data 2 0x04
5 D3 Output Data bit 3 Data 3 0x08
6 D4 Output Data bit 4 Data 4 0x10
7 D5 Output Data bit 5 Data 5 0x20
8 D6 Output Data bit 6 Data 6 0x40
9 D7 Output Data bit 7 Data 7 0x80
10 S6 Input ACK - Acknowledge Status 6 0x40
11 S7/ Input Busy Status 7 0x80
12 S5 Input Paper Out Status 5 0x20
13 S4 Input Select In Status 4 0x10
14 C1/ Output Auto Linefeed Control 1 0x02
15 S3 Input Error Status 3 0x08
16 C2 Output Initialise Printer (Reset) Control 2 0x04
17 C3/ Output Select Printer Control 3 0x08
18 - 25 GND N/A Ground N/A N/A N/A

Data register

Status register

Control register

Getting control of the port

In Linux, access to the hardware ports (parallel, serial...) is restricted unless you are the root (superuser). Now you may just be content to log in as root and always run your hardware-controlling programs from that account - but that is risky and not really acceptable if, say, you are writing code and building hardware for someone else. They will probably want to run their system from their own (non-root) account - and you should want that too.

The problem is how to get control of the port when you need it, and yet do so without being root all the time. I found the answer in an excellent article written for the Linux Gazette by P.J. Radcliffe, called 'Linux: A Clear Winner for Hardware I/O'. I'm basically ripping off his technique here, with a few very minor modifications of my own.

Radcliffe's idea is straightforward - his interfacing projects use two programs rather than just one. The first program (owned by the root user) gives port access to the second program (owned by the non-root user). This 'first program' is what I think of as an 'I/O enabler' - it sets the permissions that enable the second program to access the port as it needs.

As an example, you might have an 'I/O enabler' program called io and a parallel port interfacing program called toggle_d0 (perhaps it flashes an LED attached to the D0 port pin - see below =) ). You would run your LED flasher like this:

./io ./toggle_d0

The './io' runs the I/O enabler program, and this program takes the command to start the second program, toggle_d0, as an argument. './io' runs first, sets the port permissions, and then runs './toggle_d0'.

An I/O enabler program

This is about the simplest I/O enabler program that can be made. It allows access to only the first parallel port, that normally has a hex address of 0x378 (888 decimal). It's a very stripped down version of Radcliffe's original, with some changes to the comments for additional clarity.

/* Filename: IO.CPP Purpose: Grant hardware port access rights to a user-written program Date: Monday 17 October 2005 Author: Chris Carter, chris.carter@iee.org Credits: Essentially, this is a copy of code written by P.J. Radcliffe (see http://linuxgazette.net/112/radcliffe.html) Usage: ./io ./<user-program-name> <parameters> */ // Header files #include <iostream> // For 'cout' #include <cstdio> // C++ wrapper for 'stdio.h', needed for 'perror' #include <unistd.h> // C library needed for 'ioperm', 'setgid', etc. (libc5) #include <sys/io.h> // C library needed for 'ioperm' (glibc) #include <sys/resource.h> // C library needed for 'setpriority', etc. // Use enumeration rather than #define in C++ enum { lp0_base_addr = 0x378, // Define the base address of the first parallel port (in hex) lp_length = 3 // Define the number of register bytes that should be set, above // the base address }; using namespace std; // Start of the main function int main(int argc, char *argv[]) { if (argc < 2) // Check if the user supplies the name of a program to execute { cout << "You must supply the name of a program to run!" << endl; exit(-1) ; } // Sets the process execution priority ('niceness'). Negative values increase the // priority, positive values decrease it, zero is the default. // See: man getpriority setpriority(PRIO_PROCESS, 0, 0); // Gain access to the port(s) and REMOVE the root privilege by setting the port // access permission bits to '1'. if (ioperm(lp0_base_addr, lp_length, 1)) { perror("Attempt to change permissions on port lp0 failed"); exit(-1); } // Change the Group and User IDs of this program setgid(getgid()); // Sets the Group ID of the current process setuid(getuid()); // Sets the User ID of the current process // Replace the current running program image ('io') with another one. // argv[1] is the name of the program supplied by the user. execvp(argv[1], &argv[1]); // If 'execvp' succeeded, we will never execute anything past this point! perror("Unable to execute the user program"); cout << "Execution of " << argv[1] << " failed." << endl; exit(-1) ; } // End of file.

[Source text file: io.cpp]

Compile with:

g++ -Wall io.cpp -o io

Before this code can be used we have to change the file permissions on it, to make it owned by root.

su root chown root io chmod u+s io

Controlling an LED

OK, this is hardly the most earth-shaking project around, but if you can turn an LED on and off, you can control the port output pins.

WARNING - This is NOT the preferred way to interface to a parallel port; to be safe a buffer should be used between the output of the port and the electronics being driven, so that if a mistake is made the parallel port circuitry on the motherboard doesn't die. However, this circuit is just about the quickest way to see something work without having to build a lot of interfacing stuff beforehand.

You'll need the following components:

Wire everything up as shown in the schematic below.


[PDF schematic: one-led.pdf]

The LED is connected between the D0 (Data 0) output (pin 2), and GND (pin 25). When D0 is high (about +3.3 V on my parallel port), the LED lights.

Code: LED flasher

Now we need some C++ to control the D0 port pin and our LED. Below is some very amateurish code that does exactly that - I make no apologies for its clunkiness at this point, as its purpose is to be blindingly obvious rather than extremely clever! (We'll get to the cleverness later).

/* Filename: TOGGLE_D0.CPP Purpose: Toggle output line D0 (Data 0) of the parallel port repeatedly. Date: Monday 17 October 2005 Author: Chris Carter, chris.carter@iee.org Usage: ./toggle_d0 */ // Header files #include <iostream> #include <cstdio> #include <cstdlib> #include <unistd.h> #include <sys/io.h> using namespace std; unsigned int port = 0x378; // Address of the first parallel port in hexadecimal long int i; // A loop counter variable long int tdelay = 5000000; // A trial-and-error number to give an acceptable delay // Start of the main function int main() { while (1 > 0) // Loop forever { outb(0, port); // Send 0 to the port (turns D0 - D7 off) i = tdelay; // Crude delay loop while (i > 0) { i = i - 1; } outb(1, port); // Send 1 to the port (turns D0 on) i = tdelay; // Same crude delay loop again while (i > 0) { i = i - 1; } } exit(0); } // End of file.

[Source text file: toggle_d0.cpp]

Compile this with:

g++ -Wall toggle_do.cpp -o toggle_d0

Now connect your LED hardware to the port, and run the program in conjunction with the I/O enabler.

./io ./toggle_d0

If all is well, you should be rewarded with a slowly blinking LED.

As you can see, the code is really simple, consisting simply of an endless loop that writes out an alternating 1 or 0 to the D0 port pin using the function outb().

LED output port monitor

Now that we know our port is working we can move on to something much more useful. The circuit given below allows you to safely monitor the state of all the parallel port outputs simultaneously, on eight LEDs. This is very handy when you are writing more advanced controller code, and would like to know what the port outputs are doing.

In this circuit, we buffer the port ouputs, D0 - D7, using a 74LS244 tri-state octal buffer/line driver i.c. This allows us to provide the parallel port with some degree of isolation from the LED loads we will connect to it, and is good practise for nearly all but the simplest interfacing projects. Because the i.c. requires a +5 V supply, we derive this from a +9 V battery with a small voltage regulator. The 78L05 can supply about 100 mA at 5 V, more than enough for this application.


[PDF schematic: parallel-data-port-monitor.pdf]