【Linux驱动开发】Linux I2C 通信详解:从硬件到驱动再到应用

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 注册与匹配流程(主模式)

  1. SoC 初始化 → platform_driver 探测 → i2c_add_numbered_adapter()
  2. 设备树/ACPI 解析 → i2c_register_device() 创建 i2c_client
  3. 总线遍历:i2c_driver->id_table/of_match_tablei2c_client 匹配;
  4. 匹配成功 → 调用 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_msgI2C_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/control
    
    可打印每次 i2c_transfer 消息;
  • i2c-toolsi2ctransfer:支持一次命令多条消息,例如:
    i2ctransfer -y 0 w1@0x50 0x00 r2      # 写寄存器偏移,再读 2 字节
    
  • 逻辑分析仪:Saleae、DSLogic 抓 SDA/SCL 波形,确认 ACK/仲裁/时钟延展。

5. 实际应用与场景

外设类型典型地址常用命令/注意
24C02 EEPROM0x50–0x57页写 8 字节,tWR = 5 ms,需轮询 ACK
DS3231 RTC0x680x00–0x06 存放秒/分/时,ALM 中断需上拉
LM75 温度0x48–0x4F指针寄存器 0x00 = Temp,分辨率 0.125 °C
MPU6050 IMU0x68/0x69PWR_MGMT_1 = 0x00 退出休眠,量程寄存器
PMIC BD718370x4B多组 Buck/LDO,上电时序通过 I2C 配置
触控 FT62360x38中断脚 + 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-Ni2c-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 手册与驱动为准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值