2. Linux IIO子系统概述
Linux IIO(Industrial I/O,工业输入/输出)子系统是内核为传感器、数据转换器(ADC/DAC)、编码器等设备提供的统一驱动框架。它解决了传统字符设备驱动在采样速率、触发模式、缓冲管理等方面的碎片化问题,成为嵌入式Linux中模拟量采集的标准抽象层。
2.1 IIO框架设计思想
IIO的设计遵循“设备-通道-触发-缓冲”四层分离原则,核心思想包括:
- 设备抽象:每个物理ADC/DAC芯片注册为一个
iio_dev设备,屏蔽硬件寄存器细节。 - 通道化:将多路输入/输出抽象为独立通道,每个通道可独立配置类型、量程、偏移。
- 触发驱动:采样动作由触发源(定时器、GPIO、硬件中断)驱动,与数据读取解耦。
- 缓冲传输:高速连续采样通过内核环形缓冲区传递,避免每次读取都陷入系统调用。
- 用户空间统一接口:通过
/sys/bus/iio/devices/iio:deviceX/暴露通道属性,通过字符设备节点/dev/iio:deviceX提供缓冲数据流。
设计目标:让驱动开发者只需关注硬件寄存器操作,而采样调度、数据同步、用户空间交互由IIO框架统一管理。
2.2 核心数据结构
IIO框架围绕三个核心数据结构构建:struct iio_dev、struct iio_chan_spec、struct iio_info。理解它们是编写IIO驱动的基础。
2.2.1 struct iio_dev — 设备实例
每个IIO设备对应一个iio_dev实例,由iio_device_alloc()分配,iio_device_register()注册。关键成员如下:
| 成员 | 类型 | 说明 |
|---|---|---|
id |
int | 设备编号,自动分配 |
dev |
struct device | 内核设备对象,用于sysfs和设备模型 |
channels |
struct iio_chan_spec * | 通道描述符数组,定义设备的所有输入/输出通道 |
num_channels |
unsigned int | 通道数量 |
info |
const struct iio_info * | 驱动回调函数集合 |
modes |
unsigned long | 支持的操作模式(单次/触发/缓冲) |
buffer |
struct iio_buffer * | 数据缓冲区,用于高速连续采样 |
trig |
struct iio_trigger * | 当前绑定的触发源 |
2.2.2 struct iio_chan_spec — 通道描述符
通道描述符定义了ADC的每个输入引脚或DAC的每个输出通道的属性。对于ADC驱动,最常用的配置如下:
struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE, // 通道类型:电压
.indexed = 1, // 使用索引号区分通道
.channel = 0, // 通道编号0
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | // 暴露原始值
BIT(IIO_CHAN_INFO_SCALE) | // 暴露缩放系数
BIT(IIO_CHAN_INFO_OFFSET), // 暴露偏移量
.scan_index = 0, // 扫描索引,用于缓冲模式
.scan_type = {
.sign = 'u', // 无符号
.realbits = 12, // 有效位数
.storagebits = 16, // 存储位数
.shift = 0, // 右移位数
},
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 1, // 通道编号1
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 1,
.scan_type = {
.sign = 'u',
.realbits = 12,
.storagebits = 16,
},
},
};
关键字段说明:
info_mask_separate决定哪些属性在sysfs中按通道独立显示;scan_index决定缓冲模式下数据在采样帧中的排列顺序。
2.2.3 struct iio_info — 驱动回调
驱动通过iio_info向框架注册硬件操作函数:
| 回调 | 原型 | 用途 |
|---|---|---|
read_raw |
int (*read_raw)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) | 读取通道原始值、缩放系数或偏移量 |
write_raw |
int (*write_raw)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) | 写入通道属性(如DAC输出值) |
set_trigger |
int (*set_trigger)(struct iio_dev *indio_dev, struct iio_trigger *trig) | 绑定/解绑触发源 |
update_scan_mode |
int (*update_scan_mode)(struct iio_dev *indio_dev, const unsigned long *scan_mask) | 使能/禁用缓冲模式下的通道扫描 |
2.3 用户空间接口
IIO子系统向用户空间提供两种访问方式:sysfs属性节点用于单次读取和配置,字符设备节点用于高速缓冲数据流。
2.3.1 sysfs接口
设备注册后,在/sys/bus/iio/devices/iio:deviceX/下生成以下典型属性:
| 属性文件 | 访问 | 说明 |
|---|---|---|
name |
只读 | 设备名称,与设备树中compatible匹配 |
in_voltage0_raw |
只读 | 通道0的ADC原始采样值(如0~4095) |
in_voltage0_scale |
只读 | 原始值到实际电压的缩放系数(如0.001) |
in_voltage0_offset |
只读 | 原始值偏移量(通常为0) |
in_voltage1_raw |
只读 | 通道1的ADC原始采样值 |
buffer/enable |
读写 | 使能/禁用缓冲模式(1使能,0禁用) |
buffer/length |
读写 | 缓冲区大小(字节数) |
scan_elements/in_voltage0_en |
读写 | 使能通道0参与缓冲扫描 |
trigger/current_trigger |
读写 | 查看/设置当前触发源 |
用户空间读取单次采样示例:
# 读取通道0原始值
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2048
# 读取缩放系数
cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale
0.001000
# 计算实际电压:2048 × 0.001 = 2.048 V
2.3.2 字符设备接口
当需要高速连续采样时,使用/dev/iio:deviceX字符设备。典型操作流程如下:
- 通过sysfs使能需要采样的通道(
scan_elements/in_voltage0_en写1)。 - 设置触发源(
trigger/current_trigger)。 - 使能缓冲(
buffer/enable写1)。 - 打开
/dev/iio:device0,使用read()读取数据帧。 - 数据帧格式由
scan_type定义,每个通道按scan_index顺序排列。
注意事项:缓冲模式下,
read()返回的是多个采样帧的连续数据,每个帧包含所有使能通道的采样值。解析时需按scan_type.storagebits对齐字节边界。
2.4 触发与缓冲机制
IIO的触发系统支持多种触发源:
- 定时器触发:
iio-trig-hrtimer提供高精度定时触发,适合固定频率采样。 - GPIO触发:
iio-trig-sysfs允许用户通过sysfs手动触发一次采样。 - 硬件触发:ADC芯片的EOC(转换结束)引脚直接触发,延迟最低。
缓冲模式下,数据流路径为:
硬件ADC → 触发信号 → 驱动读取数据 → iio_push_to_buffers() → 内核环形缓冲 → 用户空间 read()
性能对比:单次采样每次读取约50~100μs(含sysfs开销),缓冲模式可达1~10Msps(取决于ADC硬件和总线速度)。