本文从零开始,逐步完成一个挂载在 I2C 总线上的虚拟 IMU(加速度计)IIO 驱动,覆盖 DT binding → i2c_driver probe/remove → iio_dev 注册 → iio_info / sysfs 验证 全流程。
1. 前置条件
确保以下环境已就绪(参考同级页面的 QEMU + 交叉编译工具链搭建指南):
交叉编译器
arm-linux-gnueabihf-gcc 可用已编译 v6.1 内核源码,且开启
CONFIG_IIO、CONFIG_I2C 支持QEMU mcimx6ul-evk(Cortex-A7)能正常启动 BusyBox rootfs
rootfs 中包含
iio_info(来自 libiio 或 iio-utils)为什么选 IIO? IIO(Industrial I/O)是 Linux 为传感器类设备设计的标准子系统,提供统一的 sysfs 接口和触发缓冲机制,是 IMU、ADC、DAC 等设备的首选框架。
2. IIO 子系统关键概念
概念 | 说明 |
iio_dev | 代表一个 IIO 设备实例,类比字符设备中的 cdev |
iio_chan_spec | 描述一路传感器通道(如 X/Y/Z 加速度),包含类型、修饰符、索引等 |
iio_info | 驱动操作集,包含 read_raw / write_raw 等回调,用户读 sysfs 时触发 |
devm_iio_device_alloc | 分配 iio_dev + 私有数据,生命周期由 devm 自动管理 |
iio_device_register | 将 iio_dev 注册进内核,对应 /sys/bus/iio/devices/iio:deviceX |
3. 驱动源码
3.1 IMU IIO 驱动 — fake_imu.c
3.2 关键结构说明
元素 | 作用 |
FAKE_IMU_ACCEL_CHANNEL 宏 | 消除三轴通道定义的重复代码,统一声明 type / modified / mask / scan_index |
info_mask_separate | 标记 每通道独立 的属性,RAW 值各轴不同 → 对应 in_accel_x_raw 等节点 |
info_mask_shared_by_type | 标记 同类型通道共享 的属性,SCALE 对所有加速度轴一致 → 对应单一 in_accel_scale 节点 |
scan_index | 为后续触发缓冲(Triggered Buffer)预留通道索引,决定数据在缓冲区中的排列顺序 |
i2c_device_id[] | 非 DT 环境的回退匹配表,配合 MODULE_DEVICE_TABLE(i2c, ...) 支持传统 board file 注册 |
iio_priv(indio_dev) | 取出紧跟在 iio_dev 后面的私有数据指针,替代全局变量 |
devm_iio_device_register | 注册 IIO 设备,由 devm 管理生命周期,remove 时自动注销 |
module_i2c_driver | 宏展开为 module_init/module_exit,自动注册/注销 i2c_driver |
info_mask_separatevsinfo_mask_shared_by_type:RAW 值是每个轴独立读取的(X/Y/Z 各不同),因此放在separate;SCALE 转换系数对所有加速度通道一致(同一芯片量程相同),放在shared_by_type可避免生成冗余的in_accel_x_scale/in_accel_y_scale/in_accel_z_scale,只暴露一个in_accel_scale。
4. Makefile
路径说明:KERNDIR使用仓库内相对路径../01_qemu_env_build/linux-6.1,确保在仓库目录结构下直接make即可编译,无需手动指定内核路径。
5. 编译模块
编译成功后确认产出:
注意depends: industrialio:IIO 核心是一个独立模块,加载fake_imu.ko前必须先加载industrialio.ko(或内核内建)。
6. 设备树配置
6.1 I2C 节点说明
I2C 设备必须作为 I2C 控制器节点的子节点 声明,并提供
reg(7-bit 地址)属性。6.2 创建 Overlay 源文件 — fake-imu-overlay.dts
6.3 编译 Overlay
6.4 备选:直接修改主 DTB
7. 一键构建脚本 — build.sh
仓库提供了
build.sh,自动完成 编译模块 → 注入 DT 节点 → 打包 rootfs 全流程:运行方式:
脚本执行后产出三个关键文件:
fake_imu.ko— 编译好的驱动模块
imx6ul-14x14-evk-imu.dtb— 包含 fake_imu 节点的设备树
rootfs.cpio.gz— 打包好的根文件系统
7.1 手动打包(备选)
如果不使用
build.sh,可手动完成:8. QEMU 内加载验证
8.1 启动 QEMU — run_qemu.sh
仓库提供了一键启动脚本,使用 mcimx6ul-evk(Cortex-A7)平台:
运行:
平台说明:使用mcimx6ul-evk(i.MX6UL EVK)而非 sabrelite,QEMU 对该机型的 I2C 控制器(i2c@21a0000)支持更完整,设备树注入后能正确触发 probe。
8.2 挂载文件系统 & 加载模块
8.3 确认 probe 调用
8.4 查看 IIO sysfs 接口
注意 sysfs 节点命名变化:由于 SCALE 使用info_mask_shared_by_type,sysfs 只生成一个共享的in_accel_scale,而非三个独立的in_accel_x_scale/in_accel_y_scale/in_accel_z_scale。
8.5 自动化验证脚本 — verify_imu.sh
仓库提供了一键验证脚本,在 QEMU 内执行:
将
verify_imu.sh 复制到 rootfs 后,QEMU 内直接运行:预期输出:
8.6 使用 iio_info 验证(可选)
9. 验证清单
make 编译无 warning,产出 fake_imu.komodinfo 输出 depends: industrialiofile fake_imu.ko 显示 ARM ELF relocatable设备树中
i2c1 节点下存在 fake_imu@68 子节点insmod 后 dmesg 显示 probe OK, addr=0x68/sys/bus/iio/devices/iio:device0/name 输出 fake_imuin_accel_z_raw 读取返回 1000in_accel_scale 读取返回 0.009806(共享节点,非 in_accel_x_scale)verify_imu.sh 全部步骤通过rmmod 后 dmesg 显示 remove called10. 常见问题排查
现象 | 原因 | 解决方案 |
insmod: can't insert: unknown symbol iio_device_register | IIO 核心未加载 | 先 insmod industrialio.ko,或内核配置 CONFIG_IIO=y |
probe 未调用,无 probe OK 日志 | DT 节点不在 i2c 控制器下,或 compatible 不匹配 | 确认 &i2c1 { fake_imu@68 { compatible = "myvendor,fake-imu"; ... } } 结构正确 |
/sys/bus/iio/devices/ 为空 | iio_device_register 失败或 sysfs 未挂载 | 检查 dmesg 错误信息;确认 mount -t sysfs none /sys |
读 in_accel_x_raw 返回 -EINVAL | read_raw 回调未处理该 mask | 检查 iio_chan_spec.info_mask_separate 是否包含 BIT(IIO_CHAN_INFO_RAW) |
iio_info 找不到设备 | 工具版本与内核 ABI 不匹配,或 /dev/iio:device0 缺失 | 确认内核开启 CONFIG_IIO_BUFFER,或改用 sysfs 直接读取验证 |
insmod: invalid module format | vermagic 不匹配 | 确保 KERNDIR 指向 QEMU 启动时使用的同一内核树 |
11. 向真实硬件(MPU-6050)迁移
完成虚拟驱动验证后,迁移到真实 IMU 只需替换以下部分:
- probe 中添加 WHO_AM_I 寄存器检测
- read_raw 中替换为真实寄存器读取
- DT compatible 改为
invensense,mpu6050
- 添加电源管理:
devm_regulator_get+pm_ops
12. 下一步扩展方向
- 触发缓冲(Triggered Buffer) — 接入 IIO 触发器,实现中断驱动的连续数据采集
- libiio 用户态应用 — 用
iio_channel_read替代 sysfs 读取,支持高频采样
- IIO 事件机制 — 配置阈值告警,通过
iio_push_event上报超量程事件
- 对接 MIPI Camera 流水线 — IMU + 摄像头时间戳同步,构建完整感知平台







