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.
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....
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.
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:
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.
There are a few other things about C that are useful for you to know in doing your labs. Here's a partial list: