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驱动分层框架

分层架构

  1. I2C总线驱动

    • 操作I2C控制器硬件
    • 负责时序产生
    • 由芯片厂家提供
  2. I2C设备驱动

    • 关心外设信息(设备地址、寄存器地址、数据)
    • 提供应用层接口
    • 由驱动工程师编写
  3. 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;
}
数据传输流程
  1. 应用层发起读请求
  2. 设备驱动调用SMBUS接口
  3. SMBUS层调用总线驱动
  4. 总线驱动产生硬件时序
  5. 数据逐层返回

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接口函数使用

使用步骤

  1. 查阅文档:/opt/kernel/Documentation/i2c/smbus-protocol
  2. 选择函数:根据时序要求选择对应函数
  3. 查找定义:使用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轴0x010x02高8位(9:2) + 低2位(1:0)
Y轴0x030x04高8位(9:2) + 低2位(1:0)
Z轴0x050x06高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. 开发总结

关键要点

  1. 分层思想:降低开发复杂度,关注业务逻辑
  2. 匹配机制:硬件信息与软件驱动的自动关联
  3. 标准接口:SMBUS提供统一的访问接口
  4. 数据解析:根据器件手册正确处理寄存器数据

开发流程

  1. 注册硬件信息(i2c_board_info)
  2. 实现设备驱动(i2c_driver)
  3. 使用SMBUS接口访问器件
  4. 提供应用层访问接口
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐