Revisiting the parallel printer adapter
Dhananjay is a scientific officer for the instrumentation laboratory at the Inter-University Centre for Astronomy & Astrophysics (IUCAA) in Pune, India. He can be contacted at [email protected]. Larry is vice president of Warp Nine Engineering and can be contacted at [email protected].
Until about 1991, the speed, data buffering, and standardization of the PC's parallel-port adapter didn't advance as much as the host computer's performance. In 1991, however, printer manufacturers formed the "Network Printing Alliance" (NPA) to develop standards for printer control across networks. As part of its charter, the NPA defined new protocols that would increase performance as well as provide bidirectional communication and backwards compatibility.
Meanwhile, the parallel port was being adapted for nonprinting uses, including tape-backup systems, CD-ROM drives, and LAN adapters. Performance in these applications, however, was severely limited by data-transfer rates that could be achieved between PCs and peripherals -- then about 150 KB/sec. Additionally, the lack of a standardized electrical interface limited the maximum distance between the PC and the external peripheral to about six feet.
Consequently, the NPA requested that the IEEE form a committee to create a specification for high-performance (greater than 1 MB/sec) bidirectional parallel ports, while retaining compatibility with the older IBM PC parallel port. The IEEE 1284 committee released the "Standard Signaling Method for a Bi-directional Parallel Peripheral Interface for Personal Computers" in March 1994. In this article, we'll examine this standard and provide details about the Enhanced Parallel Port (EPP) operation mode. For background information, see "Using the Parallel Printer Adapter as a Host Interface Port," by Dhananjay V. Gadre (DDJ, April 1996).
Standard Parallel Printer Ports
The IBM printer adapter has three independent TTL compatible ports:
- An 8-bit output port, the Data Port.
- A 4-bit output port with open collector drivers, the Control Port.
- A 5-bit input port, the Status Port.
The two output ports (Data and Control) also have an input register at the same address, which can be read back to get the latched value. Control Port output ports have open collector drivers, typically with 4.7-Kohm pull-up resistors. The Control Port can also be used as an input port by writing 1s to the Control register and performing a read operation.
One of the Status Port pins (usually the nAck line) is connected to one of the PC IRQs and enabled through the Control register.
The port addresses are stored in the BIOS Data Table in RAM (0040:0008,0009 for LPT1, :000a,000b for LPT2, and :000c,000d for LPT3) of the PC. Table 1 shows the pin position of the three ports of the adapter on the 36-pin Centronics connector and the DB-25 connector; the corresponding bit location in a byte-wide register; and the logic relation between the register contents and the output pin. (A starred entry in the Bit column means that if the register bit contains 0, the output pin is at 1.)
The IEEE 1284-1994 Standard
The IEEE 1284 standard, which defines bidirectional communication between the PC and external peripherals, can communicate 50 to 100 times faster than the original parallel-port specification. The protocol remains backward compatible to all the existing parallel-port peripherals and printers.
IEEE1284 defines five data-transfer modes:
- Compatibility mode. Forward direction only. Also referred to as "Centronics" or "Standard" mode.
- Nibble mode. Reverse direction only. Four bits at a time using status lines for data.
- Byte mode. Reverse direction only. Eight bits at a time.
- EPP (Enhanced Parallel Port) mode. Bidirectional, half-duplex.
- ECP (Extended Capability Port) mode. Bidirectional, half-duplex.
All parallel ports can implement a bidirectional link using Nibble or Byte mode and the compatible mode. However, these methods are software intensive. The data-transfer software in a PC checks if the peripheral is ready (by probing the BUSY line), then places outgoing data onto the data lines and generates a strobe signal on one of the control lines. Similarly, for reading data, the PC reads the data on the data lines (in Byte mode) or a nibble on the status lines (in Nibble mode), then generates an acknowledge signal. Since this is so software intensive, data-transfer rates are limited to between 50 and 150 KB/sec.
Today's PCs, equipped with I/O controllers that have EPP and ECP capabilities, have hardware to assist the data transfer. In the EPP mode, a data byte can be transferred by a single IN/OUT instruction to the I/O controller, which handles the handshaking and strobe-signal generation. Clearly, data-transfer rates on such machines are limited by the rate at which the instruction can be executed. Typically, transfer rates around 1-1.75 MB/sec can be easily achieved on contemporary machines.
The 1284 standard not only specifies the data-transfer protocol, but also a physical and electrical interface definition. Additionally, it provides a method for the host and the peripherals to recognize the supported modes and negotiate the requested mode.
One thing the 1284 standard does not do, however, is specify how some conditions are to be interpreted by the host. For instance, the standard does not specify how an EPP device signals that it has data to send, or that an error condition has occurred. However, the EPP protocol has the ability to signal an address or command. The standard does not provide a definition or meaning to any particular address or command, just a method to send one. In short, 1284 is a low-level physical layer protocol.
The Enhanced Parallel Port
EPP protocol was originally developed by Intel, Xircom, and Zenith Data Systems to provide a high-performance parallel-port link that was compatible with existing parallel-port peripherals and interfaces. Intel implemented this protocol in the 360SL I/O controller chipset. However, this was prior to the establishment of the IEEE 1284 committee.
Since EPP-capable parallel ports were available before the release of 1284, there is a difference between pre-IEEE EPP ports and 1284 EPP ports.
The EPP protocol defines four data-transfer cycles:
- Data Write.
- Data Read.
- Address Write.
- Address Read.
- Data Read.
Data cycles are used to transfer data to and from peripheral devices, while address cycles are used to exchange device addresses, control information, and so on. However, they can be viewed as two different data cycles.
The Enhanced Parallel Port protocol defines the SPP signal names; see Table 2. Of the 17 SPP signals, EPP utilizes 14 for data transfer, handshake, and strobe. You can use the unused signals for product-specific purposes. Figure 1 illustrates the Data Write cycle, while Figure 2 shows Data Read.
Data Write cycles as follows:
- 1. The program executes an I/O write cycle to the EPP DATA port.
- 2. The nWRITE line is asserted and data is output on the parallel-port data lines.
- 3. The data strobe is asserted, since nWAIT is asserted low.
- 4. The port waits for the acknowledge signal from the peripheral (nWAIT deasserted).
- 5. The data strobe is deasserted and the EPP cycle ends.
- 6. The ISA I/O cycle ends.
- 7. nWAIT is asserted low to indicate that the next cycle may begin.
- 2. The nWRITE line is asserted and data is output on the parallel-port data lines.
Data Read cycles as follows:
- 1. The program executes an I/O read cycle to the EPP DATA port.
- 2. The data strobe is asserted, as the nWAIT is asserted low.
- 3. The port waits for the acknowledge from the peripheral (nWAIT deasserted).
- 4. The port reads the DATA bits and the data strobe is deasserted.
- 5. The ISA cycle ends.
- 6. The EPP cycle ends.
- 2. The data strobe is asserted, as the nWAIT is asserted low.
The Address Write/Read cycles (see Figures 3 and 4) operate the same as Data Write/Read, except that the nDATASTB signal is replaced by nADDRSTB.
EPP Registers
The register model for EPP mode is an extension of the IBM parallel-port registers. Again, the SPP consists of three registers, offset from the port's base address: Data Port, Status Port, and Control Port. The base address is the address where the Data register is located in the PC's I/O space. This is typically 0x378h for LPT1 or 0x278h for LPT2. The most common EPP implementations expand this to use ports not defined by the SPP; see Table 3.
By generating a single I/O write instruction to "base_address + 4" (0x27C, for example), the EPP controller generates the necessary handshake signals and strobes to transfer the data using an EPP Data Write cycle. I/O instructions to the base addresses, ports 0 through 2, will generate behavior exactly as with a standard parallel port, guaranteeing compatibility with standard parallel-port peripherals and printers. Address cycles are generated when read/write I/O operations are generated to "base_address + 3."
Ports 5 through 7 are sometimes used differently in different hardware implementations. These may be used to implement 16- or 32-bit software interfaces, for configuration registers, or not at all. Most controllers use these addresses to support 32-bit I/O instructions. The ISA controller will intercept the 32-bit I/O and actually generate four fast 8-bit I/O cycles, to registers +4 through +7. The first cycle is to the addressed I/O port using byte 0 (bits 0-7), the second cycle to port+1 using byte 1, the third to port+2 using byte 2, and finally, port+3 using byte 3. These additional cycles are generated by hardware and are transparent to the software. The total time for these four cycles is less than four independent 8-bit cycles. This enables the software to use 32-bit I/O operations for EPP data transfer. Address cycles are still limited to 8-bit I/O.
Transferring data to/from the PC using a single instruction is what enables EPP-mode parallel ports to transfer data at ISA bus speeds. Rather than having the software implement an I/O-intensive software loop, a block of data can be transferred with a single REP IO instruction. Depending on the host-adapter port implementation and the capability of the peripheral, an EPP port can transfer data from 500 KB/sec to nearly 2 MB/sec. This data-transfer rate is more than enough to enable network adaptors, CD-ROMs, tape backups, and other peripherals to operate at nearly ISA-bus-performance levels.
The EPP protocol and current implementations provide a high degree of coupling between the peripheral driver and the peripheral. This means the software driver is always able to determine and control the state of communication to the peripheral at any time. It's easy to mix read and write operations, as well as block transfers. This type of coupling is ideal for register-oriented or real-time controlled peripherals such as network adaptors, data-acquisition operations, portable hard drives, and similar devices.
EPP BIOS Calls
When writing directly to the I/O controller, it is a good idea to use EPP BIOS calls for data transfer. Using BIOS calls enables data transfer without dealing with the I/O controller chip. Another advantage of using the BIOS is that if the host does not have an EPP-capable port, the software would still run using the EPP-emulation mode (though at a highly reduced rate).
EPP BIOS calls provide a way to perform single I/O cycles and block transfers. While the BIOS specifications are too extensive to discuss in detail here, we will describe some useful calls, including:
- Installation check is used to test for the presence of an EPP port.
- Set mode is used to set the operating mode of the EPP port.
- Get mode is used to query the current operating mode of the EPP port.
- Interrupt control is used to enable or disable the interrupt associated with the EPP port.
- EPP reset is used to reset the peripheral connected to the EPP port.
- Address read is used to perform an address-read I/O cycle.
- Address write is used to perform an address-write I/O cycle.
- Write byte is used to output a single byte via the EPP data port.
- Write block is used to output a block of user-specified data from a defined buffer via the EPP data port.
- Read byte is used to read a single byte via the EPP data port.
- Read block is used to input a stream of bytes into a user-defined buffer via the EPP data port.
- Device interrupt is used to allow an EPP device driver to install an interrupt handler that is called whenever an EPP device interrupt occurs.
At this time, only a few companies have implemented EPP BIOS. Consequently, drivers will have to be written to use the registers directly. Listing One presents the routines to implement high-speed digital I/O using EPP BIOS calls The complete proposed EPP BIOS specification can be obtained from the IEEE P1284.3 draft specification; see http://www.fapo.com/ieee1284.htm.
High-Speed Digital I/O Using EPP
Figure 5 shows the schematics for a simple data and address I/O expansion port. The circuit shows two latches, two buffers, and a simple decoding circuit. The circuit can be easily understood using the timing diagrams in Figures 1 through 4.
The latches are used to store a data byte as well as an address byte. The buffers are also read as data input as well as address input. In this simple circuit, the BUSY signal is generated from the combination of the nDATASTB and the nADDRSTB signals. In a more complex circuit (one using a processor, for instance), the BUSY signal could be generated when the processor actually reads the data (or the address) so as to ensure that data (or address) is not lost.
For More Information
Warp Nine Engineering
3645 Ruffin Road, Suite 330
San Diego, CA 92123
619-292-2740
http://www.fapo.com/ieee1284.htm
Listing One
/* Routines for Digital I/O using the Enhanced Parallel Port. Routines invoke EPP BIOS calls. Typical results: Block transfer rates: 65 Mbytes in 73 secs on 486/66 */ </p> #include<dos.h> #include<time.h> #include<stdio.h> #define FALSE 0 #define TRUE 1 void far (*pointr)(); int epp_config(void); int epp_write_byte(unsigned char tx_value); int epp_write_block(unsigned char *source_ptr, int count); int epp_read_byte(unsigned char *rx_value); int epp_read_block(unsigned char *dest_ptr, int count); int epp_config(void) </p> { unsigned char temp_ah, temp_al, temp_cl, temp_ch; _AX=0x0200; _DX=0; _CH='E'; _BL='P'; _BH='P'; geninterrupt(0x17); temp_ah=_AH; temp_al=_AL; temp_ch=_CH; temp_cl=_CL; if(temp_ah != 0) return FALSE; if(temp_al != 0x45) return FALSE; if(temp_ch != 0x50) return FALSE; if(temp_cl != 0x50) return FALSE; pointr = MK_FP(_DX , _BX); /*printf("\nEntry Address is %Fp", pointr);*/ _AH=1; _DL=0; _AL=0x04; pointr(); temp_ah=_AH; if(temp_ah != 0) return FALSE; return TRUE; } </p> int epp_write_byte(unsigned char tx_value) { unsigned char temp_ah; _AH=7; _DL=0; _AL=tx_value; pointr(); temp_ah=_AH; if(temp_ah != 0) {return FALSE;} return TRUE; } </p> int epp_write_block(unsigned char *source_ptr, int count) { unsigned char temp_ah; _SI=FP_OFF(source_ptr); _ES=FP_SEG(source_ptr); _AH=8; _DL=0; _CX=count; pointr(); temp_ah=_AH; if(temp_ah != 0) {printf("\nBlock write timeout error"); return FALSE;} return TRUE; } </p> int epp_read_byte(unsigned char *rx_value) { unsigned char temp_ah; _AH=9; _DL=0; pointr(); *rx_value=_AL; temp_ah=_AH; if(temp_ah != 0) {return FALSE;} return TRUE; } </p> int epp_read_block(unsigned char *dest_ptr, int count) { unsigned char temp_ah; _DI=FP_OFF(dest_ptr); _ES=FP_SEG(dest_ptr); _AH=0x0a; _DL=0; _CX=count; pointr(); temp_ah=_AH; if(temp_ah != 0) {return FALSE;} return TRUE; } main() { int ret_value, ret_val; time_t start, end; unsigned char buf_out[10000], buf_in[10000], rx_in; clrscr(); printf("Fast Digital I/O using the Enhanced Parallel Port"); printf("\nUses EPP BIOS Calls\nDhananjay V. Gadre\nJuly 1996"); ret_value = epp_config(); if(ret_value == FALSE) { printf("\nNo EPP"); exit(1);} printf("\nEPP Present"); printf("\n\nWriting Data Byte"); ret_val = epp_write_byte(0xa5); if (ret_val == TRUE) printf("\nWrite Byte Successful"); else printf("\nTimeout error"); printf("\n\nWriting Data Block"); start=time(NULL); for(ret_value=0; ret_value<1000; ret_value++) ret_val=epp_write_block(buf_out, 52428); end=time(NULL); printf("\nTime taken= %d seconds for 50 Mbytes", end-start); printf("\n\nReading Data Byte.."); ret_val=epp_read_byte(&rx_in); if (ret_val == TRUE) printf("\nRead Data Byte Successful, %x'', rx_in); else printf("\nRead Data byte failed"); printf("\n\nReading Data Block.."); ret_val=epp_read_block(buf_in, 1000); if (ret_val == TRUE) printf("\nRead Data Block Successful"); else printf("\nRead Data block failed"); }
DDJ
Copyright © 1997, Dr. Dobb's Journal