
;****************************************************************************

;                     Software Implemented I2C Drivers

; These routines allow an 80C51 based microcontroller to drive the I2C bus 
; as a single master. The main program at the end demonstrates writing and 
; reading several types of devices: 

;    PCF8570 256 byte static RAM.
;    PCF8574 8-bit I/O expander.
;    SAA1064 4 digit LED display driver.


;             Written by G.Goodhue, Philips Components-Signetics

;****************************************************************************


$Title(I2C Routines for 80C51)
$Date(08/14/90)
$MOD51
$DEBUG


;****************************************************************************
;                               Definitions
;****************************************************************************


; Addresses of several I2C devices as connected on the Signetics I2C 
; Evaluation Board.

I2CRAM     EQU     0AEh           ;Slave address for PCF8570 RAM chip.
I2CIO      EQU     4Eh            ;Slave address for PCF8574 I/O expandor.
I2CLED     EQU     76h            ;Slave address for SAA1064 LED driver.


; Data storage locations

BitCnt     DATA    8h             ;Bit counter for I2C routines.
ByteCnt    DATA    9h             ;Byte counter for I2C routines.
SlvAdr     DATA    0Ah            ;Slave address for I2C routines.

XmtDat     DATA    10h            ;I2C transmit buffer, 8 bytes max.
RcvDat     DATA    18h            ;I2C receive buffer, 8 bytes max.
AltRcv     DATA    20h            ;Alternate I2C receive buffer, 8 bytes max.

Flags      DATA    28h            ;Location for bit flags
NoAck      BIT     Flags.0        ;I2C no acknowledge flag.
BusFault   BIT     Flags.1        ;I2C bus fault flag.
I2CBusy    BIT     Flags.2        ;I2C busy flag.


; I2C connections.

SCLPin     BIT     P0.0           ;I2C serial clock line.
SDAPin     BIT     P0.1           ;I2C serial data line.


;****************************************************************************
;                       Reset and Interrupt Vectors
;****************************************************************************


           ORG     0
           AJMP    Reset


;****************************************************************************
;                               Subroutines
;****************************************************************************


           ORG     30h

; BitDly - insures minimum high and low clock times on I2C bus.
; This routine must be tuned for the actual oscilator frequency used, shown 
; here tuned for a 12MHz clock. Note that the CALL instruction that invokes 
; BitDly already uses 2 machine cycles.

BitDly:    NOP                    ;NOPs to delay 5 microseconds (minus 4
                                  ;  machine cycles for CALL and RET).
           RET


; SCLHigh - sends SCL pin high and waits for any clock stretching peripherals.

SCLHigh:   SETB    SCLPin         ;Set SCL from our end.
           JNB     SCLPin,$       ;Wait for pin to actually go high.
           RET


; SendStop - sends an I2C stop, releasing the bus.

SendStop:  CLR     SDAPin         ;Get SDA ready for stop.
           ACALL   SCLHigh        ;Set clock for stop.
           ACALL   BitDly
           SETB    SDAPin         ;Send I2C stop.
           ACALL   BitDly
           CLR     I2CBusy        ;Clear I2C busy status.
           RET                    ;Bus should now be released.


; SendByte - sends one byte of data to an I2C slave device.
; Enter with:
;   ACC = data byte to be sent.

SendByte:  MOV     BitCnt,#8      ;Set bit count.

SBLoop:    RLC     A              ;Send one data bit.
           MOV     SDAPin,C       ;Put data bit on pin.
           ACALL   SCLHigh        ;Send clock.
           ACALL   BitDly
           CLR     SCLPin
           ACALL   BitDly
           DJNZ    BitCnt,SBloop  ;Repeat until all bits sent.

           SETB    SDAPin         ;Release data line for acknowledge.
           ACALL   SCLHigh        ;Send clock for acknowledge.
           ACALL   BitDly
           JNB     SDAPin,SBEX    ;Check for valid acknowledge bit.
           SETB    NoAck          ;Set status for no acknowledge.
SBEX:      CLR     SCLPin         ;Finish acknowledge bit.
           ACALL   BitDly
           RET


; GoMaster - sends an I2C start and slave address.
; Enter with:
;   SlvAdr = slave address.

GoMaster:  SETB    I2CBusy        ;Indicate that I2C frame is in progress.
           CLR     NoAck          ;Clear error status flags.
           CLR     BusFault
           JNB     SCLPin,Fault   ;Check for bus clear.
           JNB     SDAPin,Fault
           CLR     SDAPin         ;Begin I2C start.
           ACALL   BitDly
           CLR     SCLPin
           ACALL   BitDly         ;Complete I2C start.
           MOV     A,SlvAdr       ;Get slave address.
           ACALL   SendByte       ;Send slave address.
           RET

Fault:     SETB    BusFault       ;Set fault status
           RET                    ;  and exit.


; SendData - sends one or more bytes of data to an I2C slave device.
; Enter with:
;   ByteCnt = count of bytes to be sent.
;   SlvAdr  = slave address.
;   @R0     = data to be sent (the first data byte will be the 
;             subaddress, if the I2C device expects one).

SendData:  ACALL   GoMaster       ;Acquire bus and send slave address.
           JB      NoAck,SDEX     ;Check for slave not responding.

SDLoop:    MOV     A,@R0          ;Get data byte from buffer.
           ACALL   SendByte       ;Send next data byte.
           INC     R0             ;Advance buffer pointer.
           JB      NoAck,SDEX     ;Check for slave not responding.
           DJNZ    ByteCnt,SDLoop ;All bytes sent?

SDEX:      ACALL   SendStop       ;Done, send an I2C stop.
           RET


;RcvByte - receives one byte of data from an I2C slave device.
; Returns:
;   ACC = data byte received.

RcvByte:   MOV     BitCnt,#8      ;Set bit count.

RBLoop:    ACALL   SCLHigh        ;Read one data bit.
           ACALL   BitDly
           MOV     C,SDAPin       ;Get data bit from pin.
           RLC     A              ;Rotate bit into result byte.
           CLR     SCLPin
           ACALL   BitDly
           DJNZ    BitCnt,RBLoop  ;Repeat until all bits received.

           PUSH    ACC            ;Save accumulator
           MOV     A,ByteCnt
           CJNE    A,#1,RBAck     ;Check for last byte of frame.
           SETB    SDAPin         ;Send no acknowledge on last byte.
           SJMP    RBAClk

RBAck:     CLR     SDAPin         ;Send acknowledge bit.
RBAClk:    ACALL   SCLHigh        ;Send acknowledge clock.
           POP     ACC            ;Restore accumulator
           ACALL   BitDly
           CLR     SCLPin
           SETB    SDAPin         ;Clear acknowledge bit.
           ACALL   BitDly
           RET


;RcvData - receives sends one or more bytes of data from an I2C slave device.
; Enter with:
;   ByteCnt = count of bytes to be sent.
;   SlvAdr  = slave address.
; Returns:
;   @R0     = data received.

; Note: to receive with a subaddress, use SendData to set the subaddress
;   first (no provision for repeated start).

RcvData:   INC     SlvAdr         ;Set for READ of slave.
           ACALL   GoMaster       ;Acquire bus and send slave address.
           JB      NoAck,RDEX     ;Check for slave not responding.

RDLoop:    ACALL   RcvByte        ;Recieve next data byte.
           MOV     @R0,A          ;Save data byte in buffer.
           INC     R0             ;Advance buffer pointer.
           DJNZ    ByteCnt,RDLoop ;Repeat untill all bytes received.

RDEX:      ACALL   SendStop       ;Done, send an I2C stop.
           RET


;****************************************************************************
;                               Main Program
;****************************************************************************


Reset:     MOV     SP,#2Fh        ;Set stack to start at 30h.

           MOV     XmtDat,#0      ;Initialize transmit data area.
           MOV     XmtDat+1,#37h
           MOV     XmtDat+2,#0AAh
           MOV     XmtDat+3,#055h
           MOV     XmtDat+4,#33h
           MOV     XmtDat+5,#0CCh
           MOV     XmtDat+6,#0FFh
           MOV     XmtDat+7,#0BBh


TestLoop:  MOV     SlvAdr,#I2CIO  ;Write data to PCF8574 I/O expandor.
           MOV     R0,#XmtDat+2   ;Start of data.
           MOV     ByteCnt,#1     ;Send one data byte.
           ACALL   SendData

           MOV     SlvAdr,#I2CIO  ;Read back data from PCF8574 I/O expandor.
           MOV     R0,#AltRcv     ;Start of data.
           MOV     ByteCnt,#1     ;Read one data byte.
           ACALL   RcvData
           INC     XmtDat+2       ;Advance data to next value.

           MOV     SlvAdr,#I2CLED ;Write data to SAA1064 LED driver.
           MOV     R0,#XmtDat     ;Start of data.
           MOV     ByteCnt,#6     ;Send 6 bytes (subaddress, control, data).
           ACALL   SendData

           MOV     SlvAdr,#I2CRAM ;Write data to PCF8570 RAM.
           MOV     R0,#XmtDat     ;Start of data.
           MOV     ByteCnt,#8     ;Send 8 bytes (subaddress + 7 data bytes).
           ACALL   SendData

           MOV     SlvAdr,#I2CRAM ;Write subaddress to PCF8570 RAM.
           MOV     R0,#XmtDat     ;Start of data.
           MOV     ByteCnt,#1     ;Send one byte (subaddress).
           ACALL   SendData
           MOV     SlvAdr,#I2CRAM ;Read back data from PCF8570 RAM.
           MOV     R0,#RcvDat     ;Start of data.
           MOV     ByteCnt,#7     ;Read 7 data bytes.
           ACALL   RcvData

           AJMP    TestLoop       ;Repeat operation for scope watchers.

           END
