以下以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引脚连接
传感器引脚 | 处理器引脚 | 备注 |
---|---|---|
VDD | 3.3V | 电源 |
GND | GND | 地线 |
SDA | I2Cx_SDA | I2C数据线(需上拉) |
SCL | I2Cx_SCL | I2C时钟线(需上拉) |
INT | GPIOx | 中断引脚(可选) |
注意: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读取数据
八、关键调试技巧
-
I2C通信调试:
i2cdump -y 0 0x68 # 查看传感器寄存器值
-
时间戳优化:
-
在中断处理函数中使用
ktime_get_real_ts64()
获取精确时间 -
启用内核高精度定时器
CONFIG_HIGH_RES_TIMERS
-
-
稳定性增强:
-
添加I2C传输重试机制
-
使用
devm_
系列函数管理资源防止内存泄漏
-
按照以上步骤,您应该能够完成驱动开发并通过测试验证功能。实际开发中需参考ICM-4265数据手册调整寄存器配置。