Linux IIO传感器驱动开发实战:从框架原理到SPI驱动实现

发布时间:2026/5/16 5:59:41

Linux IIO传感器驱动开发实战:从框架原理到SPI驱动实现 1. 项目概述从零构建一个IIO传感器驱动在嵌入式Linux开发中处理传感器数据是再常见不过的任务。无论是消费电子里的加速度计、陀螺仪还是工业环境中的温湿度、压力传感器最终都需要一个稳定、标准的接口将物理世界的模拟量转换成内核和应用层能理解的数字信息。早期大家可能各写各的驱动通过字符设备/dev/xxx暴露数据但这样缺乏统一性应用层适配起来很麻烦。Linux内核的Industrial I/O (IIO) 子系统就是为了解决这个问题而生的。它专门为模数转换器ADC和传感器设计提供了一套完整的内核框架。简单来说IIO在内核里为传感器数据建立了一个“标准仓库”并通过sysfs/sys/bus/iio/devices/和字符设备两种方式向用户空间提供统一、结构化的访问接口。这对于需要高精度数据采集、实时监控或者复杂数据融合如惯性测量单元IMU的应用场景至关重要。本文将以一个虚拟的SPI接口传感器为例手把手带你走一遍IIO驱动的完整创建流程。我们会从最基础的驱动框架搭建开始详细解析iio_dev结构体、通道Channel定义、数据读写回调函数一直到内核配置和用户空间测试。无论你是刚开始接触Linux驱动还是想系统理解IIO框架这篇内容都能给你提供可直接复现的参考。我会把我在实际项目中调试IIO驱动时踩过的坑、总结的技巧比如regmap的巧妙使用、通道scan_type的设置玄机、以及如何避免单位换算的混乱都毫无保留地分享出来。2. IIO驱动框架的核心设计思路在动手写代码之前我们必须先搞清楚IIO子系统的设计哲学和核心组件。这能帮助我们在后续实现中做出正确的设计决策而不是机械地复制粘贴代码。2.1 为什么选择IIO对比其他方案在IIO出现之前开发者处理传感器通常有几种方式直接实现字符设备驱动在驱动里实现file_operations应用层通过read()、write()、ioctl()来交互。这种方式最灵活但也最原始每个驱动都要自己定义数据格式和命令协议复用性差。使用Input子系统适合人机交互设备如触摸屏、按键。它主要上报“事件”比如EV_ABS绝对坐标、EV_KEY按键。对于需要连续、高精度读取数值的传感器如温度、压力Input子系统并不合适它缺乏对数据单位、精度、缩放因子的标准化描述。使用Hwmon子系统主要用于硬件监控如风扇转速、CPU温度。它更偏向于系统内部状态的监测其通道和属性相对固定扩展性不如IIO。IIO的优势恰恰在于它专为测量设备设计标准化的数据通道每个测量值如X轴加速度、温度、压力都被定义为一个“通道”iio_chan_spec。通道带有丰富的元数据包括类型IIO_ACCELIIO_TEMP、索引、数据格式是有符号还是无符号多少位、缩放因子等。这保证了应用层能无歧义地解析数据。灵活的用户空间接口Sysfs接口在/sys/bus/iio/devices/iio:deviceX/下生成一系列易读的文件。例如直接cat in_accel_x_raw可以读取原始ADC值cat in_accel_scale可以查看缩放比例。这对于调试、脚本控制和小型应用极其方便。Buffer与字符设备接口支持高性能、低延迟的连续数据流采集。数据通过/dev/iio:deviceX这个字符设备读取适合需要高速采样或实时处理的应用。事件支持可以定义阈值事件例如当温度超过某个值时驱动可以向上层发送事件应用层可以通过poll或select来监听。触发器支持数据采集可以由外部硬件触发器如GPIO边沿或内部定时器触发器来启动这对于多传感器同步采样非常关键。所以当你面对一个ADC芯片或数字传感器时只要它需要将模拟量或数字量以标准化的方式提供给系统IIO通常是首选框架。2.2 驱动基础框架的选择SPI、I2C还是PlatformIIO驱动本身不直接与硬件交互它需要一个“载体”或“总线驱动”来负责底层的通信。这个载体就是你的驱动基础框架。SPI/I2C框架绝大多数独立传感器芯片的选择。你的传感器通过SPI或I2C总线连接到主控SOC。在这种情况下你的IIO驱动将作为一个SPI或I2C客户端驱动来实现。在驱动代码中你需要定义spi_driver或i2c_driver结构体并在其probe函数中完成IIO设备的初始化和注册。这是最常见、最标准的方式。Platform框架当ADC模块是SOC内部集成的而不是外部芯片时使用。例如很多MCU或应用处理器内部都集成了多通道ADC。这些ADC通常通过内存映射寄存器MMIO进行控制没有标准的片上总线。这时你需要通过Platform驱动来匹配设备树Device Tree中描述的ADC节点并在probe函数中初始化IIO设备。选择依据很简单看硬件连接。外部芯片走SPI/I2C内部IP核走Platform。在本文的示例中我们以更通用的SPI接口传感器为例进行讲解其原理完全适用于I2C。2.3 核心数据结构关系图概念层面理解以下几个核心结构体的关系是写好IIO驱动的关键struct spi_device/struct i2c_client代表一个具体的SPI/I2C从设备内核在设备匹配时传入。它包含了总线号、片选、通信频率等硬件信息。struct iio_devIIO子系统的核心对象。它描述了一个IIO设备实例包含了设备信息、通道数组、操作函数集iio_info以及一个指向私有数据的指针。struct iio_chan_spec描述一个数据通道。一个IIO设备可以有多个通道例如三轴加速度计就有X, Y, Z三个通道。这个结构体定义了通道的类型、索引、数据格式等信息。struct iio_info一个包含回调函数指针的结构体。驱动需要实现其中的read_raw、write_raw等函数IIO内核会在用户空间访问sysfs属性时调用这些函数。你的私有设备结构体例如struct xxx_dev这是一个由驱动开发者自定义的结构体用于存放该设备实例的所有私有数据如spi_device指针、regmap句柄、互斥锁、校准参数等。它通过iio_priv()与iio_dev关联。它们的关系可以概括为总线驱动SPI在probe时发现设备创建并初始化一个iio_dev。iio_dev中包含了描述数据从哪里来的iio_chan_spec数组以及描述数据如何读写的iio_info回调函数集。而驱动操作硬件所需的所有上下文信息都存放在你的私有结构体中并“挂”在iio_dev上。3. 一步步拆解IIO驱动的实现现在我们进入实战环节。我将以一个虚拟的“XYZ123三轴加速度计”SPI传感器为例展示一个完整IIO驱动的骨架代码并逐一解释每个部分的实现要点和注意事项。3.1 定义设备私有结构体与初始化这是驱动的“数据中心”所有硬件操作依赖的信息都放在这里。#include linux/iio/iio.h #include linux/spi/spi.h #include linux/regmap.h #include linux/mutex.h /* 自定义设备结构体 */ struct xyz123_dev { struct spi_device *spi; /* SPI设备句柄用于底层通信 */ struct regmap *regmap; /* regmap句柄用于寄存器访问推荐方式*/ struct mutex lock; /* 互斥锁防止并发访问寄存器 */ int16_t calib_offset[3]; /* 校准偏移量用于X, Y, Z三轴 */ u32 scale_uv; /* 比例因子例如 488 (微伏/LSB) */ /* 可以根据需要添加其他状态变量如电源状态、采样率等 */ };为什么需要这些成员spi最基础的通信方式。你可以直接用spi_read/spi_write但在寄存器操作的传感器上不推荐。regmap强烈推荐使用。Regmap是内核提供的一个寄存器映射抽象层它统一了SPI/I2C等总线的寄存器访问接口并内置了缓存、调试fs接口等特性。它能极大简化驱动代码并减少错误。lock至关重要。IIO的sysfs接口是并发访问的。如果两个进程同时调用你的read_raw函数去读取传感器而底层SPI通信不是线程安全的就会导致数据错乱或总线冲突。必须用互斥锁保护对硬件的每一次访问。calib_offset,scale_uv这些是典型的传感器参数。校准数据可能来自OTP一次性可编程存储器比例因子由数据手册决定。将它们放在结构体中方便管理和使用。3.2 构建通道描述数组通道是IIO的灵魂它精确地告诉系统“你能提供什么数据”。#include linux/iio/types.h /* * 通道数组 * 描述一个三轴加速度计 */ static const struct iio_chan_spec xyz123_channels[] { { .type IIO_ACCEL, /* 通道类型加速度 */ .modified 1, /* 使用‘modified’标识配合 .channel2 */ .channel2 IIO_MOD_X, /* 修饰符X轴 */ .info_mask_separate BIT(IIO_CHAN_INFO_RAW) | /* 单独属性raw */ BIT(IIO_CHAN_INFO_SCALE), /* 单独属性scale */ .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SAMP_FREQ), /* 同类型通道共享属性采样率 */ .scan_index 0, /* 在buffer中的索引 */ .scan_type { /* 定义数据在buffer中的格式 */ .sign s, /* 有符号数 */ .realbits 12, /* 有效数据位 */ .storagebits 16, /* 存储位数通常为2的整数倍字节*/ .shift 0, /* 数据在存储字中的右移位数 */ .endianness IIO_CPU, /* 字节序通常为CPU字节序*/ }, }, { .type IIO_ACCEL, .modified 1, .channel2 IIO_MOD_Y, .info_mask_separate BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index 1, .scan_type { .sign s, .realbits 12, .storagebits 16, .shift 0, .endianness IIO_CPU, }, }, { .type IIO_ACCEL, .modified 1, .channel2 IIO_MOD_Z, .info_mask_separate BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index 2, .scan_type { .sign s, .realbits 12, .storagebits 16, .shift 0, .endianness IIO_CPU, }, }, /* 可以添加一个温度通道示例 */ { .type IIO_TEMP, .info_mask_separate BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), .scan_index -1, /* 如果不需要加入buffer设为-1 */ }, };关键字段解析与避坑指南.type和.channel2共同确定通道的唯一标识。IIO_ACCELIIO_MOD_X就代表X轴加速度。IIO_TEMP类型通常不需要channel2。.info_mask_separate和.info_mask_shared_by_type这两个掩码决定了sysfs下会生成哪些属性文件。_separate每个通道独有的属性。例如in_accel_x_rawin_accel_x_scale。_shared_by_type同一类型IIO_ACCEL的所有通道共享的属性。例如in_accel_sampling_frequency 设置它会影响所有三个轴。常用的IIO_CHAN_INFO_*有RAW: 原始ADC数值。SCALE: 比例因子用于将RAW值转换为有意义的物理量。OFFSET: 偏移量用于校准。SAMP_FREQ: 采样频率。PROCESSED: 处理后的值已应用scale和offset。.scan_index和.scan_type这是为IIO Buffer连续数据流功能服务的。scan_index指定该通道数据在buffer数据流中的顺序从0开始。如果该通道不参与buffer数据流比如你只想通过sysfs偶尔读取设为-1。scan_type定义了该通道的原始数据在buffer中是如何打包的。这里是最容易出错的地方必须与数据手册和芯片实际输出严格对应。realbits传感器实际的有效位数。比如一个12位ADC这里填12。storagebits存储这些有效位所用的位数。通常为了对齐字节会用16位来存储12位数据。这决定了你从SPI读出的u16数据中哪些位是有效的。shift有效数据在存储字中的偏移。如果12位数据在16位字的低12位shift0如果在高12位shift416-12。sign数据是有符号s还是无符号u。加速度值通常有正负所以用有符号。endianness数据的字节序。大部分SPI传感器是大端BE而我们的CPU是小端LE。这里通常填IIO_BE如果传感器是大端内核会帮你做转换。如果不确定填IIO_CPU但需要自己在驱动里处理字节序。实操心得在调试一个新传感器驱动时我强烈建议先忽略Buffer功能即把所有通道的scan_index设为-1。集中精力先让sysfs接口工作起来read_raw。等sysfs读写稳定后再根据数据手册仔细配置scan_type来启用Buffer。同时务必用逻辑分析仪抓取SPI波形确认数据的实际格式和字节序这是配置scan_type的唯一可靠依据。3.3 实现核心回调函数read_raw, write_rawiio_info中的回调函数是驱动与用户空间sysfs交互的桥梁。/* * iio_info 结构体变量 */ static const struct iio_info xyz123_info { .read_raw xyz123_read_raw, .write_raw xyz123_write_raw, /* .write_raw_get_fmt 不是必须的只有当需要特殊处理用户输入格式时才实现 */ };3.3.1 read_raw 函数详解当用户cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw时内核最终会调用这个函数。static int xyz123_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct xyz123_dev *data iio_priv(indio_dev); // 获取私有数据 int ret 0; u16 raw_data 0; __le16 buf; // 用于SPI接收假设传感器输出是小端 mutex_lock(data-lock); // 加锁 switch (mask) { case IIO_CHAN_INFO_RAW: /* 1. 根据通道选择读取哪个轴的数据 */ switch (chan-channel2) { case IIO_MOD_X: ret regmap_bulk_read(data-regmap, REG_ACCEL_X_OUT_H, buf, 2); break; case IIO_MOD_Y: ret regmap_bulk_read(data-regmap, REG_ACCEL_Y_OUT_H, buf, 2); break; case IIO_MOD_Z: ret regmap_bulk_read(data-regmap, REG_ACCEL_Z_OUT_H, buf, 2); break; default: ret -EINVAL; goto out_unlock; } if (ret 0) goto out_unlock; /* 2. 数据转换从原始u16到有符号的数值 */ /* 假设传感器输出是12位有符号数存储在16位字的低12位 */ raw_data le16_to_cpu(buf); /* 将12位有符号数扩展到16位或32位系统有符号数 */ *val sign_extend32(raw_data, 11); // 11 12 - 1 /* 3. 应用校准偏移可选*/ // *val >static int xyz123_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct xyz123_dev *data iio_priv(indio_dev); int ret 0; u8 reg_val; if (iio_buffer_enabled(indio_dev)) // 重要检查 return -EBUSY; // 如果Buffer正在运行禁止修改配置 mutex_lock(data-lock); switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: /* 设置采样频率val是用户传入的Hz值 */ if (val 1 || val 1000) { ret -EINVAL; break; } /* 根据val计算要写入的寄存器值 */ reg_val calculate_sample_rate_reg(val); ret regmap_write(data-regmap, REG_CTRL2, reg_val); if (ret 0) >static int xyz123_probe(struct spi_device *spi) { struct device *dev spi-dev; struct xyz123_dev *data; struct iio_dev *indio_dev; struct regmap_config regmap_config; int ret; /* 1. 申请并关联 iio_dev 与 私有数据 */ indio_dev devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data iio_priv(indio_dev); // 从iio_dev获取私有数据指针 >static int xyz123_remove(struct spi_device *spi) { struct iio_dev *indio_dev spi_get_drvdata(spi); struct xyz123_dev *data iio_priv(indio_dev); /* 1. 如果支持Buffer需要先停止所有触发器和Buffer */ // iio_triggered_buffer_cleanup(indio_dev); /* 2. 将传感器置于低功耗模式可选但推荐*/ xyz123_power_down(data); /* 3. devm_ 系列函数会自动释放 iio_dev 和 regmap 等资源 所以我们不需要手动释放。互斥锁也不需要手动销毁。*/ dev_info(spi-dev, %s removed\n, indio_dev-name); return 0; }注意由于我们使用了devm_iio_device_register在驱动卸载或设备断开时内核会自动注销IIO设备并释放iio_dev内存。remove函数的主要职责是执行必要的硬件反初始化如进入睡眠模式以及清理那些非devm_管理的资源如果使用了传统iio_device_alloc和iio_device_register则需要手动调用iio_device_unregister和iio_device_free。3.5 驱动模块的声明与编译最后不要忘记驱动模块的“身份证”/* 设备ID表用于匹配 */ static const struct spi_device_id xyz123_id[] { { xyz123, 0 }, { } }; MODULE_DEVICE_TABLE(spi, xyz123_id); /* 设备树匹配表 */ #ifdef CONFIG_OF static const struct of_device_id xyz123_of_match[] { { .compatible vendor,xyz123, }, { } }; MODULE_DEVICE_TABLE(of, xyz123_of_match); #endif /* SPI驱动结构体 */ static struct spi_driver xyz123_driver { .driver { .name xyz123, .of_match_table of_match_ptr(xyz123_of_match), .owner THIS_MODULE, }, .probe xyz123_probe, .remove xyz123_remove, .id_table xyz123_id, }; module_spi_driver(xyz123_driver); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(IIO driver for XYZ123 Accelerometer); MODULE_LICENSE(GPL v2);4. 内核配置与用户空间验证驱动代码写好了下一步是让它跑起来。4.1 内核配置要点如项目正文所述除了使能CONFIG_IIO还需要根据你的需求使能相关子选项。使用make menuconfigDevice Drivers --- * Industrial I/O support --- [*] Enable buffer support within IIO (如果要用Buffer功能) * Industrial I/O buffering based on kfifo [*] IIO callback buffer for data triggers (如果要用软件/硬件触发器) -*- Enable triggered sampling support (同上)我的建议在驱动开发初期可以先不选Buffer和Trigger相关选项只保留最基本的IIO核心支持。等sysfs调试通过后再根据需要使能它们这样可以减少内核配置的复杂性对调试的干扰。4.2 编译与加载编译将你的驱动文件如xyz123.c放入内核drivers/iio/accel/目录按传感器类型选择并修改该目录下的Kconfig和Makefile。或者作为外部模块编译make -C /path/to/your/kernel/source M$(pwd) modules加载insmod xyz123.ko。查看设备加载成功后查看/sys/bus/iio/devices/目录。$ ls /sys/bus/iio/devices/ iio:device0 $ cd /sys/bus/iio/devices/iio:device0 $ ls name in_accel_scale in_accel_x_raw in_accel_y_raw in_accel_z_raw uevent看到以通道名命名的*_raw和*_scale文件就说明驱动框架基本成功了。4.3 用户空间Sysfs操作实录现在你可以像操作普通文件一样与传感器交互读取X轴原始值$ cat in_accel_x_raw 1245读取比例因子假设驱动返回的是IIO_VAL_INT_PLUS_MICRO$ cat in_accel_scale 0.0488000计算实际物理值物理值 raw*scale。例如1245 * 0.0488 ≈ 60.756 mg注意单位可能是g或m/s^2看驱动实现。设置采样率如果驱动实现了write_raw$ cat in_accel_sampling_frequency 100 $ echo 50 in_accel_sampling_frequency $ cat in_accel_sampling_frequency 50调试利器iio_info和iio_readdev。安装iio-tools包后可以使用这些命令行工具更方便地查看和操作IIO设备。$ iio_info # 列出所有IIO设备及其通道、属性 $ iio_readdev -s 10 -b 256 iio:device0 # 从device0连续读取10次数据buffer大小为256样本5. 进阶话题与避坑经验总结5.1 关于Regmap的使用技巧缓存策略REGCACHE_RBTREE适用于寄存器数量不多不少的情况查询效率高。如果寄存器非常少32可以用REGCACHE_FLAT如果寄存器只写不读用REGCACHE_NONE。寄存器读写函数在驱动中永远使用regmap_read/regmap_write/regmap_update_bits等函数而不是直接调用spi_read/spi_write。Regmap能帮你处理并发、字节序、以及提供调试接口/sys/kernel/debug/regmap/。调试如果寄存器读写有问题首先检查regmap_config中的reg_bits、val_bits、max_register是否正确。然后可以挂载debugfs查看/sys/kernel/debug/regmap/spiX.0/registers来确认写入的值是否正确。5.2 单位换算的“坑”这是IIO驱动最容易出错的地方之一。IIO内核对于每种通道类型有预期的单位。例如IIO_ACCEL米每二次方秒 (m/s²)IIO_TEMP毫摄氏度 (milli degree Celsius)IIO_PRESSURE千帕斯卡 (kilo Pascal)IIO_PROXIMITY通常为厘米 (cm) 或布尔值。你的数据手册给出的灵敏度单位往往是mg/LSB,LSB/°C,Pa/LSB。你需要进行两步转换转换为标准单位例如4 mg/LSB-0.004 g/LSB-0.004 * 9.80665 ≈ 0.0392266 m/s² per LSB。匹配IIO的返回值格式0.0392266是一个小数。在read_raw的IIO_CHAN_INFO_SCALEcase中你需要决定如何返回。IIO_VAL_INT_PLUS_MICROval0, val239227是常用选择因为它能提供足够的精度。一个检查方法加载驱动后用iio_info查看通道的scale属性。如果单位看起来非常离谱比如加速度的scale是0.001那很可能单位换算错了。5.3 实现Buffer数据流功能当需要高速连续采样时就需要实现Buffer。这涉及几个额外步骤设置indio_dev-modes添加INDIO_BUFFER_TRIGGERED标志。实现Buffer回调函数主要是.read回调从硬件读取数据并推送到buffer和.postenable/.predisable回调在Buffer开启/关闭前后进行硬件配置。设置触发器可以是内核的hrtimer软件定时器也可以是外部GPIO中断。需要实现.set_trigger_state回调。配置scan_type这是关键必须精确匹配传感器数据流中每个通道数据的格式和顺序。错误配置会导致读出的数据全是乱码。建议先彻底理解并调通sysfs接口再着手实现Buffer。可以找一个内核中已有的、传感器类型相近的驱动如drivers/iio/accel/bmc150-accel.c作为参考。5.4 设备树Device Tree绑定为了让内核在启动时自动加载你的驱动需要编写设备树绑定。一个简单的SPI加速度计节点可能如下所示spi1 { status okay; cs-gpios gpio 10 GPIO_ACTIVE_LOW; accelerometer0 { compatible vendor,xyz123; reg 0; spi-max-frequency 10000000; vdd-supply vdd_3v3; vddio-supply vdd_3v3; /* 可选中断引脚用于数据就绪或事件触发 */ // interrupt-parent gpio; // interrupts 25 IRQ_TYPE_EDGE_RISING; }; };在驱动中你可以使用devm_regulator_get()来获取vdd-supply和vddio-supply实现电源管理。使用devm_gpiod_get_optional()来获取可选的中断GPIO。5.5 调试与问题排查清单驱动加载失败probe返回错误检查dmesg看具体错误码。检查SPI总线编号、片选、频率是否与设备树匹配。检查regmap初始化是否成功。检查硬件初始化如芯片ID读取是否通过。Sysfs属性文件不存在检查indio_dev-channels和indio_dev-num_channels是否正确设置。检查通道的info_mask_separate和info_mask_shared_by_type是否设置了正确的BIT。确认驱动已成功注册dmesg中应有成功信息。读取*_raw返回权限错误或I/O错误检查read_raw函数是否对应用户请求的maskIIO_CHAN_INFO_RAW实现了处理。在read_raw函数中添加printk看是否被调用。检查SPI通信函数regmap_read的返回值确认硬件读写是否成功。检查互斥锁确认没有死锁。尝试暂时注释掉锁看问题是否消失仅用于调试。读取的数据值不对全0、全1、跳动大首先用逻辑分析仪抓SPI波形这是最直接的证据。确认发送的命令和返回的数据是否符合数据手册。检查read_raw函数中的数据转换逻辑特别是符号扩展和位操作。检查scan_type中的realbits、storagebits、shift、endianness是否与SPI数据流完全匹配。检查传感器的电源和参考电压是否稳定。设置属性如采样率不生效检查write_raw函数是否实现并且mask判断正确。检查write_raw中是否调用了iio_buffer_enabled()检查并返回了-EBUSY。在write_raw中添加printk打印要写入的寄存器地址和值并用逻辑分析仪确认该值是否被正确写入传感器。Buffer功能无法工作或数据错乱确认内核配置已使能IIO_BUFFER和IIO_TRIGGER。仔细核对每个通道的scan_index是否连续且唯一scan_type是否100%准确。在Buffer的.read回调中打印出从硬件读取的原始数据与预期格式对比。驱动开发是一个反复调试的过程。从最简单的读取芯片ID开始逐步增加功能。善用printk、dev_dbg、逻辑分析仪和内核的调试工具如regmap的debugfs能帮你快速定位问题所在。记住IIO框架虽然复杂但它提供的标准化接口对于整个系统生态的价值是巨大的一旦跑通上层应用开发将变得异常轻松。

相关新闻