Skip to content

I2C rework

Dylan Laduranty edited this page Jul 14, 2018 · 2 revisions

The aim of the I2C refactoring is the provide a more versatile I2C API to RIOT’s users. The previous API and its implementation had several major issues that we tried to fix with this new API. This step forward will ease the implementation of 10bits address device or 16bits registers device.

What are the changes ?

Initialization :

The previous i2c_init_master(dev, speed) has been replaced by i2c_init(dev). Before the rework, each driver (sensors) init called this function to init the bus. Nevertheless, several sensors can be connected to the same I2C master and in the previous design, each sensors re-init the I2C bus. While this solution works perfectly for one sensor, I2C is usually with several sensors on the same bus.

Now, sensors drivers don’t have to initialize the bus anymore. The I2C initialization is done in drivers/periph_common/init.c file. This setup is done automatically by RIOT, the init function will parsed the periph_conf.h of the board and initialize all the I2C buses declare in this file just as the SPI.

Error codes :

This rework introduces new error codes and this adoption of errno for the I2C API. Possible errno for the I2C API are the following :

  • 0 When success
  • -EIO When slave device doesn't ACK the byte
  • -ENXIO When no devices respond on the address sent on the bus
  • -ETIMEDOUT When timeout occurs before device's response
  • -EINVAL When an invalid argument is given
  • -EOPNOTSUPP When MCU driver doesn't support the flag operation
  • -EAGAIN When a lost bus arbitration occurs

In this way, MCU ARCH don’t have to define its own I2C errors to avoid duplication.

API definitions :

the complete list of I2C functions that can be used by the user are :

  • int i2c_acquire(i2c_t dev);
  • int i2c_release(i2c_t dev);
  • int i2c_read_reg(i2c_t dev, uint16_t addr, uint16_t reg, void *data, uint8_t flags);
  • int i2c_read_regs(i2c_t dev, uint16_t addr, uint16_t reg, void *data, size_t len, uint8_t flags);
  • int i2c_read_byte(i2c_t dev, uint16_t addr, void *data, uint8_t flags);
  • int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len, uint8_t flags);
  • int i2c_write_byte(i2c_t dev, uint16_t addr, uint8_t data, uint8_t flags);
  • int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint8_t flags);
  • int i2c_write_reg(i2c_t dev, uint16_t addr, uint16_t reg,uint8_t data, uint8_t flags);
  • int i2c_write_regs(i2c_t dev, uint16_t addr, uint16_t reg,const void *data, size_t len, uint8_t flags);

To avoid code duplicate we also introduce 4 new defines


These defines are defined per CPU arch within cpu/ARCH/include/periph_cpu.h or cpu/ARCH/include/periph_cpu_common.h only if they are needed : If the I2C peripheral in the MCU is flexible enough, then only the i2c_write_bytes and i2c_read_bytes functions are implemented by the CPU driver. Other functions can then be define as wrapper using i2c_write_bytes and i2c_read_bytes. These default wrappers are under drivers/periph_common/i2c.c

But sometimes, the I2C peripheral hardware is not flexible enough (i.e : nrf52). In this case, MCU driver must define its own implementations for part of the functions or all the functions. This usually happen if the hardware is not versatile enough. Then, default wrappers cannot be used so the dedicated define above is not define for this ARCH and the cpu driver must define its own version of the function for example : SAM0 I2C driver only implements i2c_read_bytes and i2c_write_bytes, others functions are the default wrappers reusing i2c_read_bytes, i2c_write_bytes. NRF52 cannot use the default wrappers, so all functions are implements in its driver. This approach avoid code duplication whenever it’s possible by using common default wrappers for all CPU when we can. i2c_acquire/i2c_release keep the same role as before.

  • Acquire :
  • get the mutex which has been initialize in i2c_init functions
  • enable the peripheral clock (if needed/implemented)
  • call pm_block (if needed and implement)
  • i2c_release :
  • release the mutex
  • disable the peripheral clock to save power (if needed/implemented)
  • call pm_unblock (if implemented)

Introduction of the flag arg

The idea behind the arg is to give user the ability to add flexibility to the API functions. Currently, the following flag are implemented :

  • I2C_ADDR10 = 0x01, /**< use 10-bit device addressing */
  • I2C_REG16 = 0x02, /**< use 16-bit register addressing */
  • I2C_NOSTOP = 0x04, /**< do not issue a STOP condition after transfer */
  • I2C_NOSTART = 0x08, /**< skip START sequence, ignores address field */

All those values can be OR’ed within the function’s call. i.e : i2c_write_byte(dev, addr, data, I2C_ADDR10 | I2C_REG16); If the driver you are using cannot support one of the flag you are using, the function call will return -EOPNOTSUPP. See your I2C CPU driver for more information.

Miscellaneous :

  • all STMs CPUs provide I2C and the driver is unified (well, there's 2 versions of it)
  • STM32F3 I2C driver has been fixed (it's broken in master)
  • all boards use the new config style (array of structs), instead of defines
  • an I2C config was added to some nucleo/st disco boards provide (e.g: b-l475e-iot01a, nucleo-f722ze, ...)
  • nrf52xxdk I2C pin change to match Arduino pinout
Clone this wiki locally
You can’t perform that action at this time.