Reading switches from the parallel port

I've been working on a side project that requires me to sense the state of a few mechanical switches from software, so I've been spending some time working on parallel port programming.

There are some good resources out there for understanding parallel ports. I've been reading:

The problem is that parallel ports have been around for a long time, and went through lots of weird permuatations before the IEEE standard was written in 1994. A lot of the information is good, but describes the world as it was 10 years ago, not the way it is now.

So, there are a few things it's useful to know about today's parallel ports:

  • There are three flavors of parallel ports (and some other variants). SPP, EPP, and ECP. Most systems support all three, but you likely have to set the flavor you want in the BIOS.
  • There is lots of stuff written about doing input using SPP. Some of it uses the status lines as input (5 bits max). Some of it uses behavior that is implementation defined. Some uses extra hardware. The vast majority of it is irrelevant if you can use EPP, so don't spend too much time looking at the SPP docs.
  • You will see discussions about the need for pull-up resisters. I've written a bigger discussion later in this post, but the short form is that most (if not all) printer ports are likely to have built-in pull-up resisters.
  • You can't access the ports directly on NT-based operating systems - you need some sort of driver. I'm using Inpout32.dll for WIN NT/2000/XP .
  • EPP ports aren't automatically configured for input. You need to set bit 5 in the control register (at the base address + 2) to turn it on.

That aside, onto the fun. But first, a few words of advice.

Tread lightly, young maker, lest ye mess something up and release the magic smoke in your motherboard.

Things you'll need:

  • A male DB25 connector that plugs into the parallel port.
  • Some way to connect to the connector. If it's a solder style, you'll need wire (use an old diskette ribbon cable...), solder, and a soldering iron. And a bit of skill.
  • A ohmmeter or some sort of continuity tester. You can build one with a light bulb, a battery, and a wire. If you can't, then maybe you shouldn't be trying this...
  • A switch. Momentary contact, normally open. Or just two bare wires you can touch.
  • Wire. I use patch leads (leads with clips on the end) for trying things out.

The data lines on the parallel port are lines 2-9, where pin 1 is the top right pin when looking at the connector on the motherboard. If you're younger than I am, you may be able to read the embossed numbers on the male connector.

The ground lines are pins 18-25, designed on the maxim that you can't have too many grounds. You could also use the computer chassis ground if it's more convenient.

So, hook one side of your switch to pin 2 (data line 0) and the other to one of the ground pins (surprise me!). Inspect your work to make sure you haven't bridged any pins, and hook your continuity tester to the pins on the connector to make sure pressing the switch completes a circuit between those pins.

Preparing the computer

So, you need to set your port to EPP in the BIOS, download inpout32, and start up your favorite development environment. If you're using C#, you can find some info on using inpout32 through PInvoke here. You'll need to add the appropriate definition for the In32 method.

Oh, and plug the connector in with your computer turned off.

Writing the code

The code basically looks like this:

const int LPT1BASE = 0x378;

Out32(LPT1BASE + 2, 0x20);

int lastData = 255;

while (true)
{
    int data = In32(LPT1BASE);
    if (data != 255 && data != lastData)
    {
        // do something - print it out of something
    }
    lastData = data;
    Sleep(50);
}

Pretty tough, huh? When you run this and press the button, data should be 254 if you wired things up. If it doesn't, go back and check your wiring, your parallel port setting, etc.

You may have figured out that this approach uses polling. Yes, it does, and that means it consumes a bit of CPU on an ongoing basis. If I've read the docs correctly, there are ways to make this interrupt-based, with some more external hardware and a lot more code. You are welcome to spend time on that, but given the current state of the art, at that point it's less hassle to program a microcontroller that speaks serial or even USB.

That's it. And now for a brief aside.

<aside>

You may have asked yourself, "why does connect something to ground turn it on, and is a pull-up resistor related to high school gym class?"

But that will take a bit of history. Ancient history. Even before *I* was born history.

Way back in 1962, Texas instruments introduced the TTL logic family, forever enshrining the +5V power supply in the world of computers and electronics.

Because of the characterstics of the TTL circuit design, a TTL output held at high can only source about 0.4 mA of current. That's not much. A TTL output held low, however, can source something like 15 mA of current. That makes it preferable to sink current rather than source it.

(those with a big electronics background are probably cringing at the simplification here. Feel free to comment...)

There's also another problem that comes up related to interfacing. Say that you wanted to hook up a TTL output to a system that uses 12V to switch a relay. You need an output that only goes from 0V to 12V, but the TTL "high" output is only 5V (it actually isn't - it's more like 3 volts).

So, what you do is, you use what is called an open collector design output. The output can either be low (pulled to ground), or a floating state. You attach a resistor between your 12V source and the output, and this resistor "pulls up" the floating state up to 12V.

You also use this sort of thing when you want to drive a bus - you can let the output float with the bus most of the time, and then switch in the pull-up when you need it (real buses use different techniques, but you get the idea). Pull ups are a simple way to get what you want, but use up power and aren't terribly fast. But this was the early 60s, after all...

How does this relate to parallel ports? Well, the data pins on the ports are two-way, and early ports didn't provide pull-ups, so you would need to add them yourselves.

Oh, and one final question. Why do parallel ports use 1960s technology?

Well, TTL logic has problems, because it's bipolar transistor-based (the Ts in TTL), it's pretty tolerant of static and therefore a good choice for a bus line. You could do something similar in CMOS but you'd have to put a lot more effort into input protection. There are logic families that look like TTL but are CMOS inside, and I suspect that most parallel ports these days take that approach.

</aside>