【Linux驱动开发】Linux I2C 通信详解:从硬件到驱动再到应用
Linux I2C 通信详解:从硬件到驱动再到应用
I²C(Inter-Integrated Circuit,简称 I2C)是 Philips(现 NXP)于 1982 年推出的两线式同步串行总线,仅需 SDA(串行数据)与 SCL(串行时钟)即可实现一主多从、多主多从通信,广泛用于板级低速外设(EEPROM、RTC、温度传感器、PMIC、显示屏控制器等)。Linux 将其抽象为 I2C Core 子系统,提供统一的总线/设备/驱动模型,并支持用户态字符设备、SMBus、PMBus 等高层协议。本文从硬件原理、协议时序、底层驱动框架、内核与用户态接口、实际应用到调试方法,系统梳理 Linux I2C 通信全景。
1. 硬件原理
1.1 物理层
- 信号线:
- SCL:时钟,主设备驱动,开漏/集电极输出;
- SDA:数据,双向,开漏输出;
- 上拉电阻:典型 1.8 kΩ–10 kΩ,决定上升时间与最大速度;
- 电平标准:与 VDD 相同(1.8 V/3.3 V/5 V),允许不同电压域通过电平转换芯片(PCA9306、TXS0102 等)互联;
- 拓扑:总线型,可并联多个主/从设备,地址 7 位(0–127)或 10 位扩展;
- 速度模式:
模式 比特率 备注 标准 (Sm) ≤100 kHz 最通用,上拉 4.7 kΩ@5 V 快速 (Fm) ≤400 kHz 上拉 2.2 kΩ@3.3 V 快速+ (Fm+) ≤1 MHz 驱动能力 20 mA,更陡斜率 高速 (Hs) ≤3.4 MHz 需要电流源主设备,特殊时序 超快 (UFm) ≤5 MHz 单向,仅用于 LED 驱动等
1.2 开漏输出与“线与”
- SDA/SCL 引脚配置为开漏:输出低 = 下拉,输出高 = 高阻,由上拉电阻拉至高;
- 多主仲裁:谁先发低谁赢得总线,实现“线与”逻辑;
- 热插拔安全:无电源设备不会驱动高电平,避免冲突。
1.3 I2C 控制器框图

- 时钟生成:主模式时分频 APB 时钟产生 SCL;从模式检测 SCL 同步;
- 移位寄存器:SDA 串行 ⇄ 8 位并行;
- 地址比较器:硬件过滤匹配自身从地址,可支持双地址+广播;
- DMA 接口:TX/RX FIFO 到内存,减少 CPU 介入;
- 中断事件:START/STOP 检测、地址匹配、字节完成、仲裁丢失、总线错误 (BUSERR);
- 滤波/斜率控制:抑制 50 ns 以下毛刺,满足快速模式要求。
2. 协议时序与帧格式
I2C 是同步多主总线,靠 SCL 上升沿采样 SDA,协议元素:

| 阶段 | 描述 |
|---|---|
| 总线空闲 | SDA = SCL = 高; |
| START 条件 | SDA 先降后 SCL 降,主设备抢占总线; |
| 地址帧 | 7 位地址 + 1 位 R/W#(0=写 1=读);从设备在第 9 时钟拉低 SDA 作为 ACK; |
| 数据帧 | 每字节 8 位,MSB 先出;每字节后跟随 1 位 ACK/NACK; |
| 重启动 (Sr) | 不释放总线,再次发送 START 切换方向(常用于读 EEPROM); |
| STOP 条件 | SCL 先升后 SDA 升,释放总线; |
| 仲裁/时钟同步 | 多主同时拉低时“线与”决定胜出;时钟延展 (clock stretching) 允许从设备拉低 SCL 等待。 |
2.1 读写组合示例
- 写 1 字节:[S] [Addr+W] [ACK] [Data] [ACK] [P]
- 读 1 字节:[S] [Addr+W] [ACK] [Sr] [Addr+R] [ACK] [Data] [NACK] [P]
- 页写 EEPROM:连续发送 ≤页大小字节,最后 STOP 触发内部写周期 (tWR ≈ 5 ms)。
2.2 SMBus 与 PMBus 扩展
- SMBus:超时 35 ms、最低速 10 kHz、块读/写、PEC 校验;
- PMBus:基于 SMBus,定义电源管理命令码;Linux
i2c-smbus提供i2c_smbus_read_word_data()等 API。
3. Linux I2C 驱动框架
Linux 采用“总线-设备-驱动”模型,核心文件:drivers/i2c/i2c-core.c

3.1 关键数据结构
| 结构体 | 作用 |
|---|---|
struct i2c_adapter | 代表一条 I2C 总线(控制器),提供算法 (algo) 访问硬件; |
struct i2c_algorithm | 函数指针集:master_xfer()(主模式)、smbus_xfer()、` |
| functionality()`; | |
struct i2c_client | 代表挂在总线上的从设备,含地址、设备树句柄、驱动指针; |
struct i2c_driver | 从设备驱动,匹配 i2c_client,提供 probe/remove/suspend/resume; |
struct i2c_board_info | 静态表或设备树描述从设备信息; |
struct i2c_msg | 一次传输消息:地址、标志(读/写)、长度、缓冲区; |
3.2 注册与匹配流程(主模式)
- SoC 初始化 →
platform_driver探测 →i2c_add_numbered_adapter(); - 设备树/ACPI 解析 →
i2c_register_device()创建i2c_client; - 总线遍历:
i2c_driver->id_table/of_match_table与i2c_client匹配; - 匹配成功 → 调用
i2c_driver->probe(),驱动内部i2c_transfer()读写寄存器。
3.3 传输 API
- 最底层:
i2c_transfer(adapter, msgs, num)→ 调用algo->master_xfer; - SMBus 封装:
i2c_smbus_read_byte_data(client, reg); - DMA 友好:
i2c_msg用I2C_M_RD标志区分读/写,支持散聚。
3.4 从设备驱动示例(精简)
#include <linux/module.h>
#include <linux/i2c.h>static const struct i2c_device_id my_ids[] = {{ "my_sensor", 0 },{ }
};
static int my_probe(struct i2c_client *client, const struct i2c_device_id *id)
{u8 reg = 0x00;u8 val;i2c_smbus_read_byte_data(client, reg); // 读寄存器return 0;
}
static struct i2c_driver my_driver = {.driver = { .name = "my_sensor", },.id_table = my_ids,.probe = my_probe,
};
module_i2c_driver(my_driver);
MODULE_DESCRIPTION("I2C 从设备驱动模板");
3.5 GPIO 模拟 I2C (i2c-gpio)
- 无硬件控制器时,用两条 GPIO 位 bang 实现;
- 通过
i2c-gpio平台驱动注册i2c_adapter,算法为bit_xfer; - 速度受 GPIO 翻转延迟限制,通常 <100 kHz。
4. 用户态访问接口
4.1 字符设备 /dev/i2c-N
- 内核配置
CONFIG_I2C_CHARDEV; - 通过
ioctl进行读写:
int file = open("/dev/i2c-0", O_RDWR);
ioctl(file, I2C_SLAVE, 0x50); // 设置从地址
write(file, buf, 1); // 写寄存器地址
read(file, buf, 2); // 读数据
- 工具:
i2cdetect -y 0扫描地址,i2cget -y 0 0x50 0x00读字节,i2cset写字节,i2cdump看整段寄存器。
4.2 sysfs 接口(已逐步淘汰)
/sys/bus/i2c/devices/0-0050/下提供name/uevent;新芯片建议用hwmon/rtc等子系统属性。
4.3 调试与跟踪
dynamic_debug:
可打印每次echo 'file i2c-core.c +p' > /sys/kernel/debug/dynamic_debug/controli2c_transfer消息;i2c-tools的i2ctransfer:支持一次命令多条消息,例如:i2ctransfer -y 0 w1@0x50 0x00 r2 # 写寄存器偏移,再读 2 字节- 逻辑分析仪:Saleae、DSLogic 抓 SDA/SCL 波形,确认 ACK/仲裁/时钟延展。
5. 实际应用与场景
| 外设类型 | 典型地址 | 常用命令/注意 |
|---|---|---|
| 24C02 EEPROM | 0x50–0x57 | 页写 8 字节,tWR = 5 ms,需轮询 ACK |
| DS3231 RTC | 0x68 | 0x00–0x06 存放秒/分/时,ALM 中断需上拉 |
| LM75 温度 | 0x48–0x4F | 指针寄存器 0x00 = Temp,分辨率 0.125 °C |
| MPU6050 IMU | 0x68/0x69 | PWR_MGMT_1 = 0x00 退出休眠,量程寄存器 |
| PMIC BD71837 | 0x4B | 多组 Buck/LDO,上电时序通过 I2C 配置 |
| 触控 FT6236 | 0x38 | 中断脚 + RESET 脚,需时钟延展支持 |
5.1 多主与热插拔
- 同一总线可存在 CPU + PMIC 主设备,需仲裁;
- 热插拔 O(∩_∩)O:使用零欧姆电阻/模拟开关隔离,或采用 I2C 热插拔缓冲器 (PCA9515/PCA9516)。
5.2 电平转换与隔离
- 1.8 V ⇄ 3.3 V:PCA9306 双电压转换器,VREF1/VREF2 各接上拉;
- 隔离:ISO1540 磁耦,支持 1 MHz,耐压 2.5 kV。
6. 故障排查与性能优化
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
i2cdetect 无应答 | 上拉缺失/地址错误/芯片未供电 | 万用表量 SDA/SCL 高电平 ≈ VDD;示波器看是否有 START |
| 随机 NACK | 从设备忙 (EEPROM 写周期) | 加入 retry 或等待 tWR;用 i2c_smbus_read_byte_data() 自带重试 |
| 波形过冲 | 上拉太小/线太长 | 加大上拉至 4.7 kΩ,串联 22 Ω 阻尼电阻,降速到 100 kHz |
| 仲裁丢失 | 多主冲突 | 使能 I2C_M_RECV_LEN 动态长度,检查另一主设备访问 |
| 时钟延展过长 | 从设备 BUG | 限制总线时钟延展阈值,或换用高速模式 + 电流源主 |
6.1 吞吐与延迟优化
- 高速模式:主设备支持 3.4 MHz,用电流源上拉,缩短上升时间;
- 大包 DMA:
i2c_msg长度 > 32 字节时,驱动启用 DMA,减少中断; - 合并消息:一次
i2c_transfer携带写寄存器+读数据,减少 START/STOP 开销; - 拉高优先级:将 I2C 控制器中断绑到实时核,PREEMPT_RT 下抖动 < 200 µs。
7. 小结
I2C 以“两线+上拉+线与仲裁”实现极简总线,却覆盖从 EEPROM、传感器到电源管理的广泛应用。Linux 通过“adapter-client-driver”模型把控制器与从设备解耦,统一使用 i2c_transfer/smbus API;用户态则通过 /dev/i2c-N 和 i2c-tools 快速验证外设。掌握 START/STOP/ACK/时钟延展/仲裁的时序细节,再配合上拉选型、速度模式、DMA 与逻辑分析仪,可在嵌入式、工控、物联网场景中快速定位 NACK、仲裁丢失、写周期延迟等问题,实现稳定高速的多节点通信。
附录:参考文档
- Linux Kernel:
Documentation/i2c/,drivers/i2c/i2c-core.c,i2c-dev.c - NXP I2C-Bus Specification Rev 7:UM10204
- SMBus Specification 3.2:SBS Forum
- 芯片手册:PCA9306、ISO1540、24C02、MPU6050、BD71837
- 书籍:《Linux 设备驱动开发详解》《嵌入式 Linux 开发教程》
本文档示例基于 Linux 4.4.94,不同版本内核接口基本一致,高速/多主/热插拔细节请以实际 SoC 手册与驱动为准。
