news 2026/4/15 17:07:55

linux IIO驱动框架开发流程说明

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
linux IIO驱动框架开发流程说明

一、Linux IIO 框架基础

IIO(Industrial I/O)是 Linux 内核专为测量类 / 传感类设备设计的框架,区别于面向人机交互的 Input 框架,主要适配加速度计、陀螺仪、ADC/DAC、压力传感器等模拟 / 数字传感器。

1. IIO 核心组件
组件作用
struct iio_devIIO 设备核心结构体,代表一个 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。

四、总结

  1. 驱动层核心
    • 注册传感器 DRDY 中断,中断触发后调用iio_trigger_poll()通知 Buffer;
    • 创建 IIO Trigger 并绑定回调函数,回调中读取传感器数据并写入 Buffer;
    • 配置INDIO_BUFFER_TRIGGERED模式,替代轮询的INDIO_DIRECT_MODE
  2. 应用层核心
    • 打开/dev/iio:deviceX字符设备,配置 Buffer 长度并启用 Trigger;
    • 通过select/poll监听数据就绪,读取 Buffer 中的批量数据;
    • 数据格式需与驱动层scan_index严格匹配,确保数据解析正确。
  3. 优势:中断触发读取相比轮询,CPU 占用率更低,适合高频数据采集场景(如运动检测、惯性导航)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 15:32:50

CRNN OCR模型灰度发布:新版本无缝切换的方案

CRNN OCR模型灰度发布&#xff1a;新版本无缝切换的方案 &#x1f4d6; 项目背景与OCR技术演进 光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;是人工智能在视觉感知领域的重要应用之一。随着数字化转型加速&#xff0c;从发票扫描、证件录入到文档电…

作者头像 李华
网站建设 2026/4/15 17:07:10

语音合成API设计规范:Sambert-Hifigan的RESTful接口最佳实践

语音合成API设计规范&#xff1a;Sambert-Hifigan的RESTful接口最佳实践 &#x1f4cc; 背景与需求&#xff1a;中文多情感语音合成的技术演进 随着智能客服、有声阅读、虚拟主播等应用场景的爆发式增长&#xff0c;高质量语音合成&#xff08;Text-to-Speech, TTS&#xff09;…

作者头像 李华
网站建设 2026/4/15 17:06:27

用 Java 玩转本地大模型:Spring AI + Ollama 实现网页端实时对话

之前的文章里已经教会了大家怎么在本地安装ollama以及运行模型。接下来要开始做真正的JAVA AI应用了&#xff0c;大家准备好了吗&#xff1f; 最近玩本地大模型的朋友越来越多&#xff0c;但大多数人都是在命令行里和模型对话。说实话&#xff0c;这种方式有点反人类 ——体验远…

作者头像 李华
网站建设 2026/4/15 17:08:53

一键部署Llama Factory:告别复杂的环境配置

一键部署Llama Factory&#xff1a;告别复杂的环境配置 作为一名IT管理员&#xff0c;你可能经常需要为团队搭建各种开发环境。最近大模型微调需求激增&#xff0c;但面对PyTorch、CUDA、Transformers这些深度学习框架的复杂依赖&#xff0c;是否感到无从下手&#xff1f;本文将…

作者头像 李华
网站建设 2026/4/15 6:02:02

Llama Factory黑科技:如何用少量数据实现高质量微调

Llama Factory黑科技&#xff1a;如何用少量数据实现高质量微调 对于数据资源有限的小公司来说&#xff0c;想要利用AI技术提升业务效率往往面临一个难题&#xff1a;如何在少量数据的情况下&#xff0c;依然能获得不错的模型微调效果&#xff1f;今天我要分享的就是一个开源利…

作者头像 李华
网站建设 2026/4/15 17:08:52

车载语音系统备选:Sambert-Hifigan离线运行保障隐私与响应速度

车载语音系统备选&#xff1a;Sambert-Hifigan离线运行保障隐私与响应速度 引言&#xff1a;车载场景下的语音合成新需求 随着智能座舱技术的快速发展&#xff0c;车载语音交互已成为提升驾驶体验的核心功能之一。传统云依赖型语音合成&#xff08;TTS&#xff09;方案虽能提…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.