Bitbanging I2C

It is written in PseudoCode which is an imaginary programming language that any programmer should be capable of porting to his/her favorite language.

First we will define a set of basic interface routines. All text between / / is considered as remark.
Following variables are used : n,x = a general purpose BYTE, SIZE = a byte holding the maximum number of transferred data at a time,
DATA(SIZE) = an array holding up to SIZE number of bytes. This will contain the data we want to transmit and will store the received data.
BUFFER = a byte value holding immediate received or transmit data. 

/ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ /
/ **** I2C Driver V1.1 Written by V.Himpe. Released as Public Domain **** /
/ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ /

DECLARE N,SIZE,BUFFER,X Byte
DECLARE DATA() Array of SIZE elements

SUBroutine I2C_INIT / call this immediately after power-on /
     SDA=1
     SCK=0
     FOR n = 0 to 3
           CALL STOP 
      NEXT n 
ENDsub

SUBroutine START
     SCK=1 / BUGFIX !/
     SDA=1 / Improvement /
     SDA=0
     SCK=0
     SDA=1 
ENDsub

SUBroutine STOP
     SDA=0
     SCK=1
     SDA=1 
ENDsub

SUBroutine PUTBYTE(BUFFER)
     FOR n = 7 TO 0
           SDA= BIT(n) of BUFFER
           SCK=1
           SCK=0 
      NEXT n
     SDA=1 
ENDsub

SUBroutine GETBYTE
     FOR n = 7 to 0
           SCK=1
           BIT(n) OF BUFFER = SDA
           SCK=0 
      NEXT n
     SDA=1 
ENDsub
SUBroutine GIVEACK
     SDA=0
     SCK=1
     SCK=0
     SDA=1 
ENDsub
SUBroutine GETACK
     SDA=1
     SCK=1
     WAITFOR SDA=0
     SCK=0 
ENDSUB

/ this concludes the low-level set of instructions for the I2C driver
The next functions will handle the telegram formatting on a higher level /
SUBroutine READ(Device_address,Number_of_bytes)


SUBroutine READ(Device_address,Number_of_bytes)
     Device_adress=Device_adress OR (0000.0001)b /This sets the READ FLAG/
     CALL START
     CALL PUTBYTE(Device_adress)
     CALL GETACK
     FOR x = 0 to Number_of_bytes
           CALL GETBYTE DATA(x)=BUFFER /Copy received BYTE to DATA array /
           IF X< Number_of_bytes THEN /Not ack the last byte/
                 CALL GIVEACK 
            END IF 
      NEXT x
     CALL STOP 
ENDsub
SUBroutine WRITE(Device_address,Number_of_bytes)
     Device_adress=Device_adress AND (1111.1110)b / This clears READ flag /
     CALL START
     CALL PUTBYTE(Device_adress)
     CALL GETACK
     FOR x = 0 to Number_of_bytes
           CALL PUTBYTE (DATA(x))
           CALL GETACK 
      NEXT x
     CALL STOP 
ENDsub
SUBroutine RANDOMREAD(Device_adress,Start_adress,Number_of_bytes)
     Device_adress=Device_adress AND (1111.1110)b / This clears READ flag /
     CALL START
     CALL PUTBYTE(Device_adress)
     CALL GETACK
     CALL PUTBYTE(Start_adress)
     CALL GETACK
     CALL START /create a repeated start condition/
     Device_adress=Device_adress OR (0000.0001)b /This sets the READ FLAG/
     CALL PUTBYTE(Device_adress)
     CALL GETACK
     FOR x = 0 to Number_of_bytes
           CALL GETBYTE
           DATA(x)=BUFFER
           CALL GIVEACK 
      NEXT x
     CALL STOP 
ENDsub
SUBroutine RANDOMWRITE(Device_adress,Start_adress,Number_of_bytes)
     Device_adress=Device_adress AND (1111.1110)b / This clears READ flag /
     CALL START
     CALL PUTBYTE(Device_adress)
     CALL GETACK
     CALL PUTBYTE(Start_adress)
     CALL GETACK
     FOR x = 0 to Number_of_bytes
           CALL PUTBYTE (DATA(x))
           CALL GETACK 
      NEXT x
     CALL STOP 
ENDsub
/ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ /
/ **** I2C Driver . (c)95-97 V.Himpe . Public Domain release *** /
/ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ /

Some notes about the high level routines.

The READ and WRITE routine read / write one or more byte(s) from / to a slave device. Generally this will be used only with Number_of_bytes set to 1. An example:
PCD8574=(0100.0000)b
CALL READ(PCD8574,1)
result = DATA(0)
/ will read the status of the 8 bit input port of a PCD8574. /
DATA(0)=(0110.01010)b
CALL WRITE(PCD8574,1)
/ will write 0110.0101 to the 8 bit port of the PCD8574 /

When do you need a multi-read ? Consider a PCF8582 EEPROM. You want to read its contents in one time.
PCF8582=(1010.0000)b
CALL READ(PCF8582,255)

You can do the same with WRITE for the EEPROM with the restriction that Number_of_bytes is not larger than 4 AND that you write on page boundaries (multiple of 4 for offset).

You will have to check the components datasheets.

The most useful instructions are RANDOMREAD and RANDOMWRITE.

Write 4 bytes of data to location 20h of the EEPROM:
DATA(0)=(1010.0011)b
DATA(1)=(1110.0000)b
DATA(2)=(0000.1100)b
DATA(3)=(1111.0000)b
CALL RANDOMWRITE (PCF8582,(20)h,3)

The same goes for reading 16 bytes from the EEPROM starting at address 42h:
CALL RANDOMREAD(PCF8582,(42)h,15)

The results are stored in DATA. All you have to do is read them out of the array for processing. When you give the devices address to these routines you don't have to care about the R/W flag. It will be automatically set to the right state inside the routines.

© Vincent Himpe 2016