Parallel Port Interface Reference Materials

One of the simplest to use, and most common, computer interfaces is the parallel (printer) port of an IA32 PC. One good place to learn a bit about the parallel port is Parallel Port Central. In contrast, this page, collected/created by H. Dietz in Spring 2000, more specifically discusses what you will need to know to use the parallel port interface in the EE481 lab experiments.

How Do You Connect Your Circuit To The Port?

A PC can have any number of parallel ports, but usually has one or two. The PCs in 581AH generally have two: one on the motherboard, another on an add-in card. Typically, a "female" DB25 connector is on the back of the PC; a printer cable connects that to either a "centronics" connector or another DB25. Unfortunately, none of those connectors are easy to plug into your prototyping boards. Thus, we have made simple (and somewhat fragile) cables that each have a "male" DB25 on one end and a 24-pin DIP header on the other. The DIP header looks like:

The numbers in the figure above correspond to the numbers molded into the plastic on the connector.

Notice that the DIP header has no connection to power -- it is powered by the computer. However, you do need to connect Ground to your circuit so that the computer and your circuit share the same 0-reference voltage. In fact, you should connect Ground before connecting anything else.

The D outputs all appear to be standard TTL logic outputs, although they are often implemented using high-integration CMOS. Use them like you would ordinary TTL gate outputs, but keep the current load small because the high-integration drivers easily can be damaged.

The S inputs behave like standard TTL inputs. However, S7 is read in software as the complementary logic value. It is also worth noting that these inputs are not fed through any noise reduction logic and the long cable is very susceptible to noise. Consequently, you always should isolate these inputs from noisy input signal sources by using an appropriate TTL driver (e.g., a schmidt trigger).

The C wires are open collector outputs that the computer can also read, which means that they also can serve as inputs. Typically, each open collector output has about 5K Ohms pulling it toward +5V, so it will float high unless about 1ma of current is sunk to ground. In the labs, we generally try to avoid using these lines....

How Does Software Access The Parallel Port?

Well, there are a lot of possible answers to this question. However, you are generally not using the parallel port as it would be used by a printer, so you need a more direct way to control the outputs and sense the inputs. If this sounds like assembly language programming is needed, yes, it is -- but you do not need to write that code. Instead, we have two simple functions that wrap assembly language output and input instructions in C-callable functions. So, all you need are those functions and an appropriate C compiler.

Well, there is one other thing: you need to know which parallel port. In general, the support code for the lab is written assuming that you are using a secondary parallel port at the base address 0x278. After all, it is a lot cheaper to replace a burnt-out parallel port card than a complete motherboard. However, if you must use the parallel port on the motherboard, it has a base address of 0x378. Of course, port addresses on other PCs may be different... 0x3bc is another common parallel port base address.

The C Compiler

There are lots of C compilers out there, but GCC, the Gnu C Compiler, has become the freely-available standard for most systems. Under Linux, you can use GCC directly. Under a largely 16-bit DOS/Windows environment (such as we have in 581AH), things are a bit more complex because GCC's output is 32-bit code; to run under either DOS or Windows, each compiled program must contain its own little 32-bit environment. The good news is that DJGPP is a version of GCC that runs under DOS/Windows and does exactly what we need, and it's free.

For the most part, the exact same C code will work on a PC using Linux GCC or DOS/Windows DJGPP. The prime exception is that the Linux operating system protects the hardware port registers from ordinary user access whereas DOS, Windows 3.11, Windows 95, and Windows 98 do not. Thus, under Linux, before attempting to access the ports, you would need to be root and call either iopl(3) or ioperm(...). Incidentally, Windows NT prevents this type of direct port access, so you literally cannot do this stuff with NT.

To prepare, compile, and run a program named file.c using DJGPP on the machines in 581AH:

  1. Start a DOS shell. This can be done by selecting MSDOS from the "Start" applications menu.
  2. CD C:\WINDOWS\DESKTOP\DJGPP
    (note: that doesn't need to be in uppercase)
  3. Use any text editor to edit/create file.c; editors available include vim, jove, and various standard DOS/Windows editors like notepad.
  4. gcc file.c -o file
  5. Run your program by:
    file
    Or, if you must use the motherboard parallel port:
    file 0x378

The C Code Interface

Here's what the parallel port looks like to a program:

As you can see, a parallel port actually consists of three separate 8-bit I/O port registers. The 0x numbers above each bit position show the hex value of that bit position, as it is written in C... more about that stuff later. For now, the question is how do we get at these ports? Well, you can read the value from a port register using:

inline unsigned int
inb(unsigned short port)
{
  unsigned char _v;
  __asm__ __volatile__ ("inb %w1,%b0"
			:"=a" (_v)
			:"d" (port), "0" (0));
  return(_v);
}

You can write a new value to a port register by:

inline void
outb(unsigned char value,
unsigned short port)
{
  __asm__ __volatile__ ("outb %b0,%w1"
			:/* no writes */
			:"a" (value), "d" (port));
}

I know -- that doesn't look so easy. True enough, but that's the assembly code and C wrappers that I've given to you... all you need to do is call them.

For example, inb(0x278+1) would read and return the value of the register containing S3, etc., from the parallel port whose base address is 0x278. If you want to know if S5 is logic 1, simply check to see if (inb(0x278+1)&0x20) is non-zero. To output the pattern {D7=1, D6=0, D5=0, D4=0, D3=1, D2=1, D1=0, D0=0}, you would simply call outb((0x80|0x08|0x04),0x278+0);. Notice that, given that you know the hex values of the bit positions, C's & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), and ~ (NOT) operators come in very handy for this sort of thing.... Here's an even handier set of #defines:

#define	D7	0x80
#define	D6	0x40
#define	D5	0x20
#define	D4	0x10
#define	D3	0x08
#define	D2	0x04
#define	D1	0x02
#define	D0	0x01

#define	S7	0x80	/* note: NOT(S7) is on the input */
#define	S6	0x40
#define	S5	0x20
#define	S4	0x10
#define	S3	0x08

#define	C3	0x08	/* note: NOT(C3) is on the wire */
#define	C2	0x04
#define	C1	0x02	/* note: NOT(C1) is on the wire */
#define	C0	0x01	/* note: NOT(C0) is on the wire */

Sometimes it helps to see a complete program. Take a look at dumb.c.

Other Useful C Things...

There are a few other things about C that are useful for you to know in doing your labs. Here's a partial list:

  • Although most DOS/Windows programs can be run simply by double-clicking on them, if you run programs from a DOS command line, you can give command-line arguments (e.g., specifying a different portbase) and you can see the output generated by things like printf(). This helps a lot.
  • The usleep(t) call will sleep for about t microseconds. This is very useful for inserting delays between I/O operations. For example, usleep(100000) will return about 100000/1000000th second (aka 1/10th second) after you call it. For comparison, a typical call to outp() takes only about 1 microsecond, so consecutive port outputs would generate roughly a 1MHz waveform... fine for viewing on a scope, but way too fast to see LEDs flashing.