传感器驱动开发:从硬件时序到 Linux IIO 子系统

📅 2026/6/17 8:36:59
传感器驱动开发:从硬件时序到 Linux IIO 子系统
传感器驱动开发从硬件时序到 Linux IIO 子系统一、读一个寄存器没那么简单很多人觉得传感器驱动就是发个 I2C 读命令、拿回数据、转换一下单位几十行代码搞定。实际项目中传感器驱动反而是最容易出问题的环节。上电时序不满足导致芯片无法初始化、I2C 总线被其他设备拉低导致通信挂死、中断触发时机与数据就绪状态不同步——这些问题在数据手册里往往只有一行小字却能在生产环境中造成间歇性故障。核心问题不是读数据而是可靠地读数据。二、Linux IIO 子系统的数据流转传感器驱动在 Linux 系统中通常基于 IIOIndustrial I/O子系统实现。数据从硬件到用户空间的路径flowchart LR A[传感器硬件] --|I2C/SPI 总线| B[MCU/SoC 控制器] B --|硬件中断| C[Linux IRQ Handler] C -- D[IIO 触发器] D -- E[IIO 缓冲区: kfifo] E --|sysfs/chardev| F[用户空间应用] subgraph 内核空间 B C D E end subgraph 用户空间 F end G[设备树 DTS] -.-|平台设备注册| BIIO 子系统是 Linux 内核为 ADC、DAC、加速度计、陀螺仪等工业 I/O 设备提供的统一框架。传感器数据被抽象为通道channel每个通道有类型电压、加速度、温度等、索引和修饰词X/Y/Z 轴。IIO 触发器Trigger定义数据采集时机。定时触发器按固定频率采样数据就绪触发器在传感器发出 DRDY 信号时采样。后者更精确但需要正确配置中断引脚和极性。IIO 缓冲区使用 kfifo 存储采样数据用户空间通过/dev/iio:deviceX字符设备以 DMA 方式批量读取避免每次采样都陷入内核。设备树DTS描述传感器的硬件连接信息I2C 地址、中断引脚、供电引脚等。驱动通过设备树获取这些信息而非硬编码。三、I2C 加速度计驱动实现#include linux/module.h #include linux/i2c.h #include linux/iio/iio.h #include linux/iio/buffer.h #include linux/iio/triggered_buffer.h #include linux/iio/trigger_consumer.h #include linux/regmap.h #include linux/interrupt.h /* 传感器寄存器定义通用三轴加速度计 */ #define REG_WHO_AM_I 0x0F #define REG_CTRL1 0x20 /* 采样率、量程 */ #define REG_CTRL3 0x22 /* 中断配置 */ #define REG_STATUS 0x27 /* 数据就绪状态 */ #define REG_OUT_X_L 0x28 /* X轴低字节自动地址递增 */ #define WHO_AM_I_VAL 0x3B /* 芯片标识值 */ /* 传感器私有数据结构 */ struct accel_data { struct regmap *regmap; struct iio_trigger *trig; s64 timestamp; }; /* IIO 通道定义三轴加速度 时间戳 */ static const struct iio_chan_spec accel_channels[] { { .type IIO_ACCEL, .modified 1, .channel2 IIO_MOD_X, .info_mask_separate BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index 0, .scan_type { .sign s, .realbits 16, .storagebits 16, .endianness IIO_LE, }, }, { .type IIO_ACCEL, .modified 1, .channel2 IIO_MOD_Y, .info_mask_separate BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index 1, .scan_type { .sign s, .realbits 16, .storagebits 16, .endianness IIO_LE }, }, { .type IIO_ACCEL, .modified 1, .channel2 IIO_MOD_Z, .info_mask_separate BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index 2, .scan_type { .sign s, .realbits 16, .storagebits 16, .endianness IIO_LE }, }, IIO_CHAN_SOFT_TIMESTAMP(3), }; /* 触发缓冲区数据处理中断上下文中读取传感器数据 */ static irqreturn_t accel_trigger_handler(int irq, void *p) { struct iio_poll_func *pf p; struct iio_dev *indio_dev pf-indio_dev; struct accel_data *data iio_priv(indio_dev); u8 buf[8]; /* 6字节轴数据 2字节对齐 */ int ret; /* 批量读取三轴数据利用寄存器地址自动递增特性一次 I2C 传输读完 * 比分三次读取更高效且保证三轴数据的时间一致性 */ ret regmap_bulk_read(data-regmap, REG_OUT_X_L | 0x80, buf, 6); if (ret 0) { dev_err(indio_dev-dev, 传感器数据读取失败: %d\n, ret); goto done; } iio_push_to_buffers_with_timestamp(indio_dev, buf,>