一、Linux IIO 框架基础
IIO(Industrial I/O)是 Linux 内核专为测量类 / 传感类设备设计的框架,区别于面向人机交互的 Input 框架,主要适配加速度计、陀螺仪、ADC/DAC、压力传感器等模拟 / 数字传感器。
1. IIO 核心组件
| 组件 | 作用 |
|---|---|
struct iio_dev | IIO 设备核心结构体,代表一个 IIO 设备,需分配、初始化并注册到内核 |
struct iio_chan_spec | 通道描述结构体,定义传感器的每个测量通道(如 acc_x/gyro_z) |
struct iio_info | 包含 IIO 设备的核心操作函数(如数据读取、参数配置) |
sysfs 接口 | IIO 框架自动导出的用户空间访问接口(路径:/sys/bus/iio/devices/iio:deviceX/) |
| IIO Trigger/Buffer | 触发机制(中断 / 定时器)+ 数据缓冲区,用于批量采集传感器数据 |
2. IIO 框架优势
- 统一的用户空间接口,无需自定义节点;
- 原生支持传感器的 “测量值读取”“参数配置(量程 / 采样率)”;
- 支持轮询 / 中断两种数据读取方式,适配不同传感器场景。
二、ACC/GYRO 驱动开发整体流程
ACC(加速度计,输出单位:m/s²)和 GYRO(陀螺仪,输出单位:rad/s)通常为 I2C/SPI 接口,驱动开发核心流程如下:
步骤 1:梳理传感器硬件特性
开发前需明确传感器关键参数:
- 通信接口:I2C/SPI 地址、寄存器映射(数据寄存器、配置寄存器、中断寄存器);
- 数据格式:原始数据位宽(如 16 位补码)、量程(如 acc ±2g/±4g,gyro ±250°/s);
- 工作模式:轮询读取、中断触发读取;
- 校准参数:硬件 / 软件校准偏移量(可选)。
步骤 2:设备树(DTB)配置
在设备树中定义传感器节点,以 I2C 接口的 MPU6050(集成 acc+gyro)为例:
&i2c1 { clock-frequency = <400000>; mpu6050@68 { /* I2C 地址 0x68 */ compatible = "invensense,mpu6050"; reg = <0x68>; interrupt-parent = <&gpio1>; interrupts = <20 IRQ_TYPE_EDGE_RISING>; /* 中断引脚 */ avdd-supply = <&vdd_3v3>; /* 供电 */ dvdd-supply = <&vdd_1v8>; }; };步骤 3:驱动代码实现(核心)
以 MPU6050(acc+gyro 组合传感器)为例,实现基于 IIO 框架的驱动:
3.1 头文件与结构体定义
#include <linux/i2c.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #include <linux/module.h> // 传感器私有数据 struct mpu6050_data { struct i2c_client *client; struct mutex lock; // 保护寄存器访问 u16 acc_range; // 加速度计量程(±2g/±4g/±8g/±16g) u16 gyro_range; // 陀螺仪量程(±250/±500/±1000/±2000 °/s) }; // 通道定义:acc(x/y/z) + gyro(x/y/z) static const struct iio_chan_spec mpu6050_channels[] = { // 加速度计 X 轴 { .type = IIO_ACCEL, .channel = IIO_X, .address = 0x3B, // ACC_X 数据寄存器起始地址 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, { .type = IIO_ACCEL, .channel = IIO_Y, .address = 0x3D, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, { .type = IIO_ACCEL, .channel = IIO_Z, .address = 0x3F, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, // 陀螺仪 X 轴 { .type = IIO_GYRO, .channel = IIO_X, .address = 0x43, // GYRO_X 数据寄存器起始地址 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, { .type = IIO_GYRO, .channel = IIO_Y, .address = 0x45, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, { .type = IIO_GYRO, .channel = IIO_Z, .address = 0x47, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, };3.2 核心操作函数(数据读取 / 配置)
// 读取原始数据(寄存器值) static int mpu6050_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct mpu6050_data *data = iio_priv(indio_dev); __be16 raw_data; int ret; mutex_lock(&data->lock); switch (mask) { case IIO_CHAN_INFO_RAW: // 读取 16 位数据寄存器(两个字节) ret = i2c_smbus_read_i2c_block_data(data->client, chan->address, 2, (u8 *)&raw_data); if (ret < 0) { mutex_unlock(&data->lock); return ret; } // 转换为主机字节序(传感器输出大端) *val = (s16)be16_to_cpu(raw_data); mutex_unlock(&data->lock); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: // 转换量程为物理单位(acc: g→m/s²;gyro: °/s→rad/s) if (chan->type == IIO_ACCEL) { // ±2g 对应 scale=16384 LSB/g → 1g=9.80665 m/s² *val = 980665; // 9.80665 * 10^5 *val2 = 1638400; // 16384 * 100 } else if (chan->type == IIO_GYRO) { // ±250°/s 对应 scale=131 LSB/(°/s) → 1°/s=0.01745 rad/s *val = 1745; // 0.01745 * 10^5 *val2 = 13100; // 131 * 100 } mutex_unlock(&data->lock); return IIO_VAL_FRACTIONAL; default: mutex_unlock(&data->lock); return -EINVAL; } } // IIO 设备操作集 static const struct iio_info mpu6050_iio_info = { .read_raw = mpu6050_read_raw, .driver_module = THIS_MODULE, };3.3 Probe 函数(驱动初始化)
static int mpu6050_probe(struct i2c_client *client) { struct iio_dev *indio_dev; struct mpu6050_data *data; int ret; // 1. 分配 IIO 设备结构体 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct mpu6050_data)); if (!indio_dev) return -ENOMEM; // 2. 初始化私有数据 data = iio_priv(indio_dev); />4.2 数据读取流程(用户空间→内核)
以读取加速度 X 轴数据为例:
![]()
4.3 量程配置扩展(可选)
若需支持动态配置量程,可实现write_raw函数:
static int mpu6050_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct mpu6050_data *data = iio_priv(indio_dev); int reg_val = 0; if (mask != IIO_CHAN_INFO_SCALE) return -EINVAL; mutex_lock(&data->lock); if (chan->type == IIO_ACCEL) { // 根据scale值设置量程寄存器(0x1C) if (val == 980665 && val2 == 1638400) reg_val = 0x00; // ±2g else if (val == 1961330 && val2 == 1638400) reg_val = 0x08; // ±4g // ... 其他量程 i2c_smbus_write_byte_data(data->client, 0x1C, reg_val); />二、驱动层开发(基于 MPU6050 扩展中断 + Buffer)
以下是在之前轮询驱动的基础上,新增中断注册、IIO Trigger、IIO Buffer相关代码,实现中断触发读取:
步骤 1:扩展私有数据结构体
新增中断号、IIO Trigger、Buffer 相关成员:
#include <linux/interrupt.h> #include <linux/iio/buffer.h> #include <linux/iio/trigger.h> #include <linux/iio/triggered_buffer.h> #include <linux/iio/trigger_consumer.h> // 扩展私有数据结构体 struct mpu6050_data { struct i2c_client *client; struct mutex lock; u16 acc_range; u16 gyro_range; int irq; // 中断号 struct iio_trigger *trig; // IIO触发器 struct completion comp; // 用于同步(可选) }; // 定义扫描元素(需要采集的通道,顺序对应数据缓冲区格式) static const struct iio_chan_spec mpu6050_channels[] = { // 加速度计 X 轴 { .type = IIO_ACCEL, .channel = IIO_X, .address = 0x3B, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 0, // 扫描索引(缓冲区中第0个数据) .scan_type = { .sign = 's', // 有符号数 .realbits = 16, // 有效位宽 .storagebits = 16, // 存储位宽 .endianness = IIO_BE, // 传感器输出大端 }, }, { .type = IIO_ACCEL, .channel = IIO_Y, .address = 0x3D, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 1, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE }, }, { .type = IIO_ACCEL, .channel = IIO_Z, .address = 0x3F, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 2, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE }, }, // 陀螺仪 X 轴 { .type = IIO_GYRO, .channel = IIO_X, .address = 0x43, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 3, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE }, }, { .type = IIO_GYRO, .channel = IIO_Y, .address = 0x45, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 4, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE }, }, { .type = IIO_GYRO, .channel = IIO_Z, .address = 0x47, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = 5, .scan_type = { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE }, }, IIO_CHAN_SOFT_TIMESTAMP(6), // 可选:添加时间戳(索引6) };
步骤 2:实现中断处理函数 & Trigger 回调
// 中断处理函数(传感器DRDY中断触发) static irqreturn_t mpu6050_irq_handler(int irq, void *dev_id) { struct mpu6050_data *data = dev_id; // 触发IIO Trigger(通知Buffer采集数据) iio_trigger_poll(data->trig); return IRQ_HANDLED; } // Buffer 数据采集函数(Trigger触发后执行) static void mpu6050_trigger_handler(struct iio_trigger *trig, void *p) { struct iio_dev *indio_dev = p; struct mpu6050_data *data = iio_priv(indio_dev); int ret; u8 buf[12]; // 6个通道 × 2字节 = 12字节(acc x/y/z + gyro x/y/z) mutex_lock(&data->lock); // 一次性读取所有通道数据(0x3B开始,连续12字节) ret = i2c_smbus_read_i2c_block_data(data->client, 0x3B, 12, buf); if (ret < 0) { mutex_unlock(&data->lock); dev_err(&data->client->dev, "读取传感器数据失败: %d\n", ret); return; } // 将数据写入IIO Buffer(自动处理字节序/格式转换) iio_push_to_buffers_from_active(indio_dev, buf); mutex_unlock(&data->lock); } // 注册Trigger的回调函数集 static const struct iio_trigger_ops mpu6050_trigger_ops = { .trigger_handler = mpu6050_trigger_handler, .owner = THIS_MODULE, };
步骤 3:扩展 Probe 函数(初始化中断 + Trigger+Buffer)
static int mpu6050_probe(struct i2c_client *client) { struct iio_dev *indio_dev; struct mpu6050_data *data; int ret; // 1. 分配IIO设备结构体(同轮询版) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct mpu6050_data)); if (!indio_dev) return -ENOMEM; // 2. 初始化私有数据 data = iio_priv(indio_dev); ># 内核配置项(.config) CONFIG_IIO_BUFFER=y CONFIG_IIO_TRIGGERED_BUFFER=y CONFIG_IIO_TRIGGER=y
三、应用层获取中断触发的数据
应用层通过 IIO 框架创建的字符设备文件(/dev/iio:deviceX)读取 Buffer 数据,核心流程是 “配置 Buffer→启用 Trigger→读取数据”。
3.1 应用层示例代码(C 语言)
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/select.h> #include <sys/ioctl.h> #include <linux/iio/iio.h> // 定义数据结构体(对应驱动层的扫描元素顺序) typedef struct { int16_t acc_x; // 加速度X轴 int16_t acc_y; // 加速度Y轴 int16_t acc_z; // 加速度Z轴 int16_t gyro_x; // 陀螺仪X轴 int16_t gyro_y; // 陀螺仪Y轴 int16_t gyro_z; // 陀螺仪Z轴 uint64_t ts; // 时间戳(可选) } mpu6050_data_t; #define IIO_DEV_PATH "/dev/iio:device0" // 根据实际设备号修改 int main() { int fd, ret; mpu6050_data_t data; fd_set rfds; struct timeval tv; // 1. 打开IIO字符设备 fd = open(IIO_DEV_PATH, O_RDONLY); if (fd < 0) { perror("打开设备失败"); return -1; } // 2. 配置Buffer(可选:设置单次读取的采样数,这里设为1) int buf_len = 1; ret = ioctl(fd, IIO_BUFFER_SET_LEN, &buf_len); if (ret < 0) { perror("设置Buffer长度失败"); close(fd); return -1; } // 3. 启用Trigger(关联到传感器的DRDY中断) // 先将trigger_current指向我们的mpu6050 trigger FILE *fp = fopen("/sys/bus/iio/devices/iio:device0/trigger/current_trigger", "w"); if (!fp) { perror("打开trigger配置文件失败"); close(fd); return -1; } fprintf(fp, "mpu6050-dev0"); // Trigger名称(对应驱动层的命名) fclose(fp); // 4. 启用Buffer(开始采集数据) fp = fopen("/sys/bus/iio/devices/iio:device0/buffer/enable", "w"); if (!fp) { perror("打开buffer enable文件失败"); close(fd); return -1; } fprintf(fp, "1"); fclose(fp); // 5. 循环读取中断触发的数据 while (1) { FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = 5; // 超时5秒 tv.tv_usec = 0; // 等待数据就绪(中断触发后Buffer有数据) ret = select(fd + 1, &rfds, NULL, NULL, &tv); if (ret < 0) { perror("select失败"); break; } else if (ret == 0) { printf("超时未收到数据\n"); continue; } // 读取Buffer中的数据 ret = read(fd, &data, sizeof(mpu6050_data_t)); if (ret != sizeof(mpu6050_data_t)) { perror("读取数据失败"); continue; } // 打印数据(原始值,可乘以scale转换为物理单位) printf("acc_x: %d, acc_y: %d, acc_z: %d | gyro_x: %d, gyro_y: %d, gyro_z: %d\n", data.acc_x, data.acc_y, data.acc_z, data.gyro_x, data.gyro_y, data.gyro_z); } // 6. 关闭前清理 fp = fopen("/sys/bus/iio/devices/iio:device0/buffer/enable", "w"); fprintf(fp, "0"); fclose(fp); close(fd); return 0; }
3.3 关键说明
- 设备号确认:通过
ls /sys/bus/iio/devices/查看实际的iio:deviceX编号; - 数据格式匹配:应用层
mpu6050_data_t的成员顺序必须和驱动层scan_index一致; - 物理值转换:原始值 × scale = 物理值(如 acc 的 scale 约为 0.000598 m/s²/LSB);
- 异步读取:推荐用
select/poll/epoll监听数据就绪,避免轮询占用 CPU。
四、总结
- 驱动层核心:
- 注册传感器 DRDY 中断,中断触发后调用
iio_trigger_poll()通知 Buffer; - 创建 IIO Trigger 并绑定回调函数,回调中读取传感器数据并写入 Buffer;
- 配置
INDIO_BUFFER_TRIGGERED模式,替代轮询的INDIO_DIRECT_MODE。
- 应用层核心:
- 打开
/dev/iio:deviceX字符设备,配置 Buffer 长度并启用 Trigger; - 通过
select/poll监听数据就绪,读取 Buffer 中的批量数据; - 数据格式需与驱动层
scan_index严格匹配,确保数据解析正确。
- 优势:中断触发读取相比轮询,CPU 占用率更低,适合高频数据采集场景(如运动检测、惯性导航)。