It comes as no surprise that Inter Integrated Circuit (I2C) protocol tends to feature heavily in the projects I work with. I have recently used it to communicate with a range of devices from simple accelerometers and temperature sensors to more complex displays and image sensors.

I2C is popular as it allows us to easily communicate between several devices using only two wires (SDA — data and SCL clock). While a simple protocol, it does have a few gotchas that can slow down your system integration, which we are going to look at in this blog.

When we implement I2C (including Serial Camera Control Bus and Camera Control Interface) in our Zynq or Zynq MPSoC solutions, the easiest method is to use one of the Processing System(PS) I2C controller or an AXI I2C controller in the Programmable Logic (PL). Of course if we are using an FPGA, then we are limited to either writing our own or using the AXI I2C controller.

Regardless of either SoC or FPGA approach, unless I am sending more than just a few bytes of data I prefer to use SW to define and send the I2C commands. If we are using an SoC, the overhead is fairly minor; however, if we are using an FPGA, then we need to implement a soft core such as an Arm M1/M3 or MicroBlaze. Although, chances are there are other elements of the system design we can use it for as well for example UART or SPI communications or AXI Lite configuration.

I find this approach offers a number of benefits, including flexibility and easier debugging and a reduction in time between iterations if issues are raised. This is especially true when we are configuring image sensors or displays and the number of registers to be written is significant and might not be right the first time!

I2C is an open drain, meaning that our SoC/FPGA driver pulls down the line for a logic zero. However, when driving a logic one the output goes high impedance, enabling external pull ups to pull the line high.

These pull ups can be either external resistors, or we can use the internal pull ups in the device IO structure. Before we decide to use the internal pull up we need to ensure the resistance is acceptable. The value of pull up to use, needs to be determined and not just selected due rule of thumb. There is a very good reason we need to correctly size the resistor

If the value of resistor used it too low, then it is possible the I2C device attempting to pull the bus low will not be able to pull the bus voltage below Vil Minimum leading to incorrect operation.

Conversely, if the value of resistor is too high, this coupled with the bus capacitance will result in the I2C bus not being able to meet it timing requirements.

We can calculate the minimum resistance using the equation:

Where Vcc is the voltage the bus is pulled to, Vol(max) is the maximum output low voltage and Iol is the maximum current that be sunk but the IO. These values will be available in the device data sheet.

While the maximum resistance can be calculated using the equation:

Where tr is the maximum rise time, for the respective standard, fast and fast plus modes this is 1000 ns, 300 ns and 120 ns.

For our SoC/FPGA implementation, the value of the internal pull up will vary depending upon the Vcco of the IO bank we are using and Irpu (max). This value is available from the specific device AC and DC data sheet.

Zynq 7000 AC and DC data sheet showing IRpu for different Vcco

As can be seen in the snippet above from the Zynq data sheet, the value of pull up varies between 10K and 8.2K.

I2C addressing uses 7 bits; however, many I2C data sheets specify 8-bit addresses, which includes the Read/Write bit.

If we do not understand the translation between these 7- and 8-bit addresses, we will experience addressing issues because the Read/Write bit is set or cleared automatically by the PS or AXI I2C controller.

The picture below shows the conversion from 8-bit to 7-bit format. The simplest method is to shift the 8-bit address one place to the right.

It is worth remembering that valid I2C addresses are between 0x08 and 0x77.

It is also good practice to check the devices on the bus all use unique addresses, especially if there are multiple of the same devices on the bus.

If the I2C bus has multiple masters on it, we need to be careful during the read operation that another master does not grab the bus and interrupt the read sequence.

Many I2C devices contain multiple registers, as such before the master can read the register contents it must first identify the register it wishes to read. To do this, the master identifies the target register by first performing a I2C write to the slave with the write data containing the register address.

Once the write has been accepted, the master then proceeds with issuing the I2C read command. However, in a multi-master environment there is a chance that between the write and the read another master will grab the bus for its transactions when the bus state goes to idle.

I2C read restart

To prevent this from happening, between the register write and the read command the current master can issue the restart command.

This enables the current master to keep the bus and then be able to send the follow up read command.

The Input Is Always Listening

Generally in our SoC/FPGA implementations, the IO structure will use an tri-state output. The output of which will be used to either pull the bus low, or allow the pull ups to take the bus high (provided there is no contention)

Common tristate IO structure

But, the input buffer in the IO structure will always be listening to the line. As such, we still need to ensure the voltage the I2C bus is pulled up to is still within the maximum acceptable voltage range for the SoC/FPGA IO. Failure to do this will lead to the IO being damaged, of course this can also happen when we transmit, but the idle state of the line will do the damage first.

At times we need to debug our I2C interface to see what is happening on the physical bus, especially if we are experiencing issues related to the correct sizing of the pull up resistors.

In this instance, an oscilloscope can be very useful, even more so if it is capable of decoding the I2C waveform as well. This lets us examine not only the electrical signal but the protocol as well.

I2C register read

There are a range of scopes available, ranging from the analogue discovery to the PicoScopes and beyond. The choice depends upon your requirements and budget.

Following these has generally helped me bring up the system pretty painlessly.

See My FPGA / SoC Projects: Adam Taylor on Hackster.io

Get the Code: ATaylorCEngFIET (Adam Taylor)

Access the MicroZed Chronicles Archives with over 250 articles on the Zynq / Zynq MpSoC updated weekly at MicroZed Chronicles.