Stretching a Basic Stamp 2 using PCF8574 I/O Expanders

(I2C 2-Wire Protocol)

copyright, H. Paul Roach, Dept of Electrical Engineering,
Morgan State University, Baltimore, MD 21239, May 5, '97

Introduction.

This discussion presents a very simple technique to "stretch" the Basic Stamp's I/O capability to an additional 64 or even 128 bidirectional IO bits using Philips PCF8574 8-bit I/O Expanders. The technique uses only two of the Basic Stamp'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.

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.

Another discussion shows how to use a 8574 devices to interface a Basic Stamp with a printer. Overview.

Writing to an 8574 is simply;

     device = $00
     directions = $80 
     patt = $05
     ...
     gosub out_patt

where "device" is the address (0-7) of the device on the 2-wire bus, "directions" specifies which bits are defined as being inputs and patt is the byte to appear on the output of the specified 8574.

Reading the inputs from the 8574 is simply;

     
     device = $00
     ...
     gosub in_patt

The result is then in variable "i_byte".

Resources.

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

Note that the low level I2C routines; start, sstop, out_byte, in_byte and nack may be used with any I2C device.

I2C Protocol Routines. (See Program 8574_1.BS2).

Note that in the following routines, the SDA lead is brought low by making the SDA_OUT a logic zero and making SDA_DIR an output. The SDA lead is made high by making SDA_DIR an input. While defined as an input, the lead is in a high impedance state and the external 10K pullup resistor provides a relatively high impedance to +5VDC.

Thus, the two states associated with SDA are a low impedance to ground (logic 0) and a high impedance to +5VDC (logic 1).

The high and low states on the SCL terminal is simply a standard logic 0 and logic one.

Subroutine start is implemented by bringing SDA from a logic one to logic zero while SCL is high. Function sstop 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. 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.

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 by bringing the SDA lead low.

In subroutine 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 in variable i_byte.

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.

Subroutine 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 "directions" has been defined to specify which bits are inputs. In the LED example shown in program 8574_1.BS2, "directions" 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 "directions" and ouputs the result so as to assure the designated input bits are set to a logic one.

In subroutine 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 in variable "i_patt". 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.BS2 (LED Flash).

The input of the 8574 is read using subroutine 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 pause and then to a logic one turning the LED off. This is then followed by a pause.

' Program 8574_1.BS2

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

' Basic Stamp 2                         PCF8574

' Pin5 (term 10) ------------------- SCL (term 14) ----- To Other
' Pin4 (term 9)   ------------------ 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, H.Paul Roach, MSU, May 3, '97

' Program 8574_1.BS2

     device var byte          ' device 0-7
        
     o_byte var byte          ' byte to send to device
     i_byte var byte          ' byte from device

     o_patt var byte          ' byte to appear on output of 8574
     i_patt var byte          ' byte read from input of 8574

     directions var byte ' defines which bits on 8574 are outputs
        
     n var byte          ' index
     b var bit           ' bit 
          

     SDA_PIN con 4
     SCL_PIN con 5
     SDA_OUT var out4
     SCL_OUT var out5
     SDA_IN  var in4
     SDA_DIR var dir4

     OUT con 1
     IN con 0

     dirs=$f0ff
     
     directions=$80      ' p7 of 8574 defined to be an input
     device = 0          ' setting of A2 A1 A0

main
     o_patt = $ff        ' initialize all outputs to logic one
     gosub out_patt

top
     low SDA_PIN
     gosub in_patt  
     debug hex i_patt
     if ((i_patt & $80) >> 7 ) = 0 THEN flash     
          ' if most sign bit is a zero then flash

     o_patt = $01        ' otherwise, turn the LED off
     gosub out_patt
     goto top

flash                    ' wink the LED on and off one time
     o_patt = $00
     gosub out_patt
     pause 300
     o_patt = $01
     gosub out_patt
     pause 300
     goto top

'*****
in_patt   ' fetch input on addressed device
     gosub start
     o_byte = $40 | (device <<1) | $1
     gosub out_byte
     gosub nack
     gosub in_byte
     gosub nack
     gosub sstop
     i_patt = i_byte
     return                             

out_patt  ' output specified patt to addresses device
     gosub start
     o_byte = $40 | (device <<1)
     gosub out_byte
     gosub nack    
     o_byte = o_patt | directions  ' bits defined as inputs set 
                                   ' to logic one
     gosub out_byte
     gosub nack
     gosub sstop
     return       

in_byte   ' fetches 8 bits, most sign bit first
     SDA_DIR=IN      'input

     i_byte=0        
     for n=0 to 7
        pause 200
        high SCL_PIN     ' clock high
        pause 200
        i_byte=(i_byte << 1) | SDA_IN   'read bit and or with prev
        debug dec SDA_IN
        low SCL_PIN
     next
 
     SDA_DIR=OUT     'output
     return

out_byte  ' output o_byte     'beginning with most sig bit
        
     low SDA_PIN   
        
     for n=0 to 7 
        b= (o_byte >> 7) & 1       
        if (b=1) then out_one   
        SDA_DIR=OUT 
        debug "0"
        _clk          
        high SCL_PIN
        pause 100
        low SCL_PIN
        pause 100
        o_byte=o_byte << 1
     next
     return
   
out_one
     SDA_DIR=IN
     debug "1"
     goto _clk
 
nack                
     SDA_DIR=OUT    ' bring SDA high and clock
     high SCL_PIN
     low SCL_PIN
     return  

start
     low SCL_PIN
     SDA_DIR=IN     ' SDA at logic one
     high SCL_PIN
     low SDA_PIN
     SDA_DIR =OUT    'bring SDA low while clock is high
     low SCL_PIN
     debug "START"
     debug $0d
     return

sstop
     low SCL_PIN
     SDA_DIR=OUT   
     high SCL_PIN
     SDA_DIR=IN    'bring SDA high while clock is high
     debug "STOP"
     debug $0d
     return

Program 8574_2.BS2 - 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 8574 terminal 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 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 subroutine out_patt. This is followed by a pause. The pause is adjusted using the variable "speed".

In the following listing, the implementations of subroutines in_patt, out_patt, out_byte, in_byte, nack, start and sstop are not shown. They are the same as in Program 8574_1.BS2.

' 8574_2.BS2
'
' Illustrates how to interface with a single PCF8574 I/O Expander.
'
' Turns stepping motor at 8574 outputs at 8574 outputs P0 - P3 in
' direction determined by switch at P7.  Speed is controlled by
' switches at P4 - P6.
'
' Basic Stamp 2                         PCF8574
'
' PIN5 (term 10) ------------------- SCL (term 14) ----- To Other
' PIN4 (term 9)   ------------------ 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 pull-up resistors to +5VDC are required on both signal leads.
'
' copyright H. Paul Roach, MSU, May 4, '97
'

     device var byte          ' device 0-7
        
     o_byte var byte          ' byte to send to device
     i_byte var byte          ' byte from device

     o_patt var byte          ' byte to appear on output of 8574
     i_patt var byte          ' byte read from input of 8574

     directions var byte ' defines which bits on 8574 are outputs
        
     n var byte          ' index
     b var bit           ' bit 

     index var byte
     speed var byte        

     SDA_PIN con 4
     SCL_PIN con 5
     SDA_OUT var out4
     SCL_OUT var out5
     SDA_IN  var in4
     SDA_DIR var dir4

     OUT con 1
     IN con 0

     dirs=$f0ff
     
     directions=$80      ' p7 of 8574 defined to be an input
     device = 0          ' setting of A2 A1 A0

main
     o_patt = $ff        ' initialize all outputs to logic one
     gosub out_patt
     index=0
top
     gosub in_patt  
     debug hex i_patt
     speed = (i_patt & $70) >> 4   ' isolate speed
     if ((i_patt & $80) >> 7 ) = 0 then CW   
          ' if most sign bit is a zero then turn one way
          ' otherwise, turn the other way
CCW  
     index=index+1  ' go down through array
     if index < 8 then L1
     index=0        ' reset if index at 8
     goto L1

CW   
     index=index-1  ' otherwise go in other direction
     if index >= 0 then L1
     index=7        ' reset if index < 0
     goto L1

L1   
     lookup index, [$01, $03, $02, $06, $04, $0c, $08, $09], o_patt
     gosub out_patt ' output the pattern
     pause (speed * 10 + 10)  ' and wait for a period of time
     goto top