Inter-Integrated Circuit (I²C)

What is it?

I²C (spoken as I-Squared-C) is a bus originally invented by Philips. It is designed as a classic master-slave bus. A data transfer is always i nitiated by a master. It can also be set up in a multi-master system. I²C is connected via two signal lines (data line and clock line). The transmission rate of the bus can be between 0.1 Mbit/s up to 3.4 Mbit/s depending on the clock rate. If only a unidirectional connection is required, even 5.0 Mbit/s would be possible. It should be noted: the higher the clock rate, the more susceptible to failure the overall system becomes. The low operating voltage of only 3.3V does not contribute to interference resistance either.

Uses

I²C is mainly used for communication between microcontrollers. The advantage that a whole series of microcontrollers can be controlled via just 2 lines is of course very interesting for the circuit board layout. The main advantages of I²C are its simplicity. There are certainly newer bus systems with better transmission rates. Hardly any bus system is as easy to use as I²C. Even “hot plugging”, ie plugging in and unplugging the devices during operation, is possible with I²C.

Addressing

I²C uses an address space of 7 bits. This allows up to 112 nodes on one bus. The remaining 16 addresses are reserved for special applications. Usually the address of a device is defined directly by the manufacturer. It can therefore be found in the relevant data sheets. Due to the shortage of addresses, there is also a variant with a 10-bit address space. Up to 1136 nodes are possible, and the protocol is compatible with the smaller 7-bit address space.

Transfer rates

ModeMax. transfer rateDirection
Standard Mode0.1 Mbit/sbidirektional
Fast Mode0.4 Mbit/sbidirektional
Fast Mode Plus1.0 Mbit/sbidirektional
High Speed Mode3.4 Mbit/sbidirektional
Ultra Fast-mode5.0 Mbit/sunidirektional

Additional information

Clock Stretching

Please be aware there are some hardware issues when using the Raspberry Pi with devices that expect to be able to use clock stretching, for more info see “Adventures in I2C: clock stretching on the Raspberry Pi” and “I2C stretch bug. Been fixed or not?".

Clock stretching in I2C allows a slave device to halt the master before a more data is sent. This is often the case when the slave device writes to an EEPROM etc. which takes longer than a usual read or write to a register.

On the Raspberry Pi clock stretching can be configured by using a higher timeout while waiting for a slave to respond. This is something that can not be done by the pi4j, as it requires root privileges.

There are two ways to change the clkt_tout value. This repository path has two files, a i2c1_get_clkt_tout.c and i2c1_set_clkt_tout.c. Build them as follows:

Prepare:

# install gcc to compile the c files
apt install build-essential

mkdir clkt_tout
cd clkt_tout/
wget wget https://raw.githubusercontent.com/raspihats/raspihats/master/clk_stretch/i2c1_get_clkt_tout.c
wget wget https://raw.githubusercontent.com/raspihats/raspihats/master/clk_stretch/i2c1_set_clkt_tout.c

Makefile Save as Makefile

CC=gcc
CFLAGS=-Wall

.PHONY: all install uninstall clean

all: i2c1_set_clkt_tout i2c1_get_clkt_tout

i2c_get_clkt_tout: i2c1_get_clkt_tout.c
        $(CC) -o i2c1_get_clkt_tout i2c1_get_clkt_tout.c

i2c_set_clkt_tout: i2c1_set_clkt_tout.c
        $(CC) -o i2c1_set_clkt_tout i2c1_set_clkt_tout.c

install:
        cp i2c1_get_clkt_tout /usr/local/bin/i2c1_get_clkt_tout
        cp i2c1_set_clkt_tout /usr/local/bin/i2c1_set_clkt_tout

uninstall:
        rm -f /usr/local/bin/i2c1_get_clkt_tout
        rm -f /usr/local/bin/i2c1_set_clkt_tout

clean:
        rm -f i2c1_set_clkt_tout i2c1_get_clkt_tout

Build and install

make
sudo make install

Usage

# read current clkt_tout:
$ sudo i2c1_get_clkt_tout 
i2c1_get_clkt_tout: CLKT.TOUT = 1000

# set timeout to 1 second:
i2c_set_clkt_tout 1000

Code example

The following code shows setting the pins on a TCA 9534 which can be found on “Sequent Microsystems”

To use the LinuxFS provider, which provices I2C, add the proper dependency:


<dependency>
    <groupId>com.pi4j</groupId>
    <artifactId>pi4j-plugin-linuxfs</artifactId>
    <version>${pi4j.version}</version>
</dependency>

Now we can use the following example:

import com.pi4j.Pi4J;
import com.pi4j.context.Context;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;
import com.pi4j.io.i2c.I2CProvider;

public class SimpleTca9534I2cTest {

	private static final byte TCA9534_REG_ADDR_OUT_PORT = 0x01;
	private static final byte TCA9534_REG_ADDR_CFG = 0x03;

	public static void main(String[] args) throws Exception {

		Context pi4j = Pi4J.newAutoContext();
		I2CProvider i2CProvider = pi4j.provider("linuxfs-i2c");
		I2CConfig i2cConfig = I2C.newConfigBuilder(pi4j).id("TCA9534").bus(1).device(0x3f).build();
		try (I2C tca9534Dev = i2CProvider.create(i2cConfig)) {

			int config = tca9534Dev.readRegister(TCA9534_REG_ADDR_CFG);
			if (config < 0)
				throw new IllegalStateException(
						"Failed to read configuration from address 0x" + String.format("%02x", TCA9534_REG_ADDR_CFG));

			byte currentState = (byte) tca9534Dev.readRegister(TCA9534_REG_ADDR_OUT_PORT);

			if (config != 0x00) {
				System.out.println("TCA9534 is not configured as OUTPUT, setting register 0x" + String
						.format("%02x", TCA9534_REG_ADDR_CFG) + " to 0x00");
				currentState = 0x00;
				tca9534Dev.writeRegister(TCA9534_REG_ADDR_OUT_PORT, currentState);
				tca9534Dev.writeRegister(TCA9534_REG_ADDR_CFG, (byte) 0x00);
			}

			// bit 8, is pin 1 on the board itself, so set pins in reverse:
			currentState = setPin(currentState, 8, tca9534Dev, true);
			Thread.sleep(500L);
			currentState = setPin(currentState, 8, tca9534Dev, false);
			Thread.sleep(500L);

			currentState = setPin(currentState, 7, tca9534Dev, true);
			Thread.sleep(500L);
			currentState = setPin(currentState, 7, tca9534Dev, false);
			Thread.sleep(500L);
		}
	}

	public static byte setPin(byte currentState, int pin, I2C tca9534Dev, boolean high) {
		byte newState;
		if (high)
			newState = (byte) (currentState | (1 << pin));
		else
			newState = (byte) (currentState & ~(1 << pin));

		System.out.println("Setting TCA9534 to new state " + asBinary(newState));
		tca9534Dev.writeRegister(TCA9534_REG_ADDR_OUT_PORT, newState);
		return newState;
	}

	public static String asBinary(byte b) {

		StringBuilder sb = new StringBuilder();

		sb.append(((b >>> 7) & 1));
		sb.append(((b >>> 6) & 1));
		sb.append(((b >>> 5) & 1));
		sb.append(((b >>> 4) & 1));
		sb.append(((b >>> 3) & 1));
		sb.append(((b >>> 2) & 1));
		sb.append(((b >>> 1) & 1));
		sb.append(((b >>> 0) & 1));

		return sb.toString();
	}
}