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:
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.
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 |
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'.
/* 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
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:
[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.
/* 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().
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.