04、I2C 子系统框架(重点结构体)
一、Linux 对 IIC 的抽象
- i2c_adapter:
I2C硬件控制器,是管理整个I2C总线通信的硬件模块。 - i2c_algorithm:
一组具体函数,定义如何让特定芯片的I2C硬件模块生成I2C通信信号(如起始/停止信号、时钟脉冲等)。 - i2c_client:
代表挂在I2C总线上的某个设备,记录该设备的名称、I2C地址,以及它连接在哪个I2C控制器(adapter)上。 - i2c_driver:
设备驱动程序,负责与具体的I2C设备通信,实现对设备的控制和操作。
二、I2C 子系统框架
可以将上面这一 I2C 子系统划分为三个层次, 分别为:
用户空间
内核空间
- I2C 设备驱动层
- I2C 核心层
- I2C 适配器驱动层,
硬件层
2.1、I2C 设备驱动层
I2C设备驱动层的主要作用是编写驱动程序,让I2C外设能正常工作,并为上层应用提供统一的访问接口。具体分为四个关键部分:
设备信息对象(i2c_client)
- 表示I2C总线上的一个从设备
- 记录设备地址和所属总线的信息
设备节点(/dev/i2X)
- 为应用层提供访问设备的入口点
- 应用程序通过打开/读写/控制这些节点与设备通信,内核会自动处理具体操作
设备驱动(i2c_driver)
- 具体管理某个I2C设备
- 负责设备初始化、数据读写、参数配置等操作
- 通过i2c_client与硬件设备进行交互
总线管理系统
- 管理整个I2C总线资源
- 负责注册/注销适配器和设备
- 协调设备与驱动的配合
- 为上层提供统一的操作接口
整个架构就像这样:应用程序通过设备节点发出操作请求→总线系统将请求转交给对应的驱动→驱动通过设备信息对象与具体硬件通信,最终完成设备操作。这样既隔离了硬件细节,又让应用程序能方便地使用I2C设备。
2.2、I2C 适配器驱动层
I2C适配器驱动层是I2C系统的重要组成部分,主要负责具体硬件控制器的驱动开发。它的核心职责包括:
- 统一接口提供 - 为I2C核心模块提供标准化的数据传输方法,方便系统调用
- 通信控制 - 负责I2C总线的时序控制(如起始/停止信号)和数据收发操作
- 设备管理 - 管理总线上所有连接的从设备(如传感器、EEPROM等)
- 异常处理 - 监控总线状态并处理传输错误(如设备无响应、数据校验失败等情况)
简单来说,这个驱动就像总线的"交通指挥官",既确保数据能正确发送到目标设备,又能处理突发的通信问题,同时让整个系统能统一管理所有连接的设备。
2.3、I2C 核心层
I2C核心层是连接设备驱动和适配器驱动的中间桥梁,主要负责在两者之间传递数据。它提供了三个关键函数:
- 基础读写函数
i2c_master_send
:向I2C从设备发送数据i2c_master_recv
:从I2C从设备接收数据 这两个函数的参数很简单:- 设备指针(目标设备地址)
- 数据缓冲区(要发送的数据或接收数据的存储空间)
- 数据长度(传输的字节数)
它们会自动处理I2C协议的时序和数据封装,然后通过对应的适配器完成实际通信。
- 综合传输函数
i2c_transfer
是更底层的通信接口:
需要指定使用的I2C总线(适配器)
可发送多个连续操作(比如先写寄存器地址再读数据)
参数包括:
- 总线适配器指针
- 消息数组(每个消息包含设备地址、数据和传输方向)
- 消息数量
关键点说明:
- 前两个基础函数实际是通过调用
i2c_transfer
实现的 - 实验中我们会直接使用
i2c_transfer
,因为它能让我们完全控制每个传输步骤,从而深入理解I2C通信的底层细节
简单来说:基础函数适合简单读写,综合函数适合复杂操作或需要精确控制时序的场景。
三、重要结构体
3.1、i2c_adapter 控制器
- 如何标识I2C控制器?
- 一个芯片可能包含多个I2C控制器(如第0号、第1号等)
- 使用时只需要确定具体编号即可
在系统中:
通过i2c_adapter结构体表示一个I2C总线
这个结构体包含两个关键部分:
- nr:总线编号(对应控制器编号)
- i2c_algorithm:包含该总线的数据收发函数,用于执行I2C设备通信
3.2、怎么表示I2C Device:i2c_client
这个结构体是管理I2C设备信息的数据容器,它包含以下内容:
- 设备地址类型(如7位或10位地址)
- 设备具体的I2C地址
- 指向所属I2C总线控制器的指针
- 关联的驱动程序信息
- 基础设备类型的数据
- 连接驱动的链表节点
通过总线控制器指针和驱动指针这两个关键连接,这个结构体起到了桥梁作用,把物理总线控制器、设备驱动程序和具体硬件设备三者关联起来,形成完整的I2C设备管理系统。
C++
struct i2c_client {
unsigned short flags; /* 标志位, 用于各种配置和标识 */
unsigned short addr; /* 芯片地址 - 注意: 地址是 7 位的,存储在低 7 位中 */
char name[I2C_NAME_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter; /* 所在的 I2C 适配器 */
struct device dev; /* 设备结构体 */
int init_irq; /* 初始化时设置的中断号 */
int irq; /* 设备产生的中断号 */
struct list_head detected;/* 检测到的设备列表 */
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* 从模式下的回调函数 */
#endif
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
在编写 I2C client 代码时提到了两种方法,
- 第一种是在设备树中的 I2C 节点中追加对应的设备节点
- 第二种方法是编写 platform device C 程序
3.3、怎么表示要传输的数据:i2c_msg
- 在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg