Introduction.
A common cause for complaint when using the PC Parallel Port is that there are not enough outputs or inputs. In the PC Parallel Port Manuals (Vol 1 and Vol 2), I have presented many techniques for expanding the IO capability including a variety of multiplexing and decoding schemes, external latches and using shift registers.
This discussion presents another very simple technique to "stretch" the Parallel Port to 64 or even 128 bidirectional IO bits using Philips PCF8574 8-bit I/O Expanders. The technique uses only two of the parallel port's I/O bits. Devices may be added as is necessary for the task by simply daisy chaining the two signal leads (SDA and SCL) from the existing devices on the two wire bus to the new devices. With the addition of each device, 8 bidirectional I/O bits are added.
Writing to an 8574 is simply;
out_patt(device, dirs, patt);
where "device" is the address (0-7) of the device on the 2-wire bus, "dirs" specifies which bits are defined as being inputs and patt is the byte to appear on the output of the specified 8574.
Reading is simply;
i_patt = in_patt(device);
where "device" is the address of the device on the bus.
In fact, one might simply interface peripherals with the 8574 devices and then consider these functions as replacing the familiar outportb() and inportb() in controlling peripherals. (Of course, the outportb() and inportb() functions are still used in manipulating the SDA and SCL leads so as to accomplish these higher order functions.
Although this discussion was written for the PC Parallel Port, the same technique may be used with any processor; e.g., Basic Stamps, 68HC11, 8051 and PIC. Other abbreviated discussions are available for the Basic Stamp 2 and 68HC11.
The Philips PCF8574 may be obtained from Newark Electronics (http://www.newark.com) at a cost of less than $4.00 in single unit quantities.. A data sheet is available at http://www.semiconductors.philips.com. As of this writing, their homepage is poorly organized. I found it by doing a search on "PCF8574".
[Note that Philips has coded two different versions; PCF8574 and PCF8574A. They are functionally the same except one responds to group address 0100 and the other to 0111. This is discussed below.]
Two examples are provided in this discussion. One flashes an LED on P0 of the 8574 when a switch on P7 is depressed. The second turns a stepper on P0 - P3 in a direction determined by a switch on P7 and at a speed determined by three switches on P4 - P6.
Note that the low level I2C routines; high_SDA, low_SDA, high_SCL, lo_SCL, start(), stop(), out_byte(), in_byte() and nack() may be used with any I2C device.
I2C Protocol Routines. (See Program 8574_1.C).
Functions low_SDA() and high_SDA() are implemented by setting bit 0 of the Control Port (/STROBE) to either a logic zero or to a logic one. Recall that the output bits on the Control port are open collector. Thus a logic zero is a low impedance to ground and a logic one is an open circuit. An external 10K pullup resistor provides the relatively high impedance to +5VDC logic one state required by the I2C devices.
Thus, the two states associated with SDA are a low impedance to ground (logic 0) and a high impedance to +5VDC (logic 1).
[Recall that bit 0 of the Control Port is inverted by the hardware. Thus; outportb(CONTROL, 0x01^0x01) causes a zero to be written, but results in an output logic one. Similarly, inportb(CONTROL)^0x01 compensates fro the hardware inversion.]
Functions low_SCL() and high_SCL() simply bring bit 0 on the Data port to either a logic 0 or logic one.
Function start() is implemented by bringing SDA from a logic one to logic zero while SCL is high. Function stop() is similar; SDA is brought high while SCL is high. Note that at all other times, SDA is changed only when SCL is low.
Function out_byte() shifts out variable o_byte beginning with the most significant bit. For each bit, SCL is initially low. SDA is set to the state associated with the most significant bit of o_byte. SCL is then brought high and then low. Variable o_byte is then shifted left such that bit 6 is now the most significant bit and the process is repeated for all eight bits.
Function nack() is implemented by simply bringing SDA high and then bringing SCL high and then low. This allows the 8574 to acknowledge the byte by bringing the SDA lead low.
In function in_byte(), SDA is configured as an input. SCL is brought high and the SDA input is read. SCL is then brought low. Each of the eight bits are read in turn and the result is returned to the calling function.
8574 Routines.
Note that all of the above functions may be used to control any I2C device. This section discusses functions which are unique to the 8574.
Function out_patt() outputs the content of variable patt to the specified 8574. This is accomplished by first exerting the "start" sequence, followed by the address byte with the R/W bit set to logic zero to indicate the operation is a write. After a nack, the data to be written is then sent followed by a nack and the "stop" sequence.
START 0100 AAA 0 N DDDD DDDD N STOP 210
where 0100 is the manufacturers defined group address for the 8574. A2, A1 and A0 are the states the user has defined in wiring the A2, A1 and A0 leads. (In these examples, I assumed 000).
[Note that the A2, A1, A0 terminals permit the assignment of up to eight 8574 devices on the same bus. In addition, Philips offers the PCF8574A. The only difference is that the manufacturers defined group address is 0111. Thus, by using both 8574 and 8574A devices, 16 devices can be accommodated on the same 2-wire bus providing up 128 bidirectional I/O bits.]
Note that N indicates a nack and DDDD DDDD indicates the data to appear on the output of the 8574.
Note that outputs of the 8574 are to be in a logic one state if they are used as inputs. Thus, a variable "dirs" has been defined to specify which bits are inputs. In the LED example shown in program 8574_1.C, "dirs" has been set to 0x80 indicating that the most significant pin of the 8574 is an input.
Function out_patt() then ors the "patt" with "dirs" and ouputs the result so as to assure the designated input bits are set to a logic one.
In function in_patt(), the specified 8574 is read by exerting the "start" sequence, followed by the address byte with the R/W bit set to logic one to indicate a read. A nack is then sent, followed by an 8-bit read.
START 0100 AAA 1 N RRRR RRRR N STOP 210
the result is returned to the calling function. Note that only the bits which have been defined as inputs have meaning. The other bits are the current state of the outputs.
Details of Program 8574_1.C (LED Flash).
The input of the 8574 is read using function in_patt(). The state of the most significant bit is then tested. If it is a logic zero, the LED is flashed once. Otherwise, the LED is turned off. The entire process loops indefinitely.
In flashing the LED, 8574 output P0 is brought low turning on the LED for a time determined by the delay and then to a logic one turning the LED off. This is then followed by a delay.
/* * 8574_1.C * * Illustrates how to interface with a single PCF8574 I/O Expander. * * Flashes LED on 8574 output P0 if switch at P7 is at logic zero. * * Parallel Port PCF8574 * * Data0 (term 2) ------------------- SCL (term 14) ----- To Other * /STROBE (term 1)------------------ SDA (term 15) ----- I2C Devices * * Note that the slave address is determined by A2 (term 3), A1 * (term2) and A0 (term 1) on the 8574. The above SCL and SDA leads * may be multipled to eight devices, each strapped for a unique A2 * A1 A0 setting. * * 10K pullup resistors to +5VDC are required on both signal leads. * * copyright, Peter H. Anderson, MSU, April 26, '97 */ #include <stdio.h> #define DATA 0x0378 #define STATUS DATA+1 #define CONTROL DATA+2 typedef unsigned char byte; byte in_patt(byte device); void out_patt(byte device, byte dirs, byte patt); void out_byte(byte o_byte); byte in_byte(void); byte nack(void); void start(void); void stop(void); void low_SDA(void); void high_SDA(void); void low_SCL(void); void high_SCL(void); void main(void) { byte i_patt, device; byte dirs = 0x80; /* define most significant bit as an input */ device = 0x00; /* determined by strapping of A2, A1, A0 */ out_patt(device, dirs, 0xff); /* initialize all outputs to logic one */ while(1) { i_patt=in_patt(device); /* fetch from 8574 */ if ((i_patt && 0x80)==0) /* if switch at zero, flash LED */ { out_patt(device, dirs, 0x00); /* LED on */ delay(250); out_patt(device, dirs, 0x01); /* LED off */ delay(250); } else { out_patt(device, dirs, 0x01); /* turn LED off */ } } } byte in_patt(byte device) /* fetches byte from 8574 at specified addresss */ { byte o_byte, i_patt; start(); o_byte=0x40 | (device << 1) | 0x01; out_byte(o_byte); nack(); i_patt=in_byte(); stop(); return(i_patt); } void out_patt(byte device, byte dirs, byte patt) /* outputs specified pattern on specified 8574. Note that ** inputs specified by "dirs" are maintained at a logic one */ { byte o_byte; start(); o_byte=0x40 | (device << 1); out_byte(o_byte); nack(); o_byte = patt | dirs; out_byte(o_byte); nack(); stop(); } void out_byte(byte o_byte) /* shift out byte, beginning with most significant bit */ { int n; for(n=7; n>=0; n--) { /* note SCL is low during transitions on SDA */ if (((o_byte >>n) & 0x01) == 0) { low_SDA(); } else { high_SDA(); } high_SCL(); low_SCL(); } } byte in_byte(void) /* fetch byte, most significant byte first */ { byte i_byte=0x00; int n; high_SDA(); for (n=0; n<8; n++) { high_SCL(); i_byte=(i_byte << 1) | ((inportb(CONTROL)^0x01)&0x01); /* note inversion on least significant bit of control port */ low_SCL(); } return(i_byte); } byte nack(void) { byte ack_bit; high_SDA(); high_SCL(); if (((inportb(CONTROL)^0x01)&0x01)==0) { ack_bit=0; } else { ack_bit=1; } low_SCL(); return(ack_bit); } void start(void) /* bring SDA high to low while SCL is high */ { high_SDA(); high_SCL(); low_SDA(); /* bring SDA low while clock is high */ low_SCL(); } void stop(void) /* bring SDA low to high while SCL is high */ { low_SDA(); high_SCL(); high_SDA(); /* bring SDA high while clock is high */ low_SCL(); } void low_SDA(void) { outportb(CONTROL, 0x00^0x01); /* inversion required on ls bit of control port */ } void high_SDA(void) /* makes output high impedance */ { outportb(CONTROL, 0x01^0x01); } void low_SCL(void) { outportb(DATA, 0x00); } void high_SCL(void) { outportb(DATA, 0x01); }
Program 8574_2.ASM - Stepping Motor Controller.
In this example, the lower four bits of the 8574 are defined as outputs, driving a stepper using a ULN2803 driver or similar. The higher four bits of the 8574 are inputs. A switch at P7 determines the direction. Switches at P4, P5 and P6 determine the speed of rotation.
The inputs are read using function in_patt(). Bits 4, 5 and 6 are isolated and copied to variable "speed".
The most significant (direction) bit is then tested and program control is transferred to advance the stepper in one direction or the other. In advancing in one direction, variable "index" is incremented so as to walk down through the array of stepping motor patterns. It is set back to 0 when it is beyond the array. In turning the other way, index is decremented so as to walk up though the array of stepping motor patterns and set back to 7 when it goes to less than 0.
The pattern at the index is output using function out_patt(). This is followed by a delay. The delay is adjusted using the variable "speed".
In the following listing, the implementations of functions in_patt(), out_patt(), out_byte(), in_byte(), nack(), start(), stop(), low_SDA(), high_SDA(), low_SCL(), and high_SCL() are not shown. They are the same as in Program 8574_1.C.
/* * 8574_2.C * * Illustrates how to interface with a single PCF8574 I/O Expander. * * Turns steeping motor at 8574 outputs at 8574 outputs P0 - P3 in * direction determined by switch at P7. Speed is controlled by * switches at P4 - P6. * * Parallel Port PCF8574 * * Data0 (term 2) ------------------- SCL (term 14) ----- To Other * /STROBE (term 1)------------------ SDA (term 15) ----- I2C Devices * * Note that the slave address is determined by A2 (term 3), A1 * (term2) and A0 (term 1) on the 8574. The above SCL and SDA leads * may be multipled to eight devices, each strapped for a unique A2 A1 * A0 setting. * * 10K pullup resistors to +5VDC are required on both signal leads. * * copyright Peter H. Anderson, MSU, April 26, '97 */ #include <stdio.h> #define DATA 0x0378 #define STATUS DATA+1 #define CONTROL DATA+2 typedef unsigned char byte; byte in_patt(byte device); void out_patt(byte device, byte dirs, byte patt); void out_byte(byte o_byte); byte in_byte(void); byte nack(void); void start(void); void stop(void); void low_SDA(void); void high_SDA(void); void low_SCL(void); void high_SCL(void); void main(void) { byte i_patt, device, speed; byte dirs = 0xf0; /* define most significant 4 bits as inputs */ byte step_patts[8]= {0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08, 0x09}; int index=0; device = 0x00; /* determined by strapping of A2, A1, A0 */ out_patt(device, dirs, 0xff); /* initialize all outputs to logic one */ while(1) { i_patt=in_patt(device); /* fectch from 8574 */ speed = (i_patt & 0x07) >> 4; /* bits 4, 5 and 6 */ if ((i_patt && 0x80)==0) { ++index; if (index >7) { index=0; } out_patt(device, dirs, step_patts[index]); delay(10*(speed+1)); } else { --index; if (index <0) { index=7; } out_patt(device, dirs, step_patts[index]); delay(10*(speed+1)); } } }