linux I2C驱动详解(1)-设备驱动

以下以ICM-426xx传感器芯片驱动为例讲解Linux(openwrt) I2C设备驱动架构及编写方法,涵盖架构设计、硬件连接、设备树、驱动代码、应用测试及编译方法。所有代码均附带注释,并附流程图。


一、整体架构与开发环境

1. 驱动架构
+-----------------------+
|   User Application    | (e.g. 通过sysfs或IIO接口读取数据)
+-----------------------+
|   Linux Kernel        |
|   - IIO Subsystem     | (工业IO子系统,提供标准传感器接口)
+- - - - - - - - - - - -+
|   - I2C Core          | (处理I2C总线通信,为控制器、设备驱动及应用层提供标准接口)
+- - - - - - - - - - - -+
|   - I2C Adapter Driver| (I2C控制器驱动,连接I2C设备)
+- - - - - - - - - - - -+
|   - I2C Device Driver | (如ICM-4265驱动,实现probe/read/init等操作)
+- - - - - - - - - - - -+
|   Hardware Layer      | (I2C物理连接)
+-----------------------+
2. OpenWRT与标准Linux的区别
  • 内核配置:OpenWRT默认裁剪了部分内核功能,需在make menuconfig中启用:

    Device Drivers ->
      <*> I2C support ->
        <*> I2C device interface
      <*> Industrial I/O support ->
        <*> Accelerometers ->
          <*> InvenSense ICM-426xx accelerometer driver (需手动添加)

  • 设备树路径:OpenWRT设备树位于target/linux/<平台>/dts/,需重新编译固件。

  • 交叉编译工具链:需使用OpenWRT SDK生成驱动模块。

3. 开发环境
# 安装OpenWRT SDK和编译工具
sudo apt install build-essential libncurses-dev
git clone https://git.openwrt.org/openwrt/openwrt.git
cd openwrt
./scripts/feeds update -a && ./scripts/feeds install -a
make menuconfig  # 配置目标平台和内核选项
 

二、硬件连接

ICM-4265引脚连接
传感器引脚处理器引脚备注
VDD3.3V电源
GNDGND地线
SDAI2Cx_SDAI2C数据线(需上拉)
SCLI2Cx_SCLI2C时钟线(需上拉)
INTGPIOx中断引脚(可选)

注意:I2C地址通常为0x68(ADDR引脚接地)或0x69(接VDD)。


三、设备树配置

在OpenWRT设备树文件(如target/linux/ath79/dts/xxx.dts)中添加以下节点:

&i2c0 {  // 根据实际I2C控制器编号修改
    status = "okay";
    clock-frequency = <400000>;  // I2C速率400kHz

    icm4265: accelerometer@68 {
        compatible = "invensense,icm4265";
        reg = <0x68>;          // I2C地址
        interrupt-parent = <&gpio>;  // 若使用中断
        interrupts = <17 IRQ_TYPE_EDGE_RISING>; // GPIO17中断
    };
};

四、驱动代码实现

1. 驱动核心代码 (icm4265.c)
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

#define ICM4265_REG_WHO_AM_I  0x75
#define ICM4265_CHIP_ID       0x42  // 根据数据手册确认

struct icm4265_data {
    struct i2c_client *client;
    struct mutex lock;
    struct iio_dev *indio_dev;
};

/* 寄存器读取函数 */
static int icm4265_read_reg(struct i2c_client *client, u8 reg, u8 *val) {
    int ret = i2c_smbus_read_byte_data(client, reg);
    if (ret < 0)
        dev_err(&client->dev, "读寄存器0x%02x失败: %d\n", reg, ret);
    else
        *val = ret;
    return ret;
}

/* 寄存器写入函数 */
static int icm4265_write_reg(struct i2c_client *client, u8 reg, u8 val) {
    int ret = i2c_smbus_write_byte_data(client, reg, val);
    if (ret < 0)
        dev_err(&client->dev, "写寄存器0x%02x失败: %d\n", reg, ret);
    return ret;
}

/* 初始化传感器 */
static int icm4265_init(struct i2c_client *client) {
    u8 val;
    // 检查设备ID
    if (icm4265_read_reg(client, ICM4265_REG_WHO_AM_I, &val) < 0)
        return -ENODEV;
    if (val != ICM4265_CHIP_ID) {
        dev_err(&client->dev, "设备ID不匹配: 0x%02x\n", val);
        return -ENODEV;
    }
    // 配置加速度计量程和采样率(示例配置)
    icm4265_write_reg(client, 0x1F, 0x04);  // 加速度计±16g,100Hz
    return 0;
}

/* IIO数据读取回调 */
static int icm4265_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan,
                            int *val, int *val2, long mask) {
    struct icm4265_data *data = iio_priv(indio_dev);
    u8 buffer[6];
    int ret;

    mutex_lock(&data->lock);
    // 读取加速度计数据(假设数据寄存器从0x11开始)
    ret = i2c_smbus_read_i2c_block_data(data->client, 0x11, 6, buffer);
    if (ret < 0) goto error;

    // 转换原始数据(假设16位有符号,大端格式)
    *val = (s16)((buffer[0] << 8) | buffer[1]);
    *val2 = 0;  // 根据量程调整比例因子

error:
    mutex_unlock(&data->lock);
    return ret;
}

/* IIO通道定义 */
static const struct iio_chan_spec icm4265_channels[] = {
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_X,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    },
    // 类似定义Y和Z通道...
};

/* IIO设备信息 */
static const struct iio_info icm4265_info = {
    .read_raw = icm4265_read_raw,
};

/* Probe函数 */
static int icm4265_probe(struct i2c_client *client,
                         const struct i2c_device_id *id) {
    struct icm4265_data *data;
    struct iio_dev *indio_dev;

    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
    if (!indio_dev) return -ENOMEM;

    data = iio_priv(indio_dev);
    data->client = client;
    mutex_init(&data->lock);

    indio_dev->name = "icm4265";
    indio_dev->channels = icm4265_channels;
    indio_dev->num_channels = ARRAY_SIZE(icm4265_channels);
    indio_dev->info = &icm4265_info;
    indio_dev->modes = INDIO_DIRECT_MODE;

    if (icm4265_init(client) return -ENODEV;

    return devm_iio_device_register(&client->dev, indio_dev);
}

/* 设备ID表 */
static const struct i2c_device_id icm4265_id[] = {
    { "icm4265", 0 },
    {}
};
MODULE_DEVICE_TABLE(i2c, icm4265_id);

/* 设备树匹配表 */
static const struct of_device_id icm4265_of_match[] = {
    { .compatible = "invensense,icm4265" },
    {}
};
MODULE_DEVICE_TABLE(of, icm4265_of_match);

/* I2C驱动结构体 */
static struct i2c_driver icm4265_driver = {
    .driver = {
        .name = "icm4265",
        .of_match_table = icm4265_of_match,
    },
    .probe = icm4265_probe,
    .id_table = icm4265_id,
};
module_i2c_driver(icm4265_driver);    //注册为I2C总线驱动

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ICM-4265 Accelerometer Driver");
MODULE_LICENSE("GPL");

五、用户空间测试代码

1. 使用IIO工具快速测试
# 安装iio工具
opkg install i2c-tools iio-utils

# 查看设备是否识别
i2cdetect -y 0  # 扫描I2C总线
ls /sys/bus/iio/devices/  # 确认iio:deviceX存在

# 读取加速度数据
cat /sys/bus/iio/devices/iio:deviceX/in_accel_x_raw
2. 自定义C测试程序 (test_icm4265.c)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define IIO_DEVICE "/sys/bus/iio/devices/iio:device0"

int main() {
    FILE *fp;
    char path[256];
    int x, y, z;

    while(1) {
        // 读取X轴数据
        snprintf(path, sizeof(path), "%s/in_accel_x_raw", IIO_DEVICE);
        fp = fopen(path, "r");
        if (!fp) { perror("Failed to open file"); exit(1); }
        fscanf(fp, "%d", &x);
        fclose(fp);

        // 类似读取Y和Z轴...

        printf("Accel: X=%d, Y=%d, Z=%d\n", x, y, z);
        usleep(100000);  // 100ms间隔
    }
    return 0;
}

六、编译与安装

1. 驱动Makefile
obj-m += icm4265.o

all:
    make -C $(OPENWRT_KERNEL) M=$(PWD) modules

clean:
    make -C $(OPENWRT_KERNEL) M=$(PWD) clean
2. 编译命令
export OPENWRT_KERNEL=/path/to/openwrt/build_dir/target-xxx/linux-xxx
make ARCH=mips CROSS_COMPILE=mips-openwrt-linux-
3. 加载驱动
insmod icm4265.ko
dmesg | tail  # 查看内核日志确认驱动加载成功

七、流程图

         驱动加载
            |
            v
    +-----------------+
    | i2c_driver.probe |
    +-----------------+
            |
            v
    +-----------------+
    | 初始化传感器     |
    | (检查ID/配置寄存器)|
    +-----------------+
            |
            v
    +-----------------+
    | 注册IIO设备      |
    +-----------------+
            |
            v
用户空间通过sysfs读取数据

八、关键调试技巧

  1. I2C通信调试

    i2cdump -y 0 0x68  # 查看传感器寄存器值

  2. 时间戳优化

    • 在中断处理函数中使用ktime_get_real_ts64()获取精确时间

    • 启用内核高精度定时器CONFIG_HIGH_RES_TIMERS

  3. 稳定性增强

    • 添加I2C传输重试机制

    • 使用devm_系列函数管理资源防止内存泄漏

按照以上步骤,您应该能够完成驱动开发并通过测试验证功能。实际开发中需参考ICM-4265数据手册调整寄存器配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值