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.


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.


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

Code example

Feel free to check the Kotlin DSL for I²C

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

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


Now we can use the following example:

import com.pi4j.Pi4J;
import com.pi4j.context.Context;

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);
			currentState = setPin(currentState, 8, tca9534Dev, false);

			currentState = setPin(currentState, 7, tca9534Dev, true);
			currentState = setPin(currentState, 7, tca9534Dev, false);

	public static byte setPin(byte currentState, int pin, I2C tca9534Dev, boolean high) {
		byte newState;
		if (high)
			newState = (byte) (currentState | (1 << pin));
			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();