Interfacing with a Philips PCF8574 8-bit I/O


Overview.

The PCF8574 uses the Philips I2C 2-wire protocol and provides eight I/O channels per device. Eight such devices may be cascaded by setting the A2, A1 and A0 leads to uniquely identify each device. In addition, up to eight PCF8574A devices may be controlled on the same 2-wire interface providing for 128 I/O channels.

Program 8574_1.C illustrates how to write to and read from the device. Note that the outputs are open drain. In the zero state they are capable is sinking up to 10 mA.

The device also includes an active low interrupt feature which goes low when an input changes. This might be connected to input RB.0 so as to interrupt the PIC when any of the inputs change state. The interrupt is cleared when the data is read. The interrupt is an open drain and thus may be wire ored with other devices. This feature might be used in applications where the monitoring of many scan points is required or in interfacing with a keypad. This feature is not illustrated in the 8574_1.C program.

The 8574 is also discussed elsewhere in the context of the BASIC Stamp 2 and the PIC using assembly language.


// 8574_1.C
//
// Illustrates control of 8574.  Flashes LED on P0 of 8574 if switch at
// P7 of 8574 is at zero.
//
//    PIC16F84				PCF8574
//
// RB1 (term 7) ------------------- SCL (term 14) ----- To Other
// RB2 (term 8) ------------------- 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.
//
// Note that the device code for the PCF8574 is 0x40.  For the PCF8574A
// it is 0x70
//
// copyright, Peter H. Anderson, Baltimore, MD, May, '99

#case
#include <16f84.h>
#include <string.h>
#include <defs_f84.h>

// routines used for 8574
int in_patt(int dev_code, int adr);
void out_patt(int dev_code, int adr, int dirs, int patt);

// common i2c routines
byte i2c_in_byte(void);
void i2c_out_byte(byte o_byte);
void i2c_nack(void);
void i2c_ack(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_high_sda(void);
void i2c_low_sda(void);
void i2c_high_scl(void);
void i2c_low_scl(void);

// delay outines
void delay_ms(long t);
void delay_10us(int t);

#define SDA_PIN rb2
#define SDA_DIR trisb2

#define SCL_PIN rb1
#define SCL_DIR trisb1

void main(void)
{
   int y;
   out_patt(0x40, 0x00, 0x80, 0x7f);
   // dev_code = 0x40, address = 0, dirs = 0x80, patt = 0x7f

   while(1)
   {
      y = in_patt(0x41, 0x00); // read inputs

      if (y&0x80)  // if switch at in7==1 then turn off LED
      {
         out_patt(0x40, 0x00, 0x80, 0x7f);
      }
      else // flash the LED on time
      {
         out_patt(0x40, 0x00, 0x80, 0x7e);  // turn the LED on
         delay_ms(500);
         out_patt(0x40, 0x00, 0x80, 0x7f);  // turn it on
         delay_ms(500);
      }
   }
}

int in_patt(int dev_code, int adr)
{
   int y;
   i2c_start();
   i2c_out_byte(dev_code | (adr<<1));
   i2c_nack();
   y=i2c_in_byte();
   
   i2c_stop();
   return(y);
}

void out_patt(int dev_code, int adr, int dirs, int patt)
{
   i2c_start();
   i2c_out_byte(dev_code | (adr << 1));
   i2c_nack();
   i2c_out_byte(dirs | patt);
   i2c_nack();
   i2c_stop();
}


byte i2c_in_byte(void)
{
   byte i_byte, n;
   i2c_high_sda();
   for (n=0; n<8; n++)
   {
      i2c_high_scl();

      if (SDA_PIN)
      {
         i_byte = (i_byte << 1) | 0x01; // msbit first
      }
      else
      {
         i_byte = i_byte << 1;
      }
      i2c_low_scl();
   }
   return(i_byte);
}

void i2c_out_byte(byte o_byte)
{
   byte n;
   for(n=0; n<8; n++)
   {
      if(o_byte&0x80)
      {
         i2c_high_sda();
      }
      else
      {
         i2c_low_sda();
      }
      i2c_high_scl();
      i2c_low_scl();
      o_byte = o_byte << 1;
   }
   i2c_high_sda();
}

void i2c_nack(void)
{
   i2c_high_sda();	// data at one
   i2c_high_scl();	// clock pulse
   i2c_low_scl();
}

void i2c_ack(void)
{
   i2c_low_sda();	// bring data low and clock
   i2c_high_scl();
   i2c_low_scl();
   i2c_high_sda();
}

void i2c_start(void)
{
   i2c_low_scl();
   i2c_high_sda();
   i2c_high_scl();	// bring SDA low while SCL is high
   i2c_low_sda();
   i2c_low_scl();
}

void i2c_stop(void)
{
   i2c_low_scl();
   i2c_low_sda();
   i2c_high_scl();
   i2c_high_sda();  // bring SDA high while SCL is high
   // idle is SDA high and SCL high
}

void i2c_high_sda(void)
{
   // bring SDA to high impedance
   SDA_DIR = 1;
   delay_10us(5);
}

void i2c_low_sda(void)
{
   SDA_PIN = 0;
   SDA_DIR = 0;  // output a hard logic zero
   delay_10us(5);
}

void i2c_high_scl(void)
{
   SCL_DIR = 1;   // high impedance
   delay_10us(5);
}

void i2c_low_scl(void)
{
   SCL_PIN = 0;
   SCL_DIR = 0;
   delay_10us(5);
}

// delay routines

void delay_10us(int t)
{
#asm
      BCF STATUS, RP0
DELAY_10US_1:
      CLRWDT
      NOP
      NOP
      NOP
      NOP
      NOP
      NOP
      DECFSZ t, F
      GOTO DELAY_10US_1
#endasm
}

void delay_ms(long t)	// delays t millisecs
{
   do
   {
     delay_10us(100);
   } while(--t);
}