Language: EN

arduino-i2c

The I2C bus on Arduino

In this post we will see the I2C bus, one of the communication systems available in Arduino. In previous posts we have already seen the serial port and the SPI bus which, along with the I2C bus, integrate the main communication systems.

The I2C bus is interesting because, similarly to what happened with the SPI bus, a large number of devices have a connection via I2C, such as accelerometers, compasses, displays, etc.

The I2C bus

The I2C standard (Inter-Integrated Circuit) was developed by Philips in 1982 for the internal communication of electronic devices in their articles. Subsequently, it was progressively adopted by other manufacturers until it became a market standard.

I2C is also called TWI (Two Wired Interface) only for licensing reasons. However, the patent expired in 2006, so there is currently no restriction on the use of the term I2C.

The I2C bus requires only two wires for its operation, one for the clock signal (CLK) and the other for the data transmission (SDA), which is an advantage over the SPI bus. On the other hand, its operation is a bit more complex, as well as the electronics required to implement it.

arduino-bus-i2c-esquema

In the bus, each device has an address that is used to access the device individually. This address can be set by hardware (in which case, frequently, the last 3 bits can be modified by jumpers or switches) or entirely by software.

In general, each device connected to the bus must have a unique address. If we have several similar devices, we will have to change the address or, if that is not possible, implement a secondary bus.

The I2C bus has a master-slave architecture. The master device initiates communication with the slaves, and can send or receive data from the slaves. The slaves cannot initiate communication (the master has to ask them), nor can they talk to each other directly.

It is possible to have more than one master, but only one can be the master at a time. Changing the master involves high complexity, so it is not common.

The I2C bus is synchronous. The master provides a clock signal, which keeps all the devices on the bus synchronized. Thus, there is no need for each device to have its own clock, to agree on a transmission speed, and mechanisms to keep the transmission synchronized (as in UART).

The I2C protocol provides for Pull-UP resistors on the lines to Vcc. In Arduino, you will frequently see that these resistors are not installed, since the Wire library activates the internal Pull-UP resistors. However, the internal resistors have a value of between 20-30kOhms, so they are very weak Pull-UP resistors.

Using weak resistors implies that the rising edges of the signal will be slower, which means that lower speeds and shorter communication distances can be used. If higher transmission speeds or distances are to be used, Pull-UP resistors must be physically installed with a value between 1k and 4.7k.

Operation of the I2C bus

In order to carry out communication with only one data cable, the I2C bus uses a wide frame (the format of the sent data). The communication consists of:

  • 7 bits for the address of the slave device we want to communicate with.
  • One remaining bit indicates whether we want to send or receive information.
  • A validation bit
  • One or more bytes are the data sent or received from the slave.
  • A validation bit

arduino-bus-i2c-funcionamiento

With these 7 address bits, it is possible to access 112 devices on the same bus (16 addresses out of the 128 possible addresses are reserved for special uses).

This increase in the sent data (18 bits for every 8 bits of data) means that, in general, the speed of the I2C bus is reduced. The standard transmission speed is 100kHz, with a high-speed mode of 400kHz.

The I2C standard defines other operating modes such as sending 8,10 and 12-bit addresses, or transmission speeds of 1Mbit/s, 3.4Mbit/s and 5Mbit/s. They are not usually used in Arduino.

ADVANTAGES AND DISADVANTAGES OF I2C

Advantages

  • Requires few wires
  • Has mechanisms to verify that the signal has been received

Disadvantages

  • Its speed is medium-low
  • It is not full duplex
  • There is no verification that the message content is correct

The I2C bus on Arduino

Arduino has hardware I2C support physically linked to certain pins. It is also possible to use any other group of pins as an I2C bus through software, but in that case the speed will be much lower.

The pins associated with vary from one model to another. The following table shows the arrangement in some of the main models. For other models, consult the corresponding pinout diagram.

MODELSDASCK
UnoA4A5
NanoA4A5
Mini ProA4A5
Mega2021

To use the I2C bus on Arduino, the Standard IDE provides the “Wire.h” library, which contains the necessary functions to control the hardware.

Some of the basic functions are as follows

Wire.begin()  // Initializes the bus hardware
Wire.beginTransmission(address); // Begins transmission
Wire.endTransmission(); // Ends transmission
Wire.requestFrom(address,nBytes);  // Requests a number of bytes from the slave at the address
Wire.available();  // Detects if there is pending data to be read
Wire.write();  // Sends a byte
Wire.read();   // Receives a byte

Wire.onReceive(handler); // Registers a callback function when receiving data
Wire.onRequest(handler); // Registers a callback function when requesting data

There are other more advanced libraries than Wire.h to handle the I2C bus, such as I2Cdevlib or I2C library.

I2C Scanner

In an ideal world, we would know the address of the device we bought. But sometimes, especially when buying from Chinese sellers, the manufacturer does not provide us with the device address or even provides it incorrectly.

This is a common and not worrying situation. For this purpose, we have a sketch called “I2C Scanner” that performs a scan of all possible bus addresses, and displays the result if a device is found at the address.

This way we can comfortably determine the addresses of unknown devices.

The I2C scanner sketch is available at this link, or you can use the following reduced and translated version.

#include "Wire.h"

extern "C" { 
    #include "utility/twi.h"
}

void scanI2CBus(byte from_addr, byte to_addr, void(*callback)(byte address, byte result) ) 
{
  byte rc;
  byte data = 0;
  for( byte addr = from_addr; addr <= to_addr; addr++ ) {
    rc = twi_writeTo(addr, &data, 0, 1, 0);
    callback( addr, rc );
  }
}

void scanFunc( byte addr, byte result ) {
  Serial.print("addr: ");
  Serial.print(addr,DEC);
  Serial.print( (result==0) ? " Found!":"       ");
  Serial.print( (addr%4) ? "\t":"\n");
}

const byte start_address = 8;
const byte end_address = 119;

void setup()
{
    Wire.begin();

    Serial.begin(9600);
    Serial.print("Scanning I2C bus...");
    scanI2CBus( start_address, end_address, scanFunc );
    Serial.println("\nFinished");
}

void loop() 
{
    delay(1000);
}

In an upcoming post we will see how to connect two Arduino boards via the I2C bus, and how to connect an Arduino board with a Raspberry PI.

Download the code

All the code from this post is available for download on Github. github-full