I2C Driver in Pseudocode
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)
- 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.
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 /
-
CALL STOP
SDA=1
SCK=0
FOR n = 0 to 3
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)
-
SDA= BIT(n) of BUFFER
SCK=1
SCK=0
FOR n = 7 TO 0
NEXT n
SDA=1
ENDsub
SUBroutine GETBYTE
-
SCK=1
BIT(n) OF BUFFER = SDA
SCK=0
FOR n = 7 to 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)
-
-
CALL GIVEACK
CALL GETBYTE DATA(x)=BUFFER /Copy received BYTE to DATA array /
IF X< Number_of_bytes THEN /Not ack the last byte/
END IF
-
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
NEXT x
CALL STOP
ENDsub
SUBroutine WRITE(Device_address,Number_of_bytes)
-
CALL PUTBYTE (DATA(x))
CALL GETACK
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
NEXT x
CALL STOP
ENDsub
SUBroutine RANDOMREAD(Device_adress,Start_adress,Number_of_bytes)
-
CALL GETBYTE
DATA(x)=BUFFER
CALL GIVEACK
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
NEXT x
CALL STOP
ENDsub
SUBroutine RANDOMWRITE(Device_adress,Start_adress,Number_of_bytes)
-
CALL PUTBYTE (DATA(x))
CALL GETACK
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
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.