Skip to content

I2C ​

https://www.arduino.cc/reference/en/language/functions/communication/wire/

https://docs.arduino.cc/learn/communication/wire/

Contents ​

  • Introduction
  • Bus topology and pull-ups
  • 7-bit addressing
  • Arduino Wire basics
  • Example 1 — bus scanner
  • Example 2 — read a register
  • Example 3 — write a register
  • Clock speed and long cables
  • Multi-master and error handling
  • I2C vs SMBus
  • Common mistakes
  • Related notes in this repo

Introduction ​

I2C (Inter-Integrated Circuit, often written I²C) is a two-wire bus: SDA (data) and SCL (clock). One or more master devices clock the bus; slave devices have fixed 7-bit (or 10-bit) addresses and respond when addressed.

Typical uses: IMUs, magnetometers, port expanders, OLED displays, EEPROM, RTC chips, and Pixhawk MAG ports (DF13 to I2C).

Unlike UART, many devices share the same two wires—each must have a unique address.

Bus topology and pull-ups ​

text
        +3.3V or +5V
           |
          [Rp]     [Rp]   <-- pull-ups (often 4.7k @ 5V, 2.2k–4.7k @ 3.3V)
           |       |
   SDA ----+---+---+---- SDA (all devices)
   SCL ----+---+---+---- SCL
           |   |   |
         Master Slave Slave ...
           |
          GND (common)
  • Open-drain lines: devices only pull low; pull-ups pull high.
  • Rp value depends on voltage, bus capacitance, and speed—follow sensor breakout recommendations.
  • Keep wires short for 400 kHz Fast Mode; use lower clock or I2C buffer for long runs.

7-bit addressing ​

Addresses are usually given in hex without the R/W bit:

Address (7-bit)Device (example)
0x48TMP102 temperature sensor
0x68MPU6050 / many IMUs
0x76 / 0x77BMP280 / BME280
0x3C / 0x3DSSD1306 OLED

The 8-bit value sent on the wire is (address << 1) | read_bit.

Address clashes: some breakouts expose an ADDR pin or solder jumper to shift address—check schematic.

Arduino Wire basics ​

Default pins (Uno / Nano):

SignalUno pin
SDAA4
SCLA5

On ESP32, default pins are configurable—see ESP32 I2C pins.

cpp
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin();           // master mode, default pins
  // Wire.begin(SDA, SCL); // ESP32 / custom pins
  Wire.setClock(400000);  // optional: 400 kHz Fast Mode
}

void loop() {
  // transfer functions here
}

Always consult the slave datasheet for register map, address, and whether registers are 8-bit or 16-bit indexed.

Example 1 — bus scanner ​

Finds devices that ACK an address probe:

cpp
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; }
  Wire.begin();
  Serial.println("I2C scanner");

  for (uint8_t addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    uint8_t err = Wire.endTransmission();
    if (err == 0) {
      Serial.print("Found 0x");
      if (addr < 16) Serial.print('0');
      Serial.println(addr, HEX);
    }
  }
  Serial.println("Done");
}

void loop() {}

endTransmission() return codes: 0 success, 1 data too long, 2 NACK on address, 3 NACK on data, 4 other error.

Example 2 — read a register ​

Many sensors expose a register pointer: write register address, then repeated start and read bytes.

cpp
// Read 2 bytes from register 0x00 at address 0x48 (example device)
uint8_t deviceAddr = 0x48;
uint8_t reg = 0x00;

Wire.beginTransmission(deviceAddr);
Wire.write(reg);
Wire.endTransmission(false);  // false = repeated START, don't release bus

Wire.requestFrom((int)deviceAddr, (int)2);
if (Wire.available() >= 2) {
  uint8_t msb = Wire.read();
  uint8_t lsb = Wire.read();
  int16_t raw = (int16_t)((msb << 8) | lsb);
  Serial.println(raw);
}

Pattern names vary: write pointer → read is universal; byte order (MSB first) is device-specific.

Example 3 — write a register ​

cpp
uint8_t deviceAddr = 0x20;  // PCF8574 port expander (example)
uint8_t reg = 0x00;
uint8_t value = 0xFF;

Wire.beginTransmission(deviceAddr);
Wire.write(reg);
Wire.write(value);
uint8_t err = Wire.endTransmission();
if (err != 0) {
  Serial.print("I2C write error ");
  Serial.println(err);
}

For devices without a register address (simple EEPROM-style), omit the register byte and stream data per datasheet.

Clock speed and long cables ​

ModeMax clock (typical)
Standard100 kHz
Fast400 kHz
Fast Mode Plus1 MHz

Reduce Wire.setClock(100000) when you see intermittent NACKs on breadboards or long harnesses.

Multi-master and error handling ​

Arduino Wire is usually single master. Multiple masters on one bus require arbitration support—avoid unless you know the hardware design.

On failure: log endTransmission() code, retry with backoff, and avoid tight infinite loops that block the rest of the robot stack.

I2C vs SMBus ​

SMBus is a subset/stricter cousin of I2C (timeouts, voltage levels). Many consumer sensors speak I2C-like protocols; follow timing in the datasheet if a “SMBus” label appears.

Common mistakes ​

  • Missing pull-ups — SDA/SCL float, random failures.
  • Wrong address — 8-bit address from datasheet used as 7-bit (shift right by one).
  • No repeated START — using endTransmission() then separate requestFrom() without false breaks some devices.
  • Reading without checking Wire.available() — stale or -1 data.
  • 5 V I2C to 3.3 V-only sensor — use level shifter or 3.3 V board.

Curated technical notes — open source on GitHub