I2C协议、SMbus协议、I2C-Tools工具

0 阅读8分钟

1. I2C框架与协议

1.1 硬件框架

1746464526040.png

  • 在一个芯片(SoC)内部,有一个或多个 I2C 控制器
  • 在一个 I2C 控制器上,可以连接一个或多个 I2C 设备
  • I2C 总线只需要 2 条线:时钟线 SCL、数据线 SDA
  • 在 I2C 总线的 SCL、 SDA 线上,都有上拉电阻

1.2 软件框架

1746517629052.jpg

以 I2C 接口的存储设备 AT24C02 为例:

1746465940819.png

1746466956801.png

如下图所示,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 子系统的一部分。

1746534575799.png

1.3 I2C协议

I2C 协议只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义。

传输数据的格式

(1)写操作

流程如下:
⚫ 主设备要发出一个 start 信号
⚫ 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写, 0 表示写, 1 表示读)
⚫ 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
⚫ 主设备发送一个字节数据给从设备,并等待回应
⚫ 每传输一字节数据,接收方要有一个回应信号(确定数据是否接收完成),然后再传输下一个数据。
⚫ 数据发送完之后,主设备就会发送一个停止信号
⚫ 下图:白色背景表示"主→从",灰色背景表示"从→主"

1746467830318.png

(2)读操作

流程如下:
⚫ 主设备要发出一个 start 信号
⚫ 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写, 0 表示写, 1 表示读)
⚫ 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
⚫ 从设备发送一个字节数据给主设备,并等待回应
⚫ 每传输一字节数据,接收方要有一个回应信号(确定数据是否接收完成),然后再传输下一个数据。
⚫ 数据发送完之后,主设备就会发送一个停止信号。
下图:白色背景表示"主→从",灰色背景表示"从→主"

1746467911971.png

(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 可以改变状态(为下一个数据位做准备)。

1746499417674.png

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源码中的实现不同数据格式的函数:

1746519301886.png

因为很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。即使 I2C 控制器没有实现 SMBus,软件方面也是可以使用 I2C 协议来模拟 SMBus。所以: Linux 建议优先使用 SMBus。

3. I2C系统的重要结构体

1746523633912.jpg

1746532608249.png

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个重要的成员

1746522515634.jpg

(2)i2c_algorithm结构体

1746523073885.jpg

3.2 怎么表示 I2C Device

(1)i2c_client结构体 1746524167248.jpg

3.3 怎么表示要传输的数据

(1)i2c_msg结构体

1746533735062.png 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检测

1746535843728.png

1746535759342.png

--表示没有该地址对应的设备, UU表示有该设备并且它已经有驱动程序, //数值表示有该设备但是没有对应的设备驱动.

4.2.2 i2cset: I2C写

1746539870211.png

4.2.3 i2cget: I2C读

1746539943915.png

4.2.4 i2ctransfer: I2C 传输(不是基于SMBus)

1746540358880.png

4.3 使用I2C-Tools操作传感器AP3216C

AP3216C 是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
⚫ 复位:往寄存器 0 写入 0x4
⚫ 使能:往寄存器 0 写入 0x3
⚫ 读光强:读寄存器 0xC、 0xD 得到 2 字节的光强
⚫ 读距离:读寄存器 0xE、 0xF 得到 2 字节的距离值

AP3216C 的设备地址是0x1E,假设节在I2C BUS0上 1746542438531.jpg

1746542995226.jpg

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 控制器下的设备;

1746543402983.png (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)