Programming the Enhanced Parallel Port

The Enhanced Parallel Port defined by IEEE 1284 makes high speed parallel port communications much easier than the older PS/2 protocol. It implements handshaking in hardware, which frees the developer from having to implement or find this code, and it frees up the CPU during data transfers as well.

It was our experience, however, that developing for an EPP card (specifically the FarPoint F/PortPlus card with an SMC FDC37C666GT controller chip) was far from easy. We ran into a number of problems before we got the card to work, and so we present this page in the hope that others don't suffer as much as we did. "Parallel Port Complete" by Jan Axelson is an excellent book on the subject, and you might not need to look any further. It is a very recent publication.

  1. A brief intro to EPP
  2. Setting up EPP mode
  3. Transfering single bytes
  4. Status checking and clearing the timeout bit
  5. String transfers
  6. Resetting the EPP port
  7. Parallel Port links

Some quick background on EPP

The handshaking for an EPP transfer is performed by the hardware, and not by software as with the PS/2 parallel port protocol. This means that data is transferred in a single I/O cycle. This, of course, makes development easier and performance much faster.

The FDC37C666GT (as well as the FDC37C665GT) provides four 8-bit EPP ports, located at offsets +4, +5, +6, and +7 from the parallel port's base address (these are described in more detail in the technical spec paper on pages 92-96). An 8-bit I/O write (e.g., using the out assembler directive or the outportb function) to any of these four ports automatically transfers the data to the peripheral in EPP mode (assuming, of course, that the port was already placed in EPP mode). An advantage of having four ports is that if a 16- or 32-bit outinstruction is issued, the two or four bytes will be sent automatically in sequence to the peripheral. That is, if a 32-bit doubleword is output to the first of the four EPP ports (using, for example, the outsd) assembler directive, then the peripheral will receive the four bytes of the doubleword, even though only one I/O instruction was issued. Note that the peripheral cannot receive a single 32-bit value because the parallel port, even under EPP, is still only 8 bits wide. By using 16- and 32-bit transfers along with string transfer instructions (e.g., rep outsw) we can achieve high transfer rates.

There are two type of transfers: 'address' and 'data'. These are both 8-bit transfers, and they can be used interchangeably. The main difference is in the handshaking signals: the control signals for an address transfer are slightly different than for a data transfer. This allows an application to have, in effect, two distinct communication channels with the peripheral - values can be interpreted differently based on whether they are address or data values. In our application, for example, we use address transfers to implement our application protocol. The other difference between data and address transfers is that you cannot do 2- or 4-byte address transfers as you can with data transfers (see above).

Placing the chip into EPP mode (Corrected on March 5, 1997)

The technical spec paper for the FDC37C666GT provides the details of using the chip in EPP mode. However, the information in the paper is disorganized and incomplete, at best. An excellent resource is "Parallel Port Complete" by Jan Axelson. Page 212 starts the discussion on the SMC FDC37C665GT and FDC37C666GT chips. Here is a simple code example which demonstrates how we can reliably place the chip in EPP mode. Note that if you use a chip other than the above two, this code may not work.

#define ECR_OFFSET      0x402
#define CONTROL_PORT    2

typedef unsigned char Byte; 

/* port_addr = 0x278 or 0x378, chip = '666' or '665' */
void begin_EPP( int port_addr, int chip )
{
  begin_config_mode( chip );

/*
// control word for configuring
// CR  	Bits 1,	 Port address 	00      ->      disabled
//				01	->	0x3bc
//                             	10	->	0x378
//				11	->	0x278 (default)
//       Bit 2   Port power	1	->	power supplied (default)
//				0	->	low power mode
//  	 Bit 3	 Mode		1	->	SPP (default)
//				0	->	Extended modes allowed (CR4-bits 0,1)
// 	 Bit 4 	 IRQ polarity	1	->      active high, inactive low (default)
//				0	->	active low, inactive high-Z 
//				                (always true in ECP,EPP)
*/
	
  outportb( 0x3f0, 1 ); /* Set CR1 */
  if (port_addr == 0x378)
    outportb( 0x3f1, 0x96 ); // use 0x378
  else
    outportb( 0x3f1, 0x97 );  // use 0x278

/*
//	CR4	Bits 0,1  Ext modes	10	->	SPP, PS/2 (default)
//					01	->	SPP and EPP
//					10	->	ECP
//					11	->	ECP and EPP
//  		Bit 6	  EPP type	0	->	EPP 1.9 (default)
//					1	->	EPP 1.7
*/
	
  outportb( 0x3f0, 4 );   // use CR4
  outportb( 0x3f1, 3 );	  // use EPP

/*
//	CRA	Bits 0-3  ECP FIFO thres         -> threshold for ECP service requests
//						    default 0
*/
/*	Use if you need
  outportb( 0x3f0, 0xa );  // use CRA
  outportb( 0x3f1, 8 );    // threshold for ECP requests
*/

/*
// 0x34 == <0011 0100>
// PS/2 (byte, bidirectional) type (bits 7-5) == 001,
//	no interrupts at nError (bit 4) == 1,
//	disable DMA (bit 3) == 0,
// disable DMA and service interrupts (bit 2) == 1
// bits 1,0 read only, so don't care
*/
  outportb(port_addr + ECR_OFFSET, 0x34 );

/* pulse - nInit (bit 2) low */

  outportb(port_addr + CONTROL_PORT, 0x00 );
  end_config_mode();

/*
// ECP emulating EPP 0x80 == <1000 0000>
//	For EPP mode, set ECR of ECP to mode (bits 7-5) == 100
//	Falling edge of nError generates interrupt (bit 4) == 0,
//	disable DMA (bit 3) == 0,
// enable service interrupts (bit 2) == 0
// bits 1,0 read only, so don't care
*/
  outportb(port_addr + ECR_OFFSET, 0x80 );	/* Set ECR to EPP mode */

/*
// pulse - nInit (bit 2) high; reset the peripheral, 
// min pulse width 50 micro-sec
*/
  outportb(port_addr + CONTROL_PORT, 0x04 );
}

/*----------------------------------------------------------------------*/

/* These two functions are based on page 117 of the tech spec   */
/* and also in "Parallel Port Complete", Page 214               */
void begin_config_mode ( int chip )
{
  Byte init_code;

  switch(chip)
  {
  case 666:  init_code = 0x44; break;
  case 665:  init_code = 0x55; break;
  default:   fprintf(stderr, "Chip %d not supported!!!\n", chip); exit(1);
  }

  disable();
  outportb(0x3f0, init_code);
  outportb(0x3f0, init_code);
  enable();
}

/* 
// Note that there is a typo in Parallel Port Complete, page 214
// it says write to 0x3f1 instead of 0x3f0
*/
void end_config_mode ( void )
{
  outportb( 0x3f0, 0xaa );
}

/*----------------------------------------------------------------------*/

This code places the chip into EPP mode. It sets the necessary values in the configuration registers (pages 117-128 of spec), and in the Extended Control Register (buried in page 103 of spec).

Transferring data (and addresses)

Once the chip has been successfully placed into EPP mode, transferring data is trivial:
To write a byte of data:
  outportb( base_addr + 4, data );
To write an address byte:
  outportb( base_addr + 3, value );
To read a byte of data:
  data = inportb( base_addr + 4 );
To read an address byte:
  value = outportb( base_addr + 3 );

After each transfer, you should check the status register to see if an error has occurred (such as a timeout). The status register is described on page 94 os the spec. We found that we could ignore the PAPEREND and nACK bits when checking for errors (they would always indicate error, even though the data had been successfully transmitted).

Status checking and clearing the timeout bit

It is required that the timeout bit in the status register be cleared after every EPP operation. It is the bit 2 in the status register. In the above two chips (SMC 665 and 666) it is cleared by writing 1 (yes, 1 and not 0) to that bit. Writing 0 doesn't make a difference. If the timeout bit is set every EPP operation will fail.


typedef
struct _Status
{
 Byte timeout, error, select, paper_end, ack, busy;
} Status;

/* returns 1 when there was some problem with the last operation */
int check_status(int port_addr)
{
  Status stat;
  
  // read status and clear timeout bit
  read_status(port_addr, &stat);

  /* We ignore the 'paper_end' and 'ack' bit */
  return (stat.timeout || stat.error || ! stat.select || stat.busy);
}


#define STATUS_PORT 1

// bits in the parallel port's status register
#define STATUS_TIMEOUT  0
#define STATUS_nERROR   3
#define STATUS_SELECT   4
#define STATUS_PAPEREND 5
#define STATUS_nACK     6
#define STATUS_nBUSY    7

#define TEST_BIT( x, b )   (((x) & (1<<(b))) != 0 )

/* Checks and returns each bit of the status register */
void read_status(int port_addr, Status *stat)
{
  Byte status;

  /* read the status port */
  status = inportb(port_addr + STATUS_PORT);
/*
// Then interpret its bits
// The 1 -   is for the bits that are inverted from port pin to register
*/
  stat->timeout 	= TEST_BIT( status, STATUS_TIMEOUT );
  stat->error   	= 1 - TEST_BIT( status, STATUS_nERROR );
  stat->select  	= TEST_BIT( status, STATUS_SELECT );
  stat->paper_end	= TEST_BIT( status, STATUS_PAPEREND );
  stat->ack     	= 1 - TEST_BIT( status, STATUS_nACK );
  stat->busy    	= 1 - TEST_BIT( status, STATUS_nBUSY );

  /* We clear the timeout bit by writing 1 to bit 0 */
  /* SMC specific , might need to change for others */
  if ( stat->timeout )
  {
    status = status | STATUS_TIMEOUT; // clears by writing 1 on SMC
    outportb(port_addr+STATUS_PORT, status);
  }
}


Transferring strings of data or address bytes

String transfers are only slightly more complicated than single-byte transfers. Of course, you can write bytes in a loop to do this. Here is an example in assembly for writing 30 16-bit values to the EPP data port (for a total of 60 bytes transferred):

{
  static char buf[ 60 ]; /* Must be static so that it is appropriately 
                            placed in the data register and not on the stack.  
                            Experienced PC assembly programmers will
                            know how to circumvent this */
  asm
  {
      mov si, offset buf
      mov dx, base_addr   /* Base address of parallel port    */
      add dx, 4           /* 4 is the first EPP port's offset */
      cld
      mov cx, 30          /* 30 transfers                     */
      rep outsw           /* outsw transfers 16-bit values    */
  }
}

Resetting the EPP port

Resetting the EPP port consists of asserting the nINIT line then deasserting it. This is accomplished by the following code:

#define CONTROL_PORT     2
#define CONTROL_nINIT    2
#define SET_BIT( x, b )    ((x) |= (1 << (b)))
#define CLEAR_BIT( x, b )    ((x) &= ~(1 << (b)))

void  reset( void )
{
  char control;

  control = inportb( base_addr + CONTROL_PORT );

  CLEAR_BIT( control, CONTROL_nINIT );
  outportb( base_addr + CONTROL_PORT, control );

  SET_BIT( control, CONTROL_nINIT );
  outportb( base_addr + CONTROL_PORT, control );
}


Parallel Port links

Jan Axelson's PC Parallel Port page
Lexmark FTP site for 1284
SMC Drivers
Farpoint IEEE 1284 info
SMC - Standard Microsystems Corporation
Compaq Online - Search
FarPoint Communications - "The IEEE 1284 Experts"


Originally prepared by Paul Rademacher based on work done for the Wide-Area Tracking project.

Currently maintained by Pawan Kumar.

Last updated: 03/14/97

Interested: