I2C

Inter-Integrated Circuit(I2C) protocol is a serial communication protocol. It uses a master-slave paradigm to communicate with devices. I2C requires only two wires

  • SDA - Serial Data Line: Bidirectional line
  • SCL - Serial Clock Line: A master controlled clock line for synchronous data transfer

I2C is a half-duplex protocol, meaning at once, only one device can send out data onto the bus. This is unlike SPI which is fully duplex where we are allowed to send and receive at the same time.

The default idle state for both SDA and SCL Is HIGH. When SDA and then SCL go low, the bus is said to have been claimed. The data frame transmitted is -

  • Start condition (bus is claimed)
  • Usually 7 bit slave address (MSB first)
  • A read/write bit (0 is write, 1 is read)
  • An acknowledgement. Since the default is HIGH, an ACK would mean LOW. If there is no change in default state, this lack of ACK is called negative acknowledgement (NACK)
  • Data byte (MSB first) followed by ACK as many times as needed
  • Stop condition - SCL and then SDA high
    Important

    Some devices come with a fixed address. To use multiple devices with unchangable addresses, we can make use of a multiplexer like (Adafruit TCA9548A). We connect the multiplexer to the I2C bus, and connect the devices to the MUX's output. We then simply activate whatever MUX channel we want and it'll be as if the other devices aren't even there. Until we change the channel that is.

Libraries and utils

i2cdetect

i2cdetect is part of the i2c-tools metapackage. It outputs addresses of I2C devices that are connected to the host device.

smbus2

smbus2 is a Python library that is supposed to be a drop in replacment for its predecessor smbus. It supports read/write operations on individual bytes, words or blocks of memory. The functions that have block in them, are operating on a block of data and therefore expect an address, an offset, and the length of the block to operate on. And the ones without block will obviously not require the length argument.
Examples

# <byte|word>_data only expect the address and offset of a byte.
bus.read_byte_data(address, offset)
# ones with block in their name also require to know where the block ends
bus.read_block_data(address, offset, length)
# we also have a read_i2c_block_data for reading from registers of i2c devices
bus.read_i2c_block_data(address, offset, length)

# smbus only allows for reading blocks of 32 bytes, so smbus2 comes with additional functionality to get larger blocks
i2c_msg.read(address, length)

write = i2c_msg.write(60, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # this writes 10 bytes starting from 0x60
read = i2c_msg.read(60, 10) # this reads 10 bytes starting from 0x60
# We can put multiple i2c_msgs into i2c_rdwr to exectue them in order
bus.i2c_rdwr(write, read)

# an i2c_msg can be converted into a list to get the data contained like so
# i2c_msg objects are also iterable
data = list(msg)