The I2C Bus on Arduino

Learn about the I2C bus communication system used in Arduino projects

What is I2C?

The I2C bus is a popular communication protocol used for connecting multiple devices over two wires: the clock line (SCL) and the data line (SDA). It allows devices such as accelerometers, sensors, and displays to communicate with microcontrollers like Arduino.

I2C is also known as TWI (Two-Wire Interface), but the name I2C is more commonly used after the patent expired in 2006.

How the I2C Bus Works

The I2C bus uses a master-slave architecture. The master device controls the communication and sends clock signals, while the slave devices follow the master's instructions. Each device on the bus has a unique address, which allows communication to be directed to specific devices.

I2C Bus Schematic

The I2C bus operates using a simple format, with data being sent in frames that include the address of the slave, a read/write flag, data bytes, and validation bits.

Advantages and Disadvantages of I2C

Advantages

  • Requires only two wires
  • Supports multiple devices on a single bus
  • Has built-in error checking mechanisms

Disadvantages

  • Medium-low communication speed
  • It is not full duplex
  • There’s no built-in message validation

Using I2C in Arduino

Arduino supports I2C communication natively through the `Wire` library. The I2C pins are generally mapped to the following pins on Arduino models:

Model SDA SCK
Uno A4 A5
Nano A4 A5
Mega 20 21

The `Wire.h` library provides functions like `Wire.begin()`, `Wire.beginTransmission()`, `Wire.endTransmission()`, `Wire.requestFrom()`, `Wire.write()`, and `Wire.read()` to manage I2C communication.

Finding I2C Device Addresses with I2C Scanner

Sometimes, especially when buying from third-party vendors, the I2C address of a device may not be provided or may be incorrect. In this case, you can use an I2C scanner script to detect the address of the connected device.


#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);
}