IIO子系统

IIO(Industrial I/O)子系统是Linux中对于类ADC传感器(温湿度、电压、电流、IMU…)数据采集所提供的一个框架

总览

在Linux内核中,IIO同样遵循着“驱动”分层的理念,一个完整的IIO设备的驱动可以分为以下几层

image-20260125192238798
  • iio-core:

    • 提供统一的用户空间接口(/sys/bus/iio/iio:deviceX/和字符设备)
    • 管理IIO子系统的初始化和退出
    • 实现IIO设备的注册与注销机制
    • 定义标准的IIO操作数据结构(iio_info, iio_dev等)
  • iio device driver:这一层直接与硬件交互,是驱动开发者的工作重点

    • 实现iio_info结构体中的回调函数(read_raw, write_raw, read_avail等)

    • 处理与传感器芯片的通信(I2C/SPI等)

    • 管理传感器的配置(量程、采样率、滤波器等)

  • iio-buffer:负责高效的数据采集和传输

    • 管理硬件和软件缓冲区

    • 处理数据从内核到用户空间的传输

    • 支持poll()select()等异步I/O机制

    • 可配置的缓冲区长度和水位标记

  • iio-trigger:提供灵活的数据采集触发机制

    • 硬件触发器:外部GPIO信号、定时器中断等
    • 软件触发器:sysfs触发
    • 触发器消费者机制:多个设备可响应同一触发器

典型的数据流向:

1
2
3
4
5
1.配置:用户通过sysfs配置传感器参数
2.触发:配置触发器(定时或外部事件)
3.采集:触发器启动数据采集
4.缓冲:数据存入IIO缓冲区
5.读取:用户空间从字符设备读取数据

参考链接:

核心概念

相关数据结构的拓扑关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
iio_dev (设备实例)
|
|-- iio_info (回调函数)
| |-- read_raw()
| |-- write_raw()
| |-- update_scan_mode()
| `-- ...
|
|-- iio_chan_spec[] (通道数组)
| |-- type, channel, scan_index
| |-- info_mask_* (属性掩码)
| |-- scan_type (数据格式)
| `-- event_spec (事件规格)
|
|-- iio_buffer_setup_ops (缓冲区操作)
| |-- preenable(), postenable()
| `-- predisable(), postdisable()
|
`-- active_scan_mask (活动通道掩码)

iio_dev

iio_dev代表一个IIO设备实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include/linux/iio/iio.h
struct iio_dev {
int modes; // 设备支持的模式
int currentmode; // 当前模式
struct device dev; // 内嵌的device结构
int id; // 设备ID
const char *name; // 设备名称

const struct iio_info *info; // 设备信息和方法
const struct iio_buffer_setup_ops *setup_ops; // 缓冲区设置操作

struct iio_channel *channels; // 通道数组
int num_channels; // 通道数量

struct list_head buffer_list; // 缓冲区链表
int scan_bytes; // 扫描字节数

const unsigned long *available_scan_masks; // 可用的扫描掩码

const unsigned int *active_scan_mask; // 活动扫描掩码
bool scan_timestamp; // 是否包含时间戳
struct attribute_group *attrs; // 设备属性组
struct attribute_group *event_interface_attrs; // 事件接口属性
};
  • modes:设备的采集模式
1
2
3
4
5
#define INDIO_DIRECT_MODE		0x01  // 直接读取模式
#define INDIO_BUFFER_TRIGGERED 0x02 // 缓冲区触发模式
#define INDIO_BUFFER_SOFTWARE 0x04 // 软件缓冲区
#define INDIO_BUFFER_HARDWARE 0x08 // 硬件缓冲区
#define INDIO_EVENT_CLASS 0x10 // 支持事件

image-20260124194504595

  • 只有设置了INDIO_BUFFER_*模式的设备才能使用Buffer
  • 直接模式(INDIO_DIRECT_MODE)是互斥的,不能与Buffer模式共存

不使用Buffer:

1
用户读取 → 驱动立即读硬件 → 返回单次数据

使用Buffer:

1
用户启用Buffer → 用户触发/定时/硬件自动触发 → 驱动批量读硬件 → 数据入Buffer → 用户读取Buffer

iio_info

直接读取模式下,用户空间和驱动的交互流程:

1
用户空间操作sysfs节点 → 内核调用属性show/store → 调用iio_info回调 → 驱动处理 → 返回结果

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
用户:cat /sys/.../in_voltage0_raw

IIO核心:iio_chan_sysfs_show()

解析出:通道=0, 属性类型=IIO_CHAN_INFO_RAW

调用:indio_dev->info->read_raw()

驱动:adc_read_raw()

驱动读取硬件寄存器

返回数据 → 格式化 → 用户空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//include/linux/iio/iio.h
struct iio_info {
struct module *driver_module; // 驱动模块
const struct attribute_group *attrs; // 驱动特定属性

// 读取原始值(必须实现)
int (*read_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask);

// 写入原始值
int (*write_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask);

// 读取可用值范围
int (*read_avail)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask);

// 写入原始值(带可用值)
int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long mask);

// 读取事件值
int (*read_event_value)(struct iio_dev *indio_dev,
u64 event_code, int *val);

// 写入事件值
int (*write_event_value)(struct iio_dev *indio_dev,
u64 event_code, int val);

// 验证事件
int (*validate_trigger)(struct iio_dev *indio_dev,
struct iio_trigger *trig);

// 更新缓冲区
int (*update_scan_mode)(struct iio_dev *indio_dev,
const unsigned long *scan_mask);

// 调试寄存器访问
int (*debugfs_reg_access)(struct iio_dev *indio_dev,
unsigned reg, unsigned writeval,
unsigned *readval);
};

通道

定义

IIO通道是IIO框架中表示单个传感器数据流的抽象。每个通道对应一个物理量(如温度、电压、加速度等)的测量点。一个传感器设备可以有多个通道(如三轴加速度计有X、Y、Z三个通道)

作用

  • 定义数据类型:指定测量的是什么物理量(电压、温度、加速度等)
  • 描述数据格式:指定数据如何编码(位数、符号、字节序等)
  • 配置属性:控制哪些sysfs属性对该通道可用
  • 组织数据流:定义在Buffer中的数据排列顺序

核心数据结构

iio_chan_spec定义设备的每个数据通道,核心层会根据iio_chan_spec字段自动在sysfs下创建属性文件,并绑定showstore方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//include/linux/iio/iio.h
struct iio_chan_spec {
enum iio_chan_type type; // 通道类型
int channel; // 通道编号(用于区分同一类型的多个通道)
int channel2; // 辅助通道编号
unsigned long address; // 地址
int scan_index; // 在扫描中的索引
struct {
char sign; // 符号('s'/'u')
u8 realbits; // 实际位数
u8 storagebits; // 存储位数
u8 shift; // 位移
u8 repeat; // 重复次数
enum iio_endian endianness; // 字节序
} scan_type; // 扫描类型
long info_mask_separate; // 独立属性掩码
long info_mask_shared_by_type; // 同类型共享属性
long info_mask_shared_by_dir; // 同方向共享属性
long info_mask_shared_by_all; // 所有通道共享属性
const struct iio_event_spec *event_spec; // 事件规格
unsigned int num_event_specs; // 事件规格数量
const struct iio_chan_spec_ext_info *ext_info; // 扩展信息
const char *extend_name; // 扩展名称
const char *datasheet_name; // 数据手册名称
unsigned modified:1; // 是否修改
unsigned indexed:1; // 是否有索引
unsigned output:1; // 输出通道
unsigned differential:1; // 差分通道
};

关键字段:

  • type:最重要的字段,定义通道类型,决定了sysfs属性路径(如in_voltage0_raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum iio_chan_type {
IIO_VOLTAGE, // 电压
IIO_CURRENT, // 电流
IIO_POWER, // 功率
IIO_ACCEL, // 加速度
IIO_ANGL_VEL, // 角速度
IIO_MAGN, // 磁场
IIO_LIGHT, // 光强
IIO_INTENSITY, // 强度
IIO_PROXIMITY, // 接近
IIO_TEMP, // 温度
IIO_INCLI, // 倾角
IIO_ROT, // 旋转
IIO_ANGL, // 角度
IIO_TIMESTAMP, // 时间戳
// ... 更多类型
};
  • channel:通道编号,用于区分同一类型的多个通道

    • 如三轴加速度计的X=0、Y=1、Z=2
  • indexed:布尔标志

    • 为true时,通道名称会包含channel编号
    • 如in_voltage0而不是in_voltage
  • 差分通道:differential=1

    • 表示测量的是两个输入之差
    • 通道名称会包含-(如in_voltage0-voltage1
  • 输出通道:output=1

    • 表示通道是输出而非输入
    • 名称前缀为out_而不是in_
  • channel2的通道:

    • 用于差分测量或调制信号
    • 第二个通道编号

IIO设备在sysfs下有哪些属性由下面这几个字段控制:

一个物理量通常由多个属性,比如一个X轴加速度有RAW和SCALE2个属性,分离它们让用户既可以直接用物理量,也能获取原始数据做高级处理

  • info_mask_separate:每个通道独立的属性

    • rawoffsetscale
    • 路径:in_voltage0_rawin_voltage1_raw
  • info_mask_shared_by_type:同类型通道共享的属性

    • 如所有电压通道共享的scale
    • 路径:in_voltage_scale
  • info_mask_shared_by_dir:同方向通道共享的属性

    • 如所有输入通道共享的属性
  • info_mask_shared_by_all:所有通道共享的属性

    • sampling_frequency

Buffer子系统

定义

Buffer是IIO的数据暂存区,批量存储多个通道、多个样本的数据。使用Buffer可以实现:多个数据存到缓冲区后一次性给用户空间,减少系统调用次数

IIO Buffer不会自己填充,必须由Trigger触发IIO数据读取后,才会被填充。它本质是内核中的一个环形队列,数据在 Buffer 中是按“扫描顺序(Scan Order)”排列的。比如使能了通道 0、2 和时间戳,Buffer 中的一帧数据结构可能是:[Ch0][Ch2][Padding][Timestamp],内核通过 kfifo 机制处理并发读写,确保采集不丢包

IIO Scan Mask (扫描掩码)决定了哪些通道的数据会被放入 Buffer,假如我们需要 5 个通道,通过 Sysfs 接口(scan_elements/ in_voltageX_en),用户可以动态选择需要开启的通道,驱动程序根据 active_scan_mask 来决定读取哪些寄存器

核心数据结构

1
2
3
4
5
6
7
8
9
10
11
// include/linux/iio/buffer.h

//缓冲区操作回调,管理数据缓冲区
struct iio_buffer_setup_ops {
int (*preenable)(struct iio_dev *); // 启用前回调
int (*postenable)(struct iio_dev *); // 启用后回调
int (*predisable)(struct iio_dev *); // 禁用前回调
int (*postdisable)(struct iio_dev *); // 禁用后回调
bool (*validate_scan_mask)(struct iio_dev *indio_dev,
const unsigned long *scan_mask);
};

Trigger子系统

定义

Trigger是IIO框架中的数据采集触发器。IIO设备不会”自动”读取数据,必须被Trigger驱动。它决定”何时“读取数据,而不是”如何”读取

Trigger可以基于:

  • 定时器(固定频率)

  • 外部信号(GPIO中断)

  • 软件命令

  • 其他传感器的事件

注意:

  • Trigger是独立设备:有自己的设备节点,不依赖具体传感器
  • 多对多关系:一个Trigger可触发多个设备,一个设备可用多个Trigger
  • current_trigger是符号链接:指向实际Trigger设备
  • 必须Buffer模式:只有支持Buffer的IIO设备才有trigger/目录

作用

  • 同步采集:多个传感器同时采样

  • 事件驱动:只在需要时采集,节省功耗

  • 精确时序:保证采样时间的准确性

  • 数据关联:多传感器数据时间对齐

类型

trigger的类型确定于iio_devmode字段

硬件触发

工作原理:

  • 外部引脚产生中断

  • 中断服务程序调用Trigger

  • Trigger通知所有注册的设备

  • 各设备同时读取数据

软件触发
1
echo 1 > /sys/bus/iio/devices/trigger0/trigger_now

工作原理:

  • 用户写入trigger_now文件
  • 内核调用trigger回调
  • 设备执行数据采集
定时器触发器
1
2
# 固定时间间隔触发
echo 100 > /sys/bus/iio/devices/trigger0/sampling_frequency

工作原理:

  • 定时器中断触发数据采集

核心数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//include/linux/iio/trigger.h
struct iio_trigger {
const struct iio_trigger_ops *ops; // 触发器操作
struct module *owner; // 拥有者模块
int id; // 触发器ID
const char *name; // 触发器名称
struct device dev; // 内嵌的设备
struct list_head list; // 链表
struct list_head alloc_list; // 分配链表
};

struct iio_trigger_ops {
int (*set_trigger_state)(struct iio_trigger *trig, bool state);
int (*reenable)(struct iio_trigger *trig);
int (*validate_device)(struct iio_trigger *trig,
struct iio_dev *indio_dev);
};

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建Trigger
struct iio_trigger *trigger;
trigger = iio_trigger_alloc(dev, "my_trigger_%s", name);

// 设置Trigger操作
trigger->ops = &trigger_ops;

// 注册Trigger
iio_trigger_register(trigger);

// 设备关联Trigger
iio_trigger_set_drvdata(trigger, data);

sysfs节点详解

一个IIO设备在sysfs下有以下主要节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/sys/bus/iio/devices/iio:deviceX/
├── name # 设备名称 → iio_dev->name
├── dev # 设备号
├── of_node -> ../../../../../../../firmware/devicetree/base/...
├── power/ # 电源管理
├── subsystem -> ../../../../../../../bus/iio
├── uevent
├── in_accel_x_raw # 单次读取属通道属性(直接I/O)
├── in_accel_y_raw
├── in_accel_z_raw
├── in_accel_scale
├── sampling_frequency # 共享属性
├── buffer/ # Buffer控制
│ ├── enable
│ ├── length
│ └── watermark
├── scan_elements/ # 缓冲扫描配置目录
│ ├── in_accel_x_en # 使能X轴(0/1)
│ ├── in_accel_x_index # 在缓冲中的位置
│ └── in_accel_x_type # 数据类型(le:s16)
├── trigger/ # Trigger配置
│ └── current_trigger # 当前绑定的Trigger
└── events/ # 事件配置(如有)
├── in_accel_thresh_either_en
└── in_accel_thresh_either_value

/sys/bus/iio/devices/triggerX/
├── name # Trigger名称
├── trigger_now # 立即触发(软件Trigger)
├── sampling_frequency # 采样频率(定时Trigger)
├── sampling_frequency_available # 可用频率
└── subsystem -> ../../bus/iio

通道相关节点

1
2
3
4
5
6
7
8
struct iio_chan_spec channel = {
.type = IIO_ACCEL, // 基础类型
.modified = 1, // 有修饰符
.channel2 = IIO_MOD_X, // 修饰符:X轴
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | // 单独属性
BIT(IIO_CHAN_INFO_CALIBSCALE),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), // 类型共享
};
  • in_accel_x_raw:.info_mask_separate中的IIO_CHAN_INFO_RAW
  • in_accel_x_calibscale: .info_mask_separate中的IIO_CHAN_INFO_CALIBSCALE
  • in_accel_sampling_frequency:.info_mask_shared_by_type中的IIO_CHAN_INFO_SAMP_FREQ

Buffer相关节点

来自iio_dev.iio_buffer_setup_opsiio_chan_spec.scan_type

  • buffer/

    • enable:是否开启了buffer模式
  • scan_elements/

    • _en:控制”采不采”这个通道(仅Buffer模式下有效)
    • _ index:告诉用户 这个通道数据在Buffer的哪里
    • _type:告诉用户 这个数据怎么解析

Trigger相关节点

来自struct iio_trigger

  • trigger/目录 → 当iio_dev支持Buffer模式时自动创建
  • current_trigger→ 指向当前绑定的Trigger

不同的Tigger对应的节点不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 软件Trigger
/sys/bus/iio/devices/trigger0/
├── name = "sysfstrig0"
└── trigger_now # echo 1 > trigger_now

# 定时Trigger
/sys/bus/iio/devices/trigger0/
├── name = "hrtimer0"
├── sampling_frequency # 设置频率
└── sampling_frequency_available

# GPIO Trigger
/sys/bus/iio/devices/trigger0/
├── name = "gpio-trigger-17" # 基于GPIO 17
└── ... # 无用户可配置参数

命名规则

  • 输入通道:in_
  • 输出通道:out_
  • 类型:voltageacceltemp
  • 修饰符:xyzilluminance
  • 后缀:_raw_scale_offset

驱动模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1. 分配IIO设备
struct iio_dev *indio_dev;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;

// 2. 设置设备信息
indio_dev->name = "adc_example";
indio_dev->info = &adc_info;
indio_dev->modes = INDIO_DIRECT_MODE;

// 3. 定义通道
static const struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0, // 决定在 Buffer 中的排列顺序
.scan_type = {
.sign = 'u', // 无符号
.realbits = 12, // 真实有效位
.storagebits = 16, // 存储占用位(2字节对齐)
.shift = 4,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1), // 强制添加时间戳通道,scan_index 设为最高
};

// 4. 设置通道
indio_dev->channels = adc_channels;
indio_dev->num_channels = ARRAY_SIZE(adc_channels);

// 5. 注册设备
ret = devm_iio_device_register(&client->dev, indio_dev);

libiio

libiio是一个开源的iio用户态组件,包括一些调试用的命令行工具和库,用来简化应用层对iio设备的开发和调试

调试

1.为什么有的驱动明明有buffer、trigger文件,可是它的probe函数里iio_devmodes字段设置的是INDIO_DIRECT_MODE

  • 要初始化buffer或者trigger模式,需要另外的核心层API,其内部会modes |= 别的模式

2.ODR是什么东西

  • ODR是Output Data Rate,如果设置成0的话,传感器就没数据了