Main | Penguins | Electronics projects | Other projects | Travel | Photos | Design | Publications

I2C Interface for PC

This document is about the "I2C DLL": The Inter Integrated circuit Communication Dynamic Link Library A library for using the Philips I2C bus in MS Windows applications.

G.L. 1997

Contents

  1. Introduction
  2. The library and how to use it
    1. The low level routines
    2. Other routines
    3. Calling the routines
  3. Routine reference
    1. Start and initialise the I2C bus
    2. Address the I2C chip at address "Slave"
    3. Send a byte to the last addressed chip
    4. Send an array of up to 256 byte to the last addressed chip
    5. Read a byte from the last addressed chip
    6. Read an array of up to 256 byte to the last addressed chip
    7. Close the I2C bus
    8. Return the error code of the first occurred error
    9. Get information on the current I2C error code
    10. Approximate the specified clock frequency when using a serial port
    11. Read a byte from, or write a byte to a PCF8574 8-bit I/O port
    12. Read the analog inputs of a PCF8591 and/or set the DAC output
    13. Read data from memory chips or write data to memory chips
  4. Error messages
  5. Files
  6. References

Top

1. Introduction

The Inter Integrated circuit Communication protocol (I2C bus) as developed by Philips Semiconductors for the control and data exchange in television sets, is a gentle way to communicate with chips. It is a multi-master/multi-slave protocol using a two wire bus. Possible masters are a computer or a micro-controller. The manufacturer produces various chips which can be controlled or read-out as an I2C slave. The most important ICs for developing computer controlled systems are:

  • PCF8574 : 8 bit digital I/O chip,
  • PCF8591 : four 8 bit A/D converters and one 8 bit D/A converter,
  • TDA8444 : Octal 6 bit D/A converter.

For the control of single-master/multi-slave systems, a library of routines (Dynamic Link Library) for the PC was written. This I2C DLL supports two ways to connect the computer to the bus:

  • Using a PCF8584 I2C bus controller which has to be assembled into a PC card (for an ISA slot). The advantage of such a controller is that it takes over all the bus timing, a disadvantage is that not every computer has this card.
  • Using a serial port. By (ab)using some control lines of the computer&s serial port the I2C bus can be emulated. Figure 1.1 shows that by only using four passive components, the level conversion from RS232 (-12V/-12V) to I2C (0V/5V) can be done.

Figure 1.1: Interface for RS232 to I2C

How the pins should be connected to either a 9 or 25 pins sub-D connector can be found in table 1.1.

Table 1.1: Connector pins

Pin9 pins sub-D25 pins sub-D
CTS85
DTR420
RTS74
GND57

However, this simple electronic implementation requires the software to take over the bus timing.

Connecting an I2C chip becomes now as easy as in Figure 1.2. Where the master transmitter is the PC with the electrical bus interace of Figure 1.1 and the I2C DLL installed.

Figure 1.2: Building an application with a PC having the I2C DLL as the "Master transmitter". (Taken from the Philips Semiconductor datasheet of the PCF8591)

Top

2. The library and how to use it

2.1 The low level routines

The routines in the library can be separated into three kinds: the low level ones, the high level ones and the error routines. The basic low level ones are:

  • I2C_START: Initialises the bus. This routine must always be called first in an application that uses the library in order to select whether an RS232 port or an ISA card with PCF8584 controller is used.
  • I2C_ADDRESS: After initialising, an I2C chip must be addressed by its unique 8-bit address. All following read and write instructions will be to this chip. At any time in the application, a new chip can be addressed.
  • I2C_SEND: Send a byte to the last addressed I2C chip.
  • I2C_READ: Read a byte from the last addressed I2C chip.
  • I2C_STOP: To release the bus. A program that uses library must always uninitialise the bus afterwards by calling this routine.

These basic low level routines show already how easy an I2C session can be: Initialise the bus, address a chip, read and write bytes to his chip and close the bus.

What the chip does with the written bytes and what the read bytes mean can be found in its specific datasheet. For example, the A/D-D/A converter PCF8591 takes the first sent byte after addressing always as a control byte and the subsequent bytes as data. In order to send a new control byte to this chip the user has to re-address it.

The routines in the library take care of all handshaking and data direction control. When reading the I2C bus specifications [1] one will find a lot of rules concerning "start", "stop" and "repeated start" conditions and read/write bits. The I2C DLL figures this all out for the user.

Top

2.2 Other routines

Besides the low level routines two routines for error handling are available:

  • I2C_ERROR: Returns the error level of the bus which is unequal to zero in case of an error.
  • I2C_ERRORTEXT: When an error has occurred, this routine gives two strings containing information on the source and the cause of the error.

After an error occurred, all subsequent I2C routines are skipped. So the error checking can be done once after a complete I2C session. However, when the program has to jump out of a loop in case of an error, the checking has to be done repeatedly.

When a lot of bytes have to be send to or read from a single slave (= last addressed IC), the available routines I2C_READ_ARRAY and I2C_SEND_ARRAY are faster than the simple I2C_READ and I2C_SEND.

Besides those routines, the DLL has a number of more dedicated procedures which call the low level procedures in order to simplify the control of some specific chips.

Top

2.3 Calling the routines

The routines in the library can easily be called using various MS-Windows applications. For both LabView and Turbo Pascal, an easy to use software interface was written.

In Turbo Pascal, a routine in a library can only be called after it is defined by giving the name of the DLL and the index of the procedure or function. For example, importing the I2C_ADDRESS routine goes like this:

	{$F+} procedure I2C_Address(Slave : word); external 'I2C' index 2; {$F-}

It is essential to force the "far" model by setting the compiler directive {$F+} or by adding the "far" compiler directive. For the use under Turbo Pascal a unit was written which makes all mentioned procedures and functions accessible. This unit does nothing else than setting all the links to the routines as described before.

Here is an example of a Turbo Pascal program which sends the byte FFh to a PCF8574 8-bit I/O port at address 40h:

	program I2C_Test;
	uses I2C_Unit;	 		(* Import DLL routines *)
	const COM2 = $2F8;		(* Hexadecimal address of COM2 port *)
	var orig, loc : string;
	begin
		I2C_Start(1,COM2,0,0);	(* Start communication via COM2 at 90kHz *)
		I2C_Address($40);	(* Address chip at 40h *)
		I2C_Send($FF);		(* Send byte FFh *)
		I2C_stop;		(* Stop I2C communication *)
		if I2C_error<>0 then	(* Check on error *)
		begin
			I2C_errortext(orig, loc);
			writeln(orig);	(* And display error *)
			writeln(loc);
		end;
	end.

The use of the DLL under pascal has the advantage that it has the opportunity of adding a lot of instructive text and constants.

Under LabView a VI library was made which gives an equivalent icon for each DLL procedure or function. Sending the byte FFh to the I2C chip at address 40h becomes as easy as shown in Figure 2.1. However, this program will not work correctly because all four actions will be executed simultaniously. Besides this, it does not take care of error handling.

Figure 2.1: Example of the use of the I2C driver in a LabView application. Remark: LabView will execute these actions in parallel, for correct operation a sequence structure should be added.

Figure 2.2 shows a complete executable LabView program. The front panel (upper window) has four inputs: the port which will be used, the chip to be addressed, the byte to be send and a button for stopping the program.

Figure 2.2: A more realistic program including error checking.

The program executes a sequence structure with sequence number two determining the actual program functionality. In a while loop the byte set by the operator on the front panel will be sent to the adressed I2C chip until the STOP button is pressed or an error has occurred. Notice that the icon that takes care of the error checking will show an error message.

The LabView implementation is the most convenient because it gives a professional looking user interface after a small programming time.

Top

3. Routine reference

3.1 Start and initialise the I2C bus.

procedure I2C_Start(Connect_to : byte; Port : word; SCL, clock_freq : byte); external 'I2C' index 1;

Connect_to:

  • 0 - Use a PCF8584 I2C bus controller which is placed on a special assembled ISA card and by hardware connected to the address given by “Port”.
  • 1 - Use a serial port with the level converter as given in Figure 1.1.
  • 2 - Use a parallel port (Not yet implemented)

Port: The computer’s base I/O address of the I2C interface. When a PCF8584 interface card is used, this value depends on the card. For RS232 ports valid addresses are:

  • COM1 = $3F8
  • COM2 = $2F8
  • COM3 = $3E8
  • COM4 = $2E8

SCL: The clock frequency of the Serial CLock line. When a serial port is used (“Connect_to” = 1) then I2C_SetCOMFreq is called to approximate the frequency when 0 ≤ SCL ≤ 3. This will take a second for determining the clock speed but can be avoided by setting SCL = 4.

  • 0 : 90 kHz
  • 1 : 45 kHz
  • 2 : 11 kHz
  • 3 : 1.5 kHz
  • Other : Error when “Connect_to” = 0, else default clock speed

clock_freq: Only significant when using a PCF8584 interface card for setting this chip’s internal clock frequency. Valid values are:

  • 0 : 12 MHz
  • 1 : 8 MHz
  • 2 : 6 MHz
  • 3 : 4.43 MHz
  • 4 : 3 MHz
  • Other : Error when “Connect_to” = 0

Top

3.2 Address the I2C chip at address "Slave".

procedure I2C_Address(Slave : byte); external 'I2C' index 2;

All following I2C_read, I2C_send, I2C_read_array and I2C_send_array operations will be to this chip until another one is addressed. Notice that an odd address can never be a valid I2C address according to the I2C specifications.

Top

3.3 Send a byte to the last addressed chip.

procedure I2C_send(byt : byte); external 'I2C' index 3;

Top

3.4 Send an array of up to 256 byte to the last addressed chip.

procedure I2C_send_array(numbytes : byte; var bytarray : array_of_bytes); external 'I2C' index 4;

The array of bytes is of the type:

	type array_of_bytes  = array[1..256] of byte;

Top

3.5 Read a byte from the last addressed chip.

function I2C_read : byte; external 'I2C' index 5;

Top

3.6 Read an array of up to 256 byte to the last addressed chip.

procedure I2C_read_array(numbytes : byte; var bytarray : array_of_bytes); external 'I2C' index 6;

The array of bytes is of the type:

	type array_of_bytes  = array[1..256] of byte;

The number of bytes to read (0 .. 256) must be given by “numbytes”.

Top

3.7 Close the I2C bus.

procedure I2C_Stop; external 'I2C' index 7;

Top

3.8 Return the error code of the first occurred error.

function I2C_error : byte; external 'I2C' index 8;

Top

3.9 Get information on the current I2C error code.

procedure I2C_ErrorText(var origin, loc : string); external 'I2C' index 9;

origin : What the error is.

loc : At what procedure it happened.

Under LabView the routines I2C_error and I2C_errorText are combined into a single icon. This icon checks whether the error code is equal to zero and shows a message when an error has occurred.

Top

3.10 Approximate the specified clock frequency when using a serial port.

function I2C_SetCOMFreq(freq : byte):byte; external 'I2C' index 10;

When using a serial port, the SCL (serial clock) timing is done by performing a wait loop. The number of times this wait loop is executed is set by an internal variable. The routine I2C_SetCOMFreq tries to find the variable which results in the specified “freq” frequency (in kHz).

Top

3.11 Read a byte from, or write a byte to a PCF8574 8-bit I/O port.

procedure PCF8574(Switches: byte; Read_or_Write: boolean; var byt: byte); external 'I2C' index 11;

A high level routine which addresses a PCF8574 at address 4xh and reads or writes a byte.

Switches : specify the base address as

	40h + (“switches” shl 1)

where “switches” represents the levels of A0, A1 and A2. For example when A0 and A1 are low and A2 is high, the value of “switches” is 100b (= 4) and so the base address is 48h.

Read_or_Write :

  • True : read a byte
  • False : send a byte

byt : The byte to read or write

Top

3.12 Read the analog inputs of a PCF8591 and/or set the DAC output.

procedure PCF8591(Switches, Input_Type, Input_enable, AD_enable, Numbytes_to_send, Numbytes_to_read: byte; var bytarray : Array_of_bytes; var bytmatrix : matrix_of_bytes); external 'I2C' index 12;

A high level routine which addresses the PCF8591 at address 9xh, controls the DAC output and reads the four ADCs.

Switches : specify the base address as

	90h + (“switches” shl 1)

where “switches” represents the levels of A0, A1 and A2. For example when A0 is low and A1 and A2 are high, the value of “switches” is 110b (= 6) and so the base address is 9Ch.

Input_Type :

  • 0: Four single ended inputs
  • 1: Three differential inputs
  • 2: Single ended and differential mixed.
  • 3: Two differental inputs

Input_enable :

  • 0: enable input 0
  • 1: enable input 1
  • 2: enable input 2
  • 3: enable input 3
  • 4: enable all

AD_enable :

  • 0: enable A/D conversion only
  • 1: enable D/A conversion only
  • 2: first D/A conversion then A/D
  • 3: alternate D/A conversion and A/D

Numbytes_to_send : The number of bytes to send to the DAC.

Numbytes_to_read : The number of bytes to read from each specified ADC

bytarray : An array of type “Array_of_bytes” to send to the DAC

	type array_of_bytes  = array[1..256] of byte;

bytmatrix : A matrix of type “matrix_of_bytes” which returns the read ADC data in columns.

	type matrix_of_bytes = array[1..4,1..256] of byte;

Top

3.13 Read data from memory chips or write data to memory chips.

procedure Memories(Switches, Memory_type: byte; Read_or_write: boolean; Start_address, Numbytes: word; var bytarray : Large_Array_of_bytes); external 'I2C' index 13;

High level routine which addresses the I2C chip itself.

Switches :specify the base address as

	A0h + (“switches” shl 1)

Memory type:

  • 0: PCF8570 256x8 bit RAM
  • 1: PCA8581 128x8 bit EEPROM
  • 2: PCX8582 256x8 bit EEPROM
  • 3: PCX8594X 512x8 bit EEPROM
  • 4: PCX8598X 1024x8 bit EEPROM

Read_or_Write :

  • True : read a large array of bytes
  • False : send a large array of bytes

Start_address : The address in the memory chip of firstbyte to write or read.

Numbytes : The number of bytes to read or write

bytarray : The bytes to read or write.

	type Large_Array_of_bytes = array[1..1024] of byte;

Top

4. Error messages

The error messages which can be returned in the “origin” field of I2C_ ErrorText are:

No I2C error
Everything is OK

I2C Bus not free
Can only occur when using a PCF8584. The PCF8584 reported a BB-bit error, which means that in a multi master system another master has control of the bus.

No acknowledge from slave
The addressed chip is not responding.

PCF8584 not found
The presence of the PCF8584 I2C bus controller was checked by monitoring the BER bit of this controller, and it did not respond.

Invalid serial clock value
Can only occur when calling I2C_START using a PCF8584. The specified “SCL” value was not between 0 and 3.

Invalid internal clock frequency for PCF8584
Can only occur when calling I2C_START using a PCF8584. The specified “connect_to” value was not between 0 and 4.

Did not succeed in setting serial clock frequency
The procedure I2C_SetCOMFreq was called but could not find a delay time which resulted in the specified SCL frequency. When this routine was called automatically during I2C_START, this error can be prevented by setting “SCL” = 4.

Attempted to read or write from a not existing address
The MEMORIES routine was called with “Start_address”+”Numbytes:” larger than the available number of bytes in the memory chip.

Illegal settings
Returned by the high level routines when a meaningless option is selected.

Top

5. Files

I2C.DLL
The dynamic link library. Place this one in the ..\WINDOWS\SYSTEM directory. The Borland Pascal 7.0 sourcecode for the DLL can be found here, and an example of the usage of the DLL here.

I2C_UNIT.PAS
A unit which makes the link to the routines in I2C.DLL. Not essential when using LabView. Routines can be linked without this unit as well.

I2C.LLB
This is a LabView virtual instrument library which provides an icon and a link to I2C.DLL for each routine. It is convenient to place this file in the LABVIEW\VI.LIB\INSTR directory in order to have the icons available in the pop-up instrument list under “instrument I/O → I2C Bus”.

Top

6. References

[1] The I2C bus and how to use it, Philips Semiconductor application note.