从传感器到数据流手把手教你用Linux IIO驱动一个虚拟温度计附完整代码在嵌入式Linux开发中传感器数据采集是一个常见需求。工业I/OIIO子系统作为Linux内核的标准框架为各类传感器提供了统一的接口。本文将带你从零开始实现一个虚拟温度传感器的完整驱动并构建应用层数据采集方案。1. IIO驱动开发基础准备在开始编码前我们需要明确几个核心概念。IIO子系统采用设备-通道模型组织传感器数据每个物理传感器对应一个IIO设备而设备中的不同测量维度如三轴加速度计的X/Y/Z轴则表示为通道。典型的IIO驱动开发涉及以下关键结构体struct iio_dev { // 代表一个IIO设备 const struct iio_info *info; // 回调函数集合 struct iio_chan_spec const *channels; // 通道描述数组 int num_channels; // 通道数量 unsigned long modes; // 设备工作模式 // ...其他成员 }; struct iio_chan_spec { // 通道描述结构 enum iio_chan_type type; // 通道类型温度、加速度等 int channel; // 通道编号 int scan_index; // 缓冲区中的索引位置 struct { // 数据格式描述 char sign; // s有符号/u无符号 u8 realbits; // 有效位数 u8 storagebits; // 存储位数 } scan_type; // ...其他成员 };开发环境配置建议内核版本Linux 4.19推荐5.x系列工具链根据目标平台选择如arm-linux-gnueabihf调试工具iio_info查看IIO设备信息hexdump检查原始数据sysfs接口手动读写通道值2. 虚拟温度传感器驱动实现我们实现一个具有两个温度通道的虚拟设备模拟实际硬件行为。2.1 通道定义与数据格式首先定义通道规格明确数据属性#define NUM_CHANNELS 3 // 2个温度通道 1个比例因子通道 static const struct iio_chan_spec temp_channels[] { { // 通道0 .type IIO_TEMP, .channel 0, .info_mask_separate BIT(IIO_CHAN_INFO_RAW), .address 0, .scan_index 0, .scan_type { .sign u, .realbits 16, .storagebits 16, .shift 0, }, .indexed 1, }, { // 通道1 .type IIO_TEMP, .channel 1, .info_mask_separate BIT(IIO_CHAN_INFO_RAW), .address 1, .scan_index 1, .scan_type { /* 同通道0 */ }, .indexed 1, }, { // 比例因子通道 .type IIO_TEMP, .channel 0, .info_mask_separate BIT(IIO_CHAN_INFO_SCALE), .address 0, .scan_index -1, // 不参与缓冲区扫描 } };关键参数说明info_mask_separate定义通道支持的属性RAW原始值/SCALE比例因子scan_index缓冲区中的位置索引-1表示不参与缓冲realbits/storagebits确保与实际传感器数据手册一致2.2 核心回调函数实现实现read_raw回调为应用层提供数据static int temp_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { switch (mask) { case IIO_CHAN_INFO_RAW: // 读取原始值 /* 模拟温度值通道025°C通道130°C */ *val (chan-address 0) ? 2500 : 3000; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: // 读取比例因子 *val 83; // 0.083°C/LSB return IIO_VAL_INT; default: return -EINVAL; } } static const struct iio_info temp_info { .read_raw temp_read_raw, };温度值处理技巧内部以毫摄氏度为单位存储2500 25.00°C比例因子83表示0.083°C/LSB符合常见温度传感器特性实际项目中应根据传感器数据手册计算这些值2.3 设备注册与初始化完成驱动的probe函数注册IIO设备static int temp_probe(struct platform_device *pdev) { struct iio_dev *indio_dev; int ret; // 分配IIO设备结构体 indio_dev devm_iio_device_alloc(pdev-dev, 0); if (!indio_dev) return -ENOMEM; // 填充设备信息 indio_dev-name virtual_temp; indio_dev-channels temp_channels; indio_dev-num_channels NUM_CHANNELS; indio_dev-info temp_info; indio_dev-modes INDIO_DIRECT_MODE; // 使用直接访问模式 // 注册设备 ret devm_iio_device_register(pdev-dev, indio_dev); if (ret 0) { dev_err(pdev-dev, Failed to register IIO device\n); return ret; } return 0; }提示INDIO_DIRECT_MODE表示通过sysfs直接访问适合低速传感器。高速设备应考虑使用触发缓冲模式。3. 应用层数据采集方案驱动注册后用户空间可通过sysfs或字符设备访问数据。3.1 Sysfs接口使用驱动注册后自动生成以下接口/sys/bus/iio/devices/iio:deviceX/ ├── in_temp0_raw # 通道0原始值 ├── in_temp1_raw # 通道1原始值 └── in_temp0_scale # 比例因子读取温度的C语言示例#include stdio.h #include fcntl.h #include unistd.h int read_temp_channel(int channel) { char path[256]; int fd, temp_raw; float scale 0.083; // 已知比例因子 snprintf(path, sizeof(path), /sys/bus/iio/devices/iio:device0/in_temp%d_raw, channel); fd open(path, O_RDONLY); if (fd 0) { perror(Open failed); return -1; } char buf[16]; read(fd, buf, sizeof(buf)); close(fd); sscanf(buf, %d, temp_raw); return (int)(temp_raw * scale); // 转换为实际温度 }3.2 触发缓冲模式扩展对于需要高效采集的场景可以扩展为缓冲模式// 在probe函数中添加 indio_dev-modes | INDIO_BUFFER_TRIGGERED; // 配置缓冲区和触发回调 iio_triggered_buffer_setup(indio_dev, NULL, trigger_handler, NULL); // 触发处理函数 irqreturn_t trigger_handler(int irq, void *p) { struct iio_dev *indio_dev p; u16 buffer[2]; // 填充缓冲区 buffer[0] read_temp(0); buffer[1] read_temp(1); iio_push_to_buffers(indio_dev, buffer); return IRQ_HANDLED; }缓冲模式优势减少用户空间频繁读取的开销支持硬件触发和定时采样适合高速数据采集场景4. 调试与性能优化4.1 常用调试技巧查看设备树ls /sys/bus/iio/devices/读取原始数据cat /sys/bus/iio/devices/iio:device0/in_temp0_raw缓冲模式调试hexdump -C /dev/iio:device04.2 性能优化建议优化方向具体措施适用场景采样速率调整触发器频率高速数据采集功耗使用间歇模式电池供电设备精度校准比例因子高精度测量延迟启用DMA缓冲实时性要求高常见问题排查数据异常检查scan_type定义是否与传感器一致sysfs节点缺失确认info_mask_separate配置正确缓冲溢出增大buffer/length值5. 完整代码实现最后给出虚拟温度传感器的完整驱动代码#include linux/module.h #include linux/platform_device.h #include linux/iio/iio.h #define DRV_NAME virtual_temp #define NUM_CHANNELS 3 static const struct iio_chan_spec temp_channels[] { { /* 通道0 */ }, { /* 通道1 */ }, { /* 比例因子通道 */ } }; static int temp_read_raw(/* 参数 */) { /* 实现同前 */ } static const struct iio_info temp_info { .read_raw temp_read_raw, }; static int temp_probe(struct platform_device *pdev) { /* 实现同前 */ } static const struct of_device_id temp_of_match[] { { .compatible virtual,temp }, {} }; MODULE_DEVICE_TABLE(of, temp_of_match); static struct platform_driver temp_driver { .driver { .name DRV_NAME, .of_match_table temp_of_match, }, .probe temp_probe, }; module_platform_driver(temp_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(Virtual Temperature Sensor Driver);应用层测试程序#include stdio.h // ...其他头文件 int main() { printf(Channel 0: %d°C\n, read_temp_channel(0)); printf(Channel 1: %d°C\n, read_temp_channel(1)); return 0; }在实际项目中我曾遇到一个温度传感器数据漂移问题最终发现是realbits配置错误导致。将16位配置为12位后数据解析恢复正常。这提醒我们驱动开发必须严格遵循硬件数据手册的参数定义。