One task that faces many engineers when evaluating a new device is finding a way to control its various functions. It's sometimes quite difficult to figure out how to read and write data to and from its registers. This can involve setting up data generators, logic analyzers, and other equipment. In order to be useful, the device must have the correct timing sequence.
Another solution is to use a dedicated digital I/O board. These are often quite expensive and may require special software. A good alternative is to use an I/O port that exists on virtually all PCs and laptops—namely, the printer port. That port is often left idle in an environment of networked printers and servers. Even if it is being used by a printer or scanner that can't be disconnected, it's possible to buy a second printer-port card for less than $50.
The most basic type of printer port is the AT version. It contains an 8-bit output port (data port), a 5-bit input port (status port), and a 4-bit output port (control port). Later models of PCs improved on this slightly. The PS/2 printer port has a data port that's bidirectional. It can be programmed to read data in as well as write it out. The most recent addition to the printer-port family is the enhanced printer port (EPP), which boasts data transfer rates of up to 1 Msample/s. This article will concentrate on the AT/PS2 versions, as these are the most straightforward and the easiest to program.
The name given to the printer port indicates its location in the PC's memory map. There are three possible locations in which the printer-port registers can reside:
|Port Name||Base Address|
|LPT1||0x378 (most common)|
A number of ways exist for determining where the printer port lies in the memory. The most foolproof is to examine the computer's BIOS information. This area of memory contains information about all of the peripherals which make up the computer, such as serial ports, disk drives, and of course the printer port.
When the PC is powering up, it first attempts to locate a printer port by checking address 0x3BC. It then checks address 0x378 and 0x278. As printer ports are discovered, their addresses are copied into locations in the BIOS memory. The first address will be stored at location 0040:0008, the second at 0040:000A, and the third at 0040:000C. Listing 1 is a C-code example of a function that will return the address of the first printer port found.
Once the user figures out which port he or she is working with, that port can start being used as a control device. As stated previously, the three ports comprising the printer port are data, status, and control. Each has an associated register. Figure 1 shows the contents of those registers and the pin assignments of the printer-port connector.
The data-port register contains 8 bits. Every bit corresponds to a pin on the printer-port connector. In other words, writing a logic 1 to a bit position will set the corresponding pin to a logic 1. To set data bit 0 (pin 2) and data bit 7 (pin 9) to a logic 1 while setting all other bits to a logic 0, load the register with 0x81 (10000001 in binary). While the data port is set to input mode, reading the register's contents will reflect the state of the pins on the port connector at the time of the read.
The status register contains 5 bits that reflect the state of the input pins on the D-type connector. The five signal lines used for data input are —BUSY, ACK, PE, SLCT, and ERROR. Note that the BUSY bit contains the inverse of the logic level on the —BUSY pin.
Though it needs some care in programming, the control register basically acts like the data register. It contains two bits (6 and 7) that are reserved and shouldn't be changed. Bit 5 controls the direction of the data port (logic 1 for input mode, logic 0 for output mode). Every time a falling edge is detected on the ACK pin, bit 4 can generate an interrupt. This bit should be set to a 0 for normal operation. Bits 3 to 0 will be used to set the state of the control-port pins on the D-type connector. The —SLCT —IN, ——AUTOFEED, and —STROBE pins will be the inverse of their register setting.
Understanding the registers and bits still doesn't illustrate how the printer port can be used as a control device, however. As an example, say the objective is to program a digital-to-analog converter (DAC) to produce a ramp at its output. The DAC used here is the AD7801, which is an 8-bit parallel-loading DAC.
To produce a voltage at the output, the digital value is placed on the data bus. The —CS and —WR lines are strobed to latch the data to the input register. Then the LDAC line is pulsed to update the DAC register and produce the output voltage. Figure 2 shows one way to go about this. The 8 data bits of the DAC are connected to the corresponding 8 bits of the data port. The ——AUTOFEED pin will control the —WR and —CS line of the DAC and the —STROBE for the —LDAC. The code to generate the ramp is shown in Listing 2.
If the PC is equipped with the bidirectional PS/2 port, the user can utilize that port to read 8-bit parallel data back from an external device. Unfortunately, there's no way to determine conclusively if the printer port is bidirectional purely from software. A reasonably reliable way to check is to set the data port to an input. Next, write and read back a few values from that port with nothing connected to the printer port. If what's read doesn't match what was written, there's a very good chance that the port is bidirectional.
The user can then set the direction bit in the control register of a unidirectional (AT) port. If nothing happens, the read will return the last byte that was written to the port. On the other hand, if the port is bidirectional, whatever is on the data pins can be read at the D-type connector. These have no bearing on what the user has written, so they'll most likely be different as they float either to ground or to the positive supply. Because this uncertainty exists, it's prudent to add resistors in series with the data lines. This will limit the current in the event that the data bus cannot be changed to an input.
Figure 3 shows how to configure the printer port to read from an external device. The device used here is the AD7821, an 8-bit parallel-output analog-to-digital converter. This converter is ideally suited to printer-port control. Unlike other ADCs, which require a conversion start signal, it can be configured so that the falling edge of —RD starts the conversion process. Data can be read on the rising edge of —RD. At 700 ns, the conversion time of the ADC is far faster than the operational speed of the printer port. So there's no need to monitor the ADC to find out when it has finished its conversion.
Before a conversion result can be read from the ADC, the user needs to change the direction of the printer port to input mode. Make sure that the —RD and —CS lines are high, as they would be when the ADC in idle. To generate and read a conversion result then, it's just a matter of bringing the —RD/—CS lines low, reading the data, and returning the —RD and —CS lines high again. Listing 3 shows the code which would read a sample from the ADC.
Simulating Serial Interfaces
The examples above demonstrate how the printer port can be used to control devices which have parallel interfaces. The parallel port also can simulate serial-port operating standards, such as the Motorola SPI and Microwire. These standards would typically have a serial clock, data in, data out, and frame-synchronization lines. In software, by associating these lines with a suitable line on the printer port, the user can set the lines high or low in a required sequence to read or write data to the device. This technique is known as "bit-banging."
While using the printer port in this manner, it will take considerably longer to complete a read or write sequence. But this method does have a number of advantages, one of which is the fact that speed isn't always the most important requirement in receiving or transmitting data. Sigma-delta ADCs can have very high resolution (often greater than 20 bits) and low throughput rate (less than 1 ksample/s).
Typically, when emulating a serial clock, it can take as little as 2 µs to set an output pin from high to low and back high again. Even allowing for the additional instructions required to process the data that's transmitted or received, it's quite conceivable to expect data transfer rates of 10 kHz or more. An example illustrates how the printer port can be used to read data from a device with a serial interface (Fig. 4). The AD7816 is a single-chip temperature sensor with a resolution of 0.25°C.
This example also demonstrates how to overcome a potential problem which can occur on parts with serial interfaces—bidirectional data pins. The AD7816 has a single pin which acts both as a data-in and a data-out line. If the user tries to drive the DIN/OUT pin with one of the data-port pins, data wouldn't be able to be read back. The data pin would be constantly driven by the printer port and a bus-contention situation would arise.
The direction of the data port could be changed from output to input every time the user wants to read. But an easier alternative is to use a tri-statable buffer, as shown in the circuit diagram (Fig. 4, again). To write to the AD7816, the RD/—WR line must be brought low. This will enable the buffer. The data line will be driven by the data-port (D0) pin. When the user goes to read the data, the RD/—WR line will be high. The buffer output is tri-stated so the data pin is free to output its data.
To read data from the AD7816, first write to the device. It needs to be told that the user wants to read from the temperature register. Then pulse the ——CONVST pin low to start a temperature reading. Finally, read out the data. A simple mathematical equation turns the digital code into a temperature value. While trying to communicate with a serial device, the designer needs to develop software routines to turn a parallel byte into a serial byte for write operations—and vice versa for read operations.
An easy way to do this is to use the logic AND function to mask out all the bits that are of no interest. In Figure 5, the byte that's to be sent to the device is logic ANDed with 0x80. This will give an answer of 0 if the MSB is a 0 and 0x80 if the MSB is a 1. The byte is then shifted left one position so the next bit can be tested. The C-code in Listing 4 is an example of how to read a temperature value from the AD7816.
Using Other Programming Languages
All of the examples above have demonstrated how to access the printer port using code written in C. Programming languages which have an equivalent to the C-code inportb and outportb also can be used to control the printer port. It's possible to create applications that can use the printer port and run under Windows.
Some Windows programming languages, such as Microsoft Visual Basic, have no direct way to access the printer-port registers. But there are ways to overcome this. One approach is to compile the C code that's written into a dynamic link library (DLL). This will allow a function to be defined in the Visual Basic program. That program will then be able to call the C code. Exactly how this is done will depend on which C compiler is being used. Microsoft offers an article explaining how to write a DLL for Visual Basic at their web site (Article No. Q106553). Just go to www.microsoft.com and do a search on Q106553.
A number of third party vendors offer various add-ons that give easy access to the printer-port registers. Links to these sites can be found on virtually any Visual Basic programming site.
The examples above have shown the basics of using the PC printer port as a control device. By expanding on these techniques and mixing them together, the humble printer port can be transformed into an important piece of test or control equipment.