Interfacing a Phillips PCF8574 I/O Expander with the 68HC11

copyright, Peter H. Anderson, Dept of Electrical Engineering
Morgan State University, Baltimore, MD 21239, April 25, '97

Introduction.

This discussion shows how to interface a Philips PCF8574 with a Motorola 68HC11 processor. Two examples are provided. 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; SDA_hi, SDA_lo, SCL_hi, SCL_lo, START, STOP, out_byte, in_byte and nack may be used with any I2C device.

The Philips PCF9574 may be obtianed form Newark Electronics (http://www.newark.com). 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".

I2C Protocol Routines.

SCL_lo and SCL_hi are accomplished by setting bit 0 of the DDRC register to a logic one (output) and then setting bit 0 of PORTC to either a logic 0 or 1, respectively. Thus, the two clock states are a low impedance to ground and a low impedance to +5.

In SDA_lo, bit 1 of DDRC is set to a logic one (ouput) and bit 1 of PORTC to a logic zero.

SDA_hi is a bit different. Bit 1 of DDRC is set to a logic zero, thus setting the associated bit on PORTC to an input, resulting in a high impedance.

Thus, the two states associated with SDA are a low impedance to ground (logic 0) and an open circuit (logic 1).

START is implemented by bring SDA for a logic one to logic zero while SCL is high. 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.

Subroutine out_byte shifts out variable o_byte beginning with the most significant bit. SCL is 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.

Subroutine nack is implemented by simply bringing SDA high and then bringing SCL high and then low. This allows the 8574 to acknowledge the byte.

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. The result of the read is located in i_byte.

8574 Routines.

Subroutine out_patt outputs content of variable patt to the 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 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 assummed 000).

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, "dirs" has been set to 0x80 indicating that the most significant pin of the 8574 is an input.

Subroutine 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.

The 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 in variable i_patt. Note that only the bits which have been defined as inputs have meaning.

Program 8574_1.ASM

The input of the 8574 is read using subroutine in_patt. The state of the most significant bit is then tested using the sign flag. If "plus", indicating the most significant bit is a logic zero, the LED is flashed once. Otherwise, the LED is turned off.

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 fllowed by a delay.

* Program 8574_1.ASM
*
* Illustrates interface with PCF8574 8-bit I/O Expander using I2C
* protocol.
*
* Pulses LED on least significant bit of 8574 when switch on most
* significant bit is in one state.  
*
* PCF8574 controlled via PORTC, bit 0 - SCL, bit 1 - SDA
*
* Copyright, Peter H. Anderson, MSU, 23 April 97
*

PSCT	EQU $C000
DSCT	EQU $D000
IDSCT	EQU $D300

DDRC	EQU $07
PORTB	EQU $04
PORTC	EQU $03


	ORG PSCT

	LDS #$0040
	LDY #$1000

	ldaa #%10000000
	staa dirs		* ms bit is an input

	ldaa #$ff		* set all outputs to an intial value of 1
	staa o_patt
	bsr out_byte
	
top
	bsr in_patt		* get the inputs
	tst i_patt
	bpl flash		* if most sign input is zero
	bmi no_flash

flash
	ldaa #$00		* turn on the LED
	staa o_patt
	bsr out_patt
	bsr delay
	ldaa #$01		* turn it off
	staa o_patt
	bsr out_patt
	bsr delay
	bra top

no_flash
	ldaa #$01		* turn off the LED
	staa o_patt
	bsr out_patt
	bsr delay
	bra top 	

delay
	pshx			* delay for about 250 ms
	ldx #$ffff
delay_L1
	dex
	bne delay_L1
	pulx
	rts


in_patt
	bsr START
	ldaa #$41		* address - read
	staa o_byte
	bsr out_byte
	bsr nack
	bsr in_byte		* fetch the input
	bsr nack
	bsr STOP
	ldaa i_byte
	staa i_patt		* return result in i_patt
	rts
			
out_patt
	bsr START	
	ldaa #$40		* address - write 
	staa o_byte	            	
	bsr out_byte
	bsr nack
	ldaa o_patt		* pattern to be output
	oraa dirs		* be sure pins designated as inputs are at
logic one
	staa o_byte
	bsr out_byte
	bsr nack
	bsr STOP
	rts

in_byte
	bsr SDA_hi		* be sure SDA is an input
	ldaa #8
	staa n
	clr i_byte
L1_in
	bsr SCL_hi
	ldaa PORTC,Y
	asra			* align bit 1 in bit 0 position
	anda #$01		* bit only
	asla i_byte		* shift i_byte and or the bit
	oraa i_byte
	staa i_byte
	bsr SCL_lo
	dec n
	bne L1_in
	rts

out_byte
	ldaa #8
	staa n
L1_out
	tst o_byte
	bpl out_0		* SDA set high or low based on ms bit
	bmi out_1
L2_out
	bsr SCL_hi
	bsr SCL_lo

	asl o_byte
	dec n
	bne L1_out
	rts

out_0
	bsr SDA_lo
	bra L2_out
out_1
	bsr SDA_hi
	bra L2_out	

**

nack
	bsr SDA_hi		* bring SDA to logic one and clock
	bsr SCL_hi
	bsr SCL_lo
	rts
	
START
	bsr SCL_lo
	bsr SDA_hi
	bsr SCL_hi
	bsr SDA_lo		* SDA 1 to 0 when SCL is high
	bsr SCL_lo
	rts

STOP
	bsr SCL_lo
	bsr SDA_lo
	bsr SCL_hi
	bsr SDA_hi			* SDA 0 to 1 when SCL is high
	bsr SCL_lo
	rts

SCL_hi
	bset DDRC,Y %00000001	* make bit 0 on PORTC an output
	bset PORTC,Y %00000001
	rts

SCL_lo
	bset DDRC,Y %00000001
	bclr PORTC,Y %00000001
	rts

SDA_hi
	bclr DDRC,Y %00000010	* make bit 1 an input
	rts

SDA_lo
	bclr PORTC,Y %00000010	
	bset DDRC,Y %00000010
	rts


	ORG DSCT

dirs	rmb 1
i_patt	rmb 1
o_patt	rmb 1
o_byte	rmb 1
i_byte	rmb 1
n		rmb 1

Program 8574_2.ASM

Note that the low four bits are defined as outputs, driving a stepper using a ULN2803 driver of similar. The high four bits 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 subroutine 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 either CW or CCW. In CW, pointer X is walked down through the array of stepping motor patterns and set back to PATTS when it gets beyond PATTS+7. In CCW, pointer X is walked up and set back to PATTS+7 when it is less than PATTS.

The pattern pointed to by X is output using subroutine out_patt. This is followed by a delay.

In the delay function, the content of variable "speed" is multipied by 0xff to determine the intial state of the counter which is then decremented to zero.

* Program 8574_2.ASM
*
* Illustrates how an 8574 might be used to control a stepping motor.
* Four coils of stepper controlled by lowest four bits on 8574.  
*
* Switch on most significant bit (P7) of 8574 controls direction of motor.
* Speed is controlled by switches on P6, P5 and P4 of 8574
*
* 8574 is controlled by PORTC;  bit 0 - SCL, bit 1 - SDA
* 			
* coyright Peter H. Anderson, MSU, April 23, '97
*

PSCT	EQU $C000
DSCT	EQU $D000
IDSCT	EQU $D300

DDRC	EQU $07
PORTB	EQU $04
PORTC	EQU $03

	ORG PSCT

	LDS #$0040
	LDY #$1000

	ldaa #%11110000
	staa dirs		* ms nibble is an input

	ldaa #ff		* intialize all outputs to logic 1
	staa o_patt
	bsr out_byte

	ldx #PATTS		* intialize pointer
	
top
	bsr in_patt		* fetch switch to determine direction and
speed
	ldaa i_patt
	asra
	asra
	asra
	asra
	anda #$07
	staa speed
	tst i_patt		* direction bit
	bpl CW		* if direction bit is zero
	bmi CCW


CW	inx			* advance the pointer
	cpx #PATTS+8	* if too far, set back to beginning
	bne CW_around
	ldx #PATTS
CW_around
	ldaa 00,x
	staa o_patt		* get the pattern and output it
	jsr out_patt
	jsr delay
    	bra top

CCW	dcx			* dec the pointer
	cpx #PATTS-1	* it too far, set at end
	bne CCW_around
	ldx #PATTS+7
CCW_around
	ldaa 00,x
	staa o_patt
	jsr out_patt
	jsr delay
    	bra top

***

delay
	pshx	* delay for period
	psha
	pshb

	ldaa speed		* multiply speed by 0xff
	ldab $ff
	mul
	xgdx			* result is now in X
delay_L1
	dcx
	bne delay_L1

	pulb
	pula
	pulx
	rts

***

in_patt
	jsr START		* reads 8574.  returns result in i_patt
	ldaa #$41
	staa o_byte
	jbsr out_byte
	jsr nack
	jsr in_byte
	jsr nack
	jsr STOP
	ldaa i_byte
	staa i_patt
	rts
			
out_patt
	bsr START	
	ldaa #$40	 
	staa o_byte	            	
	jsr out_byte
	jsr nack
	ldaa o_patt
	oraa dirs
	staa o_byte
	jsr out_byte
	jsr nack
	bsr STOP
	rts

in_byte
	SDA_hi		* be sure SDA is an input
	ldaa #8
	staa n
	clr i_byte
L1_in
	SCL_hi
	ldaa PORTC,Y
	asra			* align bit 1 in bit 0 position
	anda #$01		* bit only
	alsa i_byte		* shift i_byte and or the bit
	oraa i_byte
	staa i_byte
	SCL_lo
	dec n
	bne L1_in
	rts

out_byte
	ldaa #8
	staa n
L1_out
	tst o_byte
	bpl out_0		* SDA set high or low based on ms bit
	bmi out_1

L2_out
	jsr SCL_hi
	jsr SCL_lo

	asl o_byte
	dec n
	bne L1_out
	rts

out_0
	jsr SDA_lo
	bra L2_out
out_1
	jsr SDA_hi
	bra L2_out	

**

nack
	jsr SDA_hi		* SDA to high impedance and clock
	jsr SCL_hi
	jsr SCL_lo
	rts
	
START
	jsr SCL_lo
	jsr SDA_hi
	jsr SCL_hi
	jsr SDA_lo		* SDA 1 to 0 when SCL is high
	jsr SCL_lo
	rts

STOP
	jsr SCL_lo
	jsr SDA_lo		* SDA 0 to 1 when SCL is high
	jsr SCL_hi
	jsr SDA_hi		* SDA 0 to 1 when SCL is high
	jsr SCL_lo
	rts

SCL_hi
	bset DDRC,Y %00000001	* make bit 0 on PORTC an output
	bset PORTC,Y %00000001
	rts

SCL_lo
	bset DDRC,Y %00000001
	bclr PORTC,Y %00000001
	rts

SDA_hi
	bclr DDRC,Y %00000010	* make bit 1 an input
	rts

SDA_lo
	bclr PORTC,Y %00000010	
	bset DDRC,Y %00000010
	rts


	ORG DSCT

dirs		rmb 1
i_patt	rmb 1
o_patt	rmb 1
o_byte	rmb 1
i_byte	rmb 1
n		rmb 1
speed		rmb 1

	ORG IDSCT

PATTS	FCB $01, $03, $02, $06
	FCB $04, $0c, $08, $09