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.

Command line tool to output a list of installed busses:

root@rp5:~# i2cdetect -l
i2c-1	i2c       	Synopsys DesignWare I2C adapter 	I2C adapter
i2c-11	i2c       	107d508200.i2c                  	I2C adapter
i2c-12	i2c       	107d508280.i2c                  	I2C adapter

Command line tool to immediately scan the standard addresses on I2C bus 1 (i2c-1)

root@rp5:~# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3f 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

1 device found with address of 0x3f.

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:


<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();
	}
}