Using STM32 I2C with ChibiOS

The Inter-Integrated Circuit

The Inter-Integrated Circuit (often shored as I2C or I2C bus pronounced I-squared-C or alternatively I-two-C) is a widely used synchronous serial communication peripheral which communicates in half duplex mode using a multi-master-multi-slave architecture.

Like the SPI, the I2C is a Synchronous Serial bus, and the clock signal is generated by one of the endpoint and provided to the others through a specific Serial Clock Line often shorted as SCL by a party which is named Master.

In half-duplex buses, the communication happens on the same line no matter the direction. The I2C uses indeed a single data line known as Serial Data often shorted as SDA.

The slave selection happens sending additional data along with information and implementing a protocol: no additional wires are required and I2C is often called two-wire serial in contrast to the four-wire one (the SPI).

I2C highlights

The whole I2C communication happens on the two wire which can be driven by all the bus contenders. To avoid short circuits the I2C uses open drain lines with pull up resistor with typical voltages of 3.3 or 5 V: when bus is in idle condition both SDA and SCL assume an high logic value.

Each I2C transaction begins with a Start condition (SCL high and SDA moves from high to low) and ends with a Stop condition (SCL high and SDA moves from low to high). A transaction is composed by one or more messages where each message is 1 byte word (8-bit) plus an addition bit which has the purpose to guarantee synchronization between master and slave: Acknowledge/Not acknowledge (ACK/NACK) bit.

The I2C timing diagram

The communication cannot stop in the middle of the transaction: To prematurely abort the operation the receiver as to issue a NACK and after that the master has to issue the Stop Condition otherwise the bus will be in an undefined condition.

The first two word are used to establish the slave to which the communication is addressed, the type of communication and indirectly the number of messages of the transaction. Such addition data is usually know as protocol overhead. The first byte includes the Slave Address (shorted as SAD and having usually 7-bit lenght MSB bun in certain cases also 10-bit) and a read/write bit (R/W 1-bit LSB where read is 1 and write is 0). If slave is a device having registers, the second word is often used to issue which slave sub register the master wants to read/write (often shorted as SUB). At this point in case of read the slave will reply a certain number of messages and the master has to provide clock for each messages, in case of write the master will send a certain number of messages. Communication always ends with a stop condition.

While some of these stuff are well specified by I2C standard others depends on slave thus you should always refer to their user manual. Note that if a device is I2C compliant will most likely be aligned to I2C specification.

The communication baud rate is adjustable according to some well specified values:

  • 10 kbit/s in the Low-speed mode,
  • 100 kbit/s in the Standard mode,
  • 400 kbit/s in the Fast mode,
  • 1 Mbit/s in the Fast mode plus
  • 3.4 Mbit/s in the High Speed mode.

Anyway note that these values refers only to clock speed and thus does not take in account protocol overhead as well as clock stretching and other condition which makes the actual baud rate lower than the selected value.

The STM32 I2C

Across the various STM32 subfamilies there are three different hardware version of the I2C cell. We have already explained how to identify the driver version of your STM32 board here reporting them in a table. Anyway, as quick reference:

  • STM32F1xx, STM32F2xxSTM32F4xxSTM32L0xx and STM32L1xx use I2Cv1;
  • STM32F0xx, STM32F3xxSTM32F7xx and STM32L4xx use I2Cv2;
  • STM32H7xx uses I2Cv3.

The differences between various SPI versions are minimal and basically related to exposed configuration registers. STM32 allows:

  • both master and slave mode,
  • multi-master mode,
  • to configure the baud-rate as Standard mode, Fast mode or Fast mode plus (only I2Cv2 and I2Cv3),
  • to address slave in 7-bit or 10-bit mode (only I2Cv2 and I2Cv3),
  • to programmable clock stretching and hold/setup time (only I2Cv2 and I2Cv3).

Note that STM32 offers many I2C instances (from 2 in the small packages up to 4 in larger packages). I2C cells are identified with a progressive number (i.e. I2C 1, I2C 2, I2C 3 and so on). Note also that I2C bus relies on GPIO alternate functions to be able to get in touch with the outside world.

The ChibiOS I2C driver

The current ChibiOS I2C driver allows only master mode offering a simple interface to use I2C in synchronous way only.

Each API of the I2C Driver starts with the prefix “i2c”. Function names are camel-case, pre-processor constants uppercase and variables lowercase.

Different driver same approach

Comparing the I2C driver with previously presented SPI Driver or Serial Driver, we can notice that there are some certitudes in ChibiOS: one of these is for sure the consistency of design patterns. The I2C driver is organized like every other simple driver of ChibiOS\HAL:

  • The whole driver subsystem can be enabled disabled through the proper switch in the halconf.h. The switch name is HAL_USE_I2C.
  • To use the driver we have then to assign it a I2C peripheral acting on mcuconf.h. 
  • Assigning a peripheral a new object will become available: I2CD1 on I2C 1 assignation, I2CD2 on I2C 2 and so on.
  • Each I2CDx object represent a driver which implements a Finite State Machine.
  • A driver to be used shall be initialized but this is done automatically on halInit();
  • Each driver operation (e.g. a master transmit) can be done only if driver has been properly started. This requires a call to i2cStart().
  • The i2cStart() receives as usual two parameters: a pointer to the driver and a pointer to its configuration.
  • If the driver is not used it can be stopped through the i2cStop().

The following figure represent the state machine of the driver.

The I2C Driver state machine

Configuring the I2C

The I2C configuration structure has not some common fields but it depends entirely on hardware. The configuration is completely different between I2Cv1 and I2Cv2/I2Cv3 (which has the same configuration structure).

I2Cv1 configuration structure

The I2Cv1 is extremely simple and has basically three fields:

where op_mode represent the I2C mode which almost always we will configure as OPMODE_I2C and which available values are:

As side note the SMBUS is a lightweight communication bus derivative of I2C usually found in computer motherboards. It is used for communication with the power source transmitting ON/OFF instructions. In 99% cases we have to use complex devices which relies on I2C mode.

The second parameter is clock_speed and is a number representing the baudrate. Many devices communicates in standard mode (100 kbps i.e 100000) or fast mode (400 kbps i.e 400000).

The last parameter specify the clock duty cycle and can be

In Standard mode we should select STD_DUTY_CYCLE while in Fast mode FAST_DUTY_CYCLE_2.

Thus I2Cv1 configuration is trivial and as example this configuration is one of the most used dealing with MEMS when I am using STM32 Nucleo-64 F401 which is equipped with I2Cv1

I2Cv2/I2Cv3 configuration structure

The configuration structure of I2Cv2 and I2Cv3 is identical and exposes three registes

The first parameter is timingr which represent the value of the I2C Timing Register. It allows to apply some fine tuning to the clock speed, hold and setup time. Bit fields are described in manual and it offers also a series of formula to compute the high and low period as well as rising and falling time.

The second and third parameters cr1 and cr2 are the value of I2C Control Register 1 and I2C Control Register 2 which allows to configure some stuff but which are already internally addressed thus more often are left to 0

These register offer a lot of flexibility but are very difficult to be configured especially the first one. My suggestion is to refer to testhal demo for STM32 equipped with I2Cv2 (e.g STM32F3) to copy a working configuration an adopt it in your project: this would work almost always.

Communication API

As said the I2C communication from the master point of view can be synthesized in these steps:

  • The master send the first word with slave address and R/W bit
  • The master send the second word with the SUB he wants to read/write
  • The master send/receives n words.

This activity can be simply addressed with a single API

This API receives 7 parameters:

  • the pointer to the I2C driver which shall be already started
  • the SAD having 7-bit lenght non shifted (without the R/W)
  • a pointer to a transmission buffer (can be NULL)
  • the number of word to transmit (can be 0)
  • a pointer to a reception buffer (can be NULL)
  • the number of word to receive (can be 0)
  • an operation timeout expressed as interval useful to avoid stuck in this fuction

If we want to write only we can set rxbuf to NULL and rxbytes to 0. Similarly some slave devices have has not registers and can be read only. In this case after issuing the slave address and the read bit (first word) it will start to reply with words until the master does not issue the stop condition: in such case we could set txbuf to NULL and txbytes to 0, or alternatively use the function

Timeout can be disabled choosing TIME_INFINITE or using the macros

which actually are re-definitions of the previous presented functions with TIME_INFINITE timeout.

To conclude, note that these functions return a message which relies on ACK/NACK. This is interesting because in case of communication error software can take an action based on the error flag.

where available error flags are

Mutual exclusion

The driver offers also a couple of API to acquire and release the bus: i2cAcquireBus and i2cReleaseBus.

Such functions are useful when the same I2C driver is used from two threads because they guarantee that none will use that I2C driver until it is acquired avoiding misbehaviour due to concurrent access. Note that these API requires I2C_USE_MUTUAL_EXCLUSION is set to TRUE in the halconf.h file.

Further readings and Hands-on

We have already planned a collection of example and exercises for the I2C driver. If you are interested in follow us on Facebook to be updated on our articles. Anyway, at this moment you could refer to the I2C demo under testhal to give it a try.

Another interesting use of I2C is the HD44780 LDC with backpack:

You can also try the demo under testex for STM32 Nucleo with external x-Nucleo IKS01A1 or IKS01A2 which are expansion board equipped with MEMS which communicates over I2C.

Even STM32F3 Discovery which uses I2C to communicate with LSM303DLHC: this is a 3-axis accelerometer + 3-axis magnetometer connected to the MCU through the I2C. Basically the chip acts like two separate slaves having different registers and different Slave Address.

Previous and next

This article is part of a series of articles which are meant to be tutorials. I have composed them to be read in sequence. Here the previous and next article of this series:

Be the first to reply at Using STM32 I2C with ChibiOS

Leave a Reply