传感器驱动开发:从硬件时序到 Linux IIO 子系统
2026/6/17 8:36:51 网站建设 项目流程

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

一、读一个寄存器没那么简单

很多人觉得传感器驱动就是发个 I2C 读命令、拿回数据、转换一下单位,几十行代码搞定。实际项目中,传感器驱动反而是最容易出问题的环节。上电时序不满足导致芯片无法初始化、I2C 总线被其他设备拉低导致通信挂死、中断触发时机与数据就绪状态不同步——这些问题在数据手册里往往只有一行小字,却能在生产环境中造成间歇性故障。

核心问题不是"读数据",而是"可靠地读数据"。

二、Linux IIO 子系统的数据流转

传感器驱动在 Linux 系统中通常基于 IIO(Industrial 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] -.->|平台设备注册| B

IIO 子系统是 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,>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询