一、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);
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, indio_dev);
// 3. 硬件初始化(复位、配置量程)
// 写入电源管理寄存器,唤醒传感器
ret = i2c_smbus_write_byte_data(client, 0x6B, 0x00);
if (ret < 0)
return ret;
// 配置加速度计量程(±2g)
ret = i2c_smbus_write_byte_data(client, 0x1C, 0x00);
if (ret < 0)
return ret;
// 配置陀螺仪量程(±250°/s)
ret = i2c_smbus_write_byte_data(client, 0x1B, 0x00);
if (ret < 0)
return ret;
// 4. 初始化 IIO 设备核心参数
indio_dev->name = "mpu6050";
indio_dev->info = &mpu6050_iio_info;
indio_dev->channels = mpu6050_channels;
indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels);
indio_dev->dev.parent = &client->dev;
indio_dev->modes = INDIO_DIRECT_MODE; // 直接读取模式(轮询)
// 5. 注册 IIO 设备
return devm_iio_device_register(&client->dev, indio_dev);
}
// I2C 驱动匹配表
static const struct i2c_device_id mpu6050_id[] = {
{ "mpu6050", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mpu6050_id);
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "invensense,mpu6050" },
{ }
};
MODULE_DEVICE_TABLE(of, mpu6050_of_match);
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.probe = mpu6050_probe,
.id_table = mpu6050_id,
};
module_i2c_driver(mpu6050_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MPU6050 IIO driver for ACC/GYRO");
步骤 4:功能流程说明
4.1 驱动加载流程

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);
data->acc_range = reg_val;
}
mutex_unlock(&data->lock);
return 0;
}
4.4 中断触发读取(可选)
若传感器支持数据就绪中断,可配置 IIO Trigger + Buffer 实现批量采集:
- 注册中断处理函数,监听传感器 “数据就绪” 中断;
- 初始化 IIO Trigger(中断触发);
- 启用 IIO Buffer,中断触发时自动读取数据并写入缓冲区;
- 用户空间通过
/dev/iio:deviceX读取缓冲区数据。
一、中断触发读取的核心原理
中断触发读取是 IIO 框架的进阶用法,核心是利用传感器的 数据就绪中断(DRDY) 替代轮询,实现 “数据准备好才读取”,降低 CPU 占用。整体流程:

二、驱动层开发(基于 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);
data->client = client;
data->irq = client->irq; // 从设备树获取中断号
mutex_init(&data->lock);
init_completion(&data->comp);
i2c_set_clientdata(client, indio_dev);
// 3. 硬件初始化(新增:开启DRDY中断)
ret = i2c_smbus_write_byte_data(client, 0x6B, 0x00); // 唤醒传感器
if (ret < 0) return ret;
ret = i2c_smbus_write_byte_data(client, 0x1C, 0x00); // ACC量程±2g
if (ret < 0) return ret;
ret = i2c_smbus_write_byte_data(client, 0x1B, 0x00); // GYRO量程±250°/s
if (ret < 0) return ret;
// 配置MPU6050中断:开启DRDY中断,中断引脚为高电平触发
ret = i2c_smbus_write_byte_data(client, 0x38, 0x01); // 使能DRDY中断
if (ret < 0) return ret;
ret = i2c_smbus_write_byte_data(client, 0x37, 0x00); // 中断引脚不做其他配置
if (ret < 0) return ret;
// 4. 注册中断处理函数
ret = devm_request_irq(&client->dev, data->irq, mpu6050_irq_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"mpu6050_drdy", data);
if (ret < 0) {
dev_err(&client->dev, "注册中断失败: %d\n", ret);
return ret;
}
// 5. 创建并注册IIO Trigger
data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", client->name, indio_dev->id);
if (!data->trig) return -ENOMEM;
data->trig->dev.parent = &client->dev;
data->trig->ops = &mpu6050_trigger_ops;
data->trig->private_data = indio_dev;
ret = devm_iio_trigger_register(&client->dev, data->trig);
if (ret < 0) {
dev_err(&client->dev, "注册Trigger失败: %d\n", ret);
return ret;
}
// 将Trigger绑定到IIO设备
iio_trigger_set_drvdata(data->trig, indio_dev);
indio_dev->trig = data->trig;
// 6. 配置IIO Buffer
indio_dev->name = "mpu6050";
indio_dev->info = &mpu6050_iio_info;
indio_dev->channels = mpu6050_channels;
indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels);
indio_dev->dev.parent = &client->dev;
indio_dev->modes = INDIO_BUFFER_TRIGGERED; // 触发式Buffer模式(区别于轮询)
// 初始化Triggered Buffer(自动创建字符设备/dev/iio:deviceX)
ret = iio_triggered_buffer_setup(indio_dev, NULL, NULL, NULL);
if (ret < 0) {
dev_err(&client->dev, "初始化Buffer失败: %d\n", ret);
return ret;
}
// 7. 注册IIO设备
ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret < 0) {
iio_triggered_buffer_cleanup(indio_dev);
return ret;
}
return 0;
}
// 移除函数(可选:清理Buffer)
static void mpu6050_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
iio_triggered_buffer_cleanup(indio_dev);
}
// 更新I2C驱动结构体(添加remove函数)
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_id,
};
步骤 4:驱动编译(新增依赖)
需确保内核配置开启 IIO Buffer 和 Trigger 相关选项:
# 内核配置项(.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。
- 注册传感器 DRDY 中断,中断触发后调用
- 应用层核心:
- 打开
/dev/iio:deviceX字符设备,配置 Buffer 长度并启用 Trigger; - 通过
select/poll监听数据就绪,读取 Buffer 中的批量数据; - 数据格式需与驱动层
scan_index严格匹配,确保数据解析正确。
- 打开
- 优势:中断触发读取相比轮询,CPU 占用率更低,适合高频数据采集场景(如运动检测、惯性导航)。

2640

被折叠的 条评论
为什么被折叠?



