Writing an OS Kernel from Scratch | I2C Bus — The Blood Vessels of Embedded Systems

Have you ever wondered: How does a gyroscope chip or touchscreen communicate with the main controller? They are not as complex as USB, nor do they need the massive bandwidth of PCIe — they use I2C.

I2C (Inter-Integrated Circuit) is the most common low-speed bus in embedded systems. It needs only two wires (SCL + SDA) to connect multiple devices. This article covers everything from protocol principles to Linux hands-on exercises, building your complete I2C debugging skills.


I. I2C Protocol: How Two Wires Transmit Data

I2C is a synchronous serial protocol using only two wires:

  • SCL (Serial Clock): Clock line, provided by the master
  • SDA (Serial Data): Data line, bidirectional half-duplex

Bash
         Master                           Slave 1          Slave 2
           │                                │                 │
  ─────────┼────────────────────────────────┼─────────────────┼───── SCL
           │                                │                 │
  ─────────┼────────────────────────────────┼─────────────────┼───── SDA
           │                                │                 │
       Start condition                      │                 │
       Write addr 0x68 (7-bit) + W(0)       │                 │
      ←─── ACK ────                         │                 │
       Write register addr 0x43             │                 │
      ←─── ACK ────                         │                 │
       Write data 0x00                      │                 │
      ←─── ACK ────                         │                 │
       Stop condition                       │                 │

Timing details:

  1. Start (S): SDA goes low while SCL is high
  2. Address + R/W: 7-bit address + 1-bit read/write (8 bits total)
  3. ACK: Slave pulls SDA low on the 9th clock to acknowledge
  4. Data: Each byte followed by ACK
  5. Stop (P): SDA goes high while SCL is high

I2C address is 7 bits, but shifted left 1 bit + R/W bit during transmission, so 8 bits are actually sent:

Bash
# MPU6050 gyroscope I2C address 0x68
# Write: addr << 1 + 0 = 0xD0
# Read:  addr << 1 + 1 = 0xD1

II. Master-Slave Architecture and Address Conflicts

I2C is a single-master, multi-slave bus. One master can drive multiple slave devices, each with a unique 7-bit address.

Common address rules:

  • Slave device addresses are fixed by the manufacturer or configured via pin strapping (high/low)
  • Some chips have two address options (different AD0 pin)
  • I2C addresses must not have duplicates (conflict)

Bash
# Scan all devices on the bus using i2cdetect on Linux
$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- 68 -- -- -- --
70: -- -- -- -- -- -- -- --

III. Linux I2C Debugging

Bash
# Common i2c-tools commands
$ i2cdetect -l              # List all I2C buses
$ i2cdump -y 1 0x68         # Dump all registers of device at 0x68
$ i2cget -y 1 0x68 0x75     # Read a single register (0x75 = WHO_AM_I)
$ i2cset -y 1 0x68 0x6B 0x00 # Write a value to a register

# Kernel I2C debug
$ cat /sys/bus/i2c/devices/i2c-1/name   # Bus name
$ ls /sys/bus/i2c/drivers/              # All registered I2C drivers

# Read MPU6050 WHO_AM_I (should return 0x68)
$ i2cget -y 1 0x68 0x75
0x68
Last modified: 2024年8月29日

Author

Comments

Write a Reply or Comment

Your email address will not be published.