1. I2C框架与协议
1.1 硬件框架
- 在一个芯片(SoC)内部,有一个或多个 I2C 控制器
- 在一个 I2C 控制器上,可以连接一个或多个 I2C 设备
- I2C 总线只需要 2 条线:时钟线 SCL、数据线 SDA
- 在 I2C 总线的 SCL、 SDA 线上,都有上拉电阻
1.2 软件框架
以 I2C 接口的存储设备 AT24C02 为例:
如下图所示,APP 可以通过两类驱动程序访问设备:
◼ I2C 设备自己的驱动程序:针对特定 I2C 设备(如传感器、EEPROM)开发的内核驱动模块
◼ Linux内核自带的 i2c-dev.c 驱动程序,它是 i2c 控制器驱动程序暴露给用户空间的驱动程序(i2c-dev.c)
两类驱动程序的区别
特性 | I2C设备自己的驱动程序 | 内核自带的i2c-dev.c 驱动程序 |
---|---|---|
驱动类型 | 直接管理具体硬件设备(如传感器、EEPROM) | 提供用户空间直接访问 I2C 总线的通用接口 |
设备节点 | 自动创建(如 /dev/ap3216c ) | 手动操作(如 /dev/i2c-1 ) |
硬件控制 | 内核直接处理通信协议(如寄存器读写) | 用户空间程序需自行实现协议(如 ioctl + read/write ) |
开发复杂度 | 需编写内核模块,难度高 | 用户空间开发,难度低 |
适用场景 | 标准化设备(如传感器、RTC)的自动化管理 | 调试、自定义协议设备、快速原型开发 |
代码位置 | 内核模块(分散在 drivers/i2c/ 的各个子目录中) | 内核自带(固定路径 drivers/i2c/i2c-dev.c ) |
(1)内核模块:
drivers/i2c/...
是 Linux 内核源码树中 I2C 子系统相关代码的根目录
drivers/i2c/algos
:I2C 总线算法实现(如软件模拟 I2C)。drivers/i2c/busses
:具体 I2C 总线控制器驱动(如 SoC 内置的 I2C 控制器)。drivers/i2c/chips
:特定 I2C 设备的驱动(如传感器、EEPROM)。
(2)内核自带:
drivers/i2c/i2c-dev.c
是Linux内核源码中 通用 I2C 用户空间接口的实现文件,属于 I2C 子系统的一部分。
1.3 I2C协议
I2C 协议只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义。
传输数据的格式
(1)写操作
流程如下:
⚫ 主设备要发出一个 start 信号
⚫ 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写, 0 表示写, 1 表示读)
⚫ 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
⚫ 主设备发送一个字节数据给从设备,并等待回应
⚫ 每传输一字节数据,接收方要有一个回应信号(确定数据是否接收完成),然后再传输下一个数据。
⚫ 数据发送完之后,主设备就会发送一个停止信号。
⚫ 下图:白色背景表示"主→从",灰色背景表示"从→主"
(2)读操作
流程如下:
⚫ 主设备要发出一个 start 信号
⚫ 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写, 0 表示写, 1 表示读)
⚫ 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
⚫ 从设备发送一个字节数据给主设备,并等待回应
⚫ 每传输一字节数据,接收方要有一个回应信号(确定数据是否接收完成),然后再传输下一个数据。
⚫ 数据发送完之后,主设备就会发送一个停止信号。
下图:白色背景表示"主→从",灰色背景表示"从→主"
(3)I2C 信号
I2C 协议中数据传输的单位是字节,也就是 8 位。但是要用到 9 个时钟:前面 8 个时钟用来传输 8 数据,第 9 个时钟用来传输回应信号。传输时,先传输最高位(MSB)。
⚫ 开始信号( S): SCL(时钟线) 为高电平时, SDA由高电平向低电平跳变,开始传送数据。
⚫ 结束信号( P): SCL 为高电平时, SDA (数据线) 由低电平向高电平跳变,结束传送数据。
⚫ 响应信号(ACK):接收器在接收到 8 位数据后,在第 9 个时钟周期,拉低 SDA
⚫ SDA 上传输的数据必须在 SCL 为高电平期间保持稳定, SDA 上的数据只能在SCL 为低电平期间变化。
- SCL 高电平期间:读取 SDA 的电平状态,SDA 必须保持稳定(不可变化),否则可能导致数据误判。
- SCL 上升沿:锁存 SDA 的当前值(即读取数据)
- SCL 低电平期间:SDA 可以改变状态(为下一个数据位做准备)。
2. SMBus协议
SMBus 协议定义了几种数据格式
2.1 SMBus 是 I2C 协议的一个子集
SMBus: System Management Bus,系统管理总线。SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系 统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。SMBus 是基于 I2C 协议的, SMBus 要求更严格, SMBus 是 I2C 协议的子集。
(1)SMBus 有哪些更严格的要求?跟一般的 I2C 协议有哪些差别?
- VDD 的极限值不一样:I2C 协议范围很广;SMBus为1.8V~5V
- 最小时钟频率:I2C时钟频率最小值无限制;SMBus最小值是10KHz
- 最大的Clock Stretching:I2C没限制;SMBus有限制
- 地址回应(Address Acknowledge):I2C没有强制要求必须发出回应信号;SMBus强制要求
- 传输格式:I2C只定义了怎么传输数据,但是并没有定义数据的格式;SMBus定义了几种数据格式
- 重复发出开始信号:在写、读之间,可以不发出结束信号,而是直接发出开始信号
2.2 SMBus协议分析
I2C-tools源码中的实现不同数据格式的函数:
因为很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。即使 I2C 控制器没有实现 SMBus,软件方面也是可以使用 I2C 协议来模拟 SMBus。所以: Linux 建议优先使用 SMBus。
3. I2C系统的重要结构体
12c transfer函数会从adapter里面找到master xfer传输函数,通过这个函数把一个或者多个i2c msg传给设备,设备地址在i2c_msg里面包含有,所以这个函数不需要i2c_client
3.1 怎么表示 I2C Controller
(1)i2c_adapter结构体
使用 i2c_adapter 表示一个 I2C BUS,或称为 I2C Controller, 里面有2个重要的成员
(2)i2c_algorithm结构体
3.2 怎么表示 I2C Device
(1)i2c_client结构体
3.3 怎么表示要传输的数据
(1)i2c_msg结构体
i2c_msg 中的 flags 用来表示传输方向: bit 0 等于 I2C_M_RD 表示读, bit 0 等于 0 表示写
4. I2C-Tools
i2c-tools
是基于 i2c-dev.c
驱动开发的用户空间工具集,是一套好用的工具,也是一套示例代码
- i2c-dev.c 是“桥梁”,连接用户程序和物理 I2C 总线。
- i2c-tools 是“工具箱”,通过这座桥梁实现快速调试。
4.1 交叉编译、执行make
在《编写APP操作AP3216C》文章中已实现。
4.2 i2c-tool用法
4.2.1 i2cdetect: I2C检测
--
表示没有该地址对应的设备, UU
表示有该设备并且它已经有驱动程序, //
数值表示有该设备但是没有对应的设备驱动.
4.2.2 i2cset: I2C写
4.2.3 i2cget: I2C读
4.2.4 i2ctransfer: I2C 传输(不是基于SMBus)
4.3 使用I2C-Tools操作传感器AP3216C
AP3216C 是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
⚫ 复位:往寄存器 0 写入 0x4
⚫ 使能:往寄存器 0 写入 0x3
⚫ 读光强:读寄存器 0xC、 0xD 得到 2 字节的光强
⚫ 读距离:读寄存器 0xE、 0xF 得到 2 字节的距离值
AP3216C 的设备地址是0x1E,假设节在I2C BUS0上
4.4 I2C-Tools访问I2C设备的2种方式
两种方式:基于i2c和基于SMBus。
(1)怎么指定 I2C 控制器?
⚫ i2c-dev.c 为每个 I2C 控制器(I2C Bus、 I2C Adapter)都生成一个设备节点: /dev/i2c-0、 /dev/i2c-1 等等;
⚫ open 某个/dev/i2c-X 节点,就是去访问该 I2C 控制器下的设备;
(2)怎么指定 I2C 设备?
通过 ioctl 指定 I2C 设备的地址
⚫ ioctl(file, I2C_SLAVE, address)
如果该设备已经有了对应的设备驱动程序,则返回失败。
⚫ ioctl(file, I2C_SLAVE_FORCE, address)
如果该设备已经有了对应的设备驱动程序但是还是想通过 i2c-dev 驱动来访问它,则使用这个 ioctl 来指定 I2C 设备地址。
(3)怎么传输数据?
两种方式
⚫ 一般的 I2C 方式: ioctl(file, I2C_RDWR, &rdwr)
⚫ SMBus 方式: ioctl(file, I2C_SMBUS, &args)