Linux内核I2C驱动编程框架
·
1. Linux内核I2C驱动编程框架
1.1 I2C总线协议回顾
I2C总线基本概念
- 定义:七个关键特性 - SCL/SDA/串行/并行/总线/上拉电阻
- 设备寻址:CPU通过设备地址找到外设
- 数据传输:通过特定的时序完成单字节/多字节寄存器读写
- 信号配合:SCL和SDA遵循"低放高取"原则
MMA8653三轴加速度传感器示例
- 设备地址识别
- 寄存器读写时序
- 数据传输协议
1.2 Linux内核I2C驱动开发方法
传统方法
- 移植ARM裸板I2C程序
- 使用ioremap映射物理地址
- 封装read/write/ioctl接口
传统方法弊端
- 代码工作量大
- 需要操作I2C控制器寄存器
- 需要关心外设寄存器信息
- 开发效率低
1.3 Linux内核I2C驱动分层框架
分层架构
-
I2C总线驱动
- 操作I2C控制器硬件
- 负责时序产生
- 由芯片厂家提供
-
I2C设备驱动
- 关心外设信息(设备地址、寄存器地址、数据)
- 提供应用层接口
- 由驱动工程师编写
-
SMBUS接口层
- 连接总线驱动和设备驱动
- 提供标准API接口
- 由Linux内核提供
配置I2C总线驱动
cd /opt/kernel
make menuconfig
# 路径:Device Drivers -> I2C support -> I2C Hardware Bus support
# 选择:<*> Slsiap I2C (S5P6818 I2C控制器驱动)
1.4 Linux内核I2C驱动分层示例
案例:读取MMA8653设备ID
- 设备ID:0x5A
- 寄存器地址:0x0D
应用层代码
#define MMA_READ 0x100001
#define MMA_WRITE 0x100002
struct mma {
unsigned char addr; // 片内寄存器地址
unsigned char data; // 寄存器数据
};
struct mma mma;
mma.addr = 0x0D; // 指定寄存器地址
ioctl(fd, MMA_READ, &mma);
printf("ID=%#x\n", mma.data); // 输出:ID=0x5A
设备驱动层
long mma_ioctl(file, cmd, unsigned long buf) {
struct mma kmma;
copy_from_user(&kmma, (struct mma *)buf, sizeof(kmma));
// 调用SMBUS接口读取数据
kmma.data = i2c_smbus_read_byte_data(client, kmma.addr);
copy_to_user((struct mma *)buf, &kmma, sizeof(kmma));
return 0;
}
数据传输流程
- 应用层发起读请求
- 设备驱动调用SMBUS接口
- SMBUS层调用总线驱动
- 总线驱动产生硬件时序
- 数据逐层返回
1.5 I2C设备驱动开发模型
设备-总线-驱动模型
- 虚拟总线:i2c_bus_type
- 设备链表:维护硬件信息节点
- 驱动链表:维护软件信息节点
匹配机制
- 硬件节点:struct i2c_client
- 软件节点:struct i2c_driver
- 匹配函数:比较i2c_client.name和i2c_driver.id_table.name
- 匹配成功:自动调用probe函数
1.6 详解struct i2c_client
结构体定义
struct i2c_client {
unsigned short addr; // 设备地址
char name[I2C_NAME_SIZE]; // 设备名称(用于匹配)
struct device dev; // 设备结构
int irq; // 中断号
// ...
};
硬件信息注册
struct i2c_board_info {
char type[I2C_NAME_SIZE]; // 设备类型(用于匹配)
unsigned short addr; // 设备地址
void *platform_data; // 自定义硬件信息
int irq; // 中断号
};
// 注册函数
int i2c_register_board_info(int busnum,
struct i2c_board_info const *info,
unsigned n);
MMA8653硬件信息注册示例
#include <linux/i2c.h>
static struct i2c_board_info mma8653 = {
.type = "mma8653", // 匹配名称
.addr = 0x1D, // 设备地址
};
// 在初始化函数中注册
i2c_register_board_info(2, &mma8653, 1); // 总线编号2
1.7 详解struct i2c_driver
结构体定义
struct i2c_driver {
struct device_driver driver; // 驱动信息
const struct i2c_device_id *id_table; // 设备ID表(用于匹配)
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
// ...
};
关键成员说明
- id_table:设备匹配表
- probe:匹配成功回调函数
- remove:驱动卸载回调函数
配套函数
i2c_add_driver(&driver); // 注册驱动
i2c_del_driver(&driver); // 卸载驱动
MMA8653驱动示例
static struct i2c_driver mma8653_driver = {
.driver = {
.name = "mma8653_drv",
},
.id_table = mma8653_idtable,
.probe = mma8653_probe,
.remove = mma8653_remove,
};
2. SMBUS接口函数使用
使用步骤
- 查阅文档:
/opt/kernel/Documentation/i2c/smbus-protocol - 选择函数:根据时序要求选择对应函数
- 查找定义:使用SourceInsight查看函数原型
常用函数示例
// 读取单字节数据
i2c_smbus_read_byte_data(client, reg_addr);
// 写入单字节数据
i2c_smbus_write_byte_data(client, reg_addr, data);
// 读取多字节数据
i2c_smbus_read_i2c_block_data(client, reg_addr, length, buffer);
MMA8653 ID读取时序
- 启动 → 设备地址+写 → ACK → 寄存器地址 → ACK
- 重启 → 设备地址+读 → ACK → 数据字节 → NACK → 停止
3. MMA8653加速度数据处理
寄存器映射
| 方向 | 高位寄存器 | 低位寄存器 | 数据位分配 |
|---|---|---|---|
| X轴 | 0x01 | 0x02 | 高8位(9:2) + 低2位(1:0) |
| Y轴 | 0x03 | 0x04 | 高8位(9:2) + 低2位(1:0) |
| Z轴 | 0x05 | 0x06 | 高8位(9:2) + 低2位(1:0) |
数据合并算法
// 读取X轴加速度值
x_high = i2c_smbus_read_byte_data(client, 0x01); // 高8位
x_low = i2c_smbus_read_byte_data(client, 0x02); // 低2位在bit[7:6]
x_accel = (x_high << 2) | (x_low >> 6); // 合并为10位值
测试步骤
# 下位机测试
cd /home/drivers
insmod mma8653_drv.ko # 加载驱动,触发probe
./mma8653_test # 测试程序,读取XYZ加速度值
rmmod mma8653_drv # 卸载驱动,触发remove
4. 开发总结
关键要点
- 分层思想:降低开发复杂度,关注业务逻辑
- 匹配机制:硬件信息与软件驱动的自动关联
- 标准接口:SMBUS提供统一的访问接口
- 数据解析:根据器件手册正确处理寄存器数据
开发流程
- 注册硬件信息(i2c_board_info)
- 实现设备驱动(i2c_driver)
- 使用SMBUS接口访问器件
- 提供应用层访问接口
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)