一、什么是 IIO 子系统
IIO(Industrial I/O) 是 Linux 内核专为传感器类设备设计的通用驱动框架,2009 年由 Jonathan Cameron 主导引入,现为内核主线的标准子系统(drivers/iio/)。
适用场景
IIO 专门处理:
- ADC / DAC(模拟转换)
- 环境传感器:光照、接近、温湿度、气压
- 运动传感器:加速度计、陀螺仪、磁力计
- 电压 / 电流监控
与 miscdevice 方案对比
| 维度 | miscdevice | IIO 子系统 |
|---|---|---|
| 用户接口 | /dev/xxx,自定义 ioctl | /sys/bus/iio/devices/iio:deviceX/,标准 sysfs 属性 |
| 工具生态 | 无通用工具 | iio_info、iio_readdev、libiio 直接可用 |
| 通道抽象 | 手动 | 声明式,内核自动生成属性文件 |
| 缓冲/触发 | 需自己实现 | 内置 kfifo 缓冲 + 触发机制 |
| 代码量 | 较多样板代码 | 驱动只需关注硬件读取逻辑 |
| 适用性 | 任意字符设备 | 传感器/ADC 类设备 |
结论:做传感器驱动首选 IIO;做控制类外设(如 LED、蜂鸣器)用 miscdevice/platform 更直接。
二、架构概览
┌─────────────────────────────────────────┐
│ 用户空间 │
│ cat in_illuminance_raw │
│ iio_readdev /dev/iio:device0 │
└───────────────┬─────────────────────────┘
│ sysfs / dev
┌───────────────▼─────────────────────────┐
│ IIO 核心层 │
│ iio_dev iio_chan_spec iio_info │
│ sysfs 属性自动生成 │
│ 触发器 / 缓冲管理(可选) │
└───────────────┬─────────────────────────┘
│ read_raw / write_raw
┌───────────────▼─────────────────────────┐
│ 具体传感器驱动 │
│ ap3216c.c bmp280.c mpu6050.c ... │
└───────────────┬─────────────────────────┘
│ I²C / SPI / 寄存器
┌───────────────▼─────────────────────────┐
│ 硬件 │
│ ALS / PS / Gyro / ADC ... │
└─────────────────────────────────────────┘
三、核心数据结构
3.1 struct iio_dev — 设备描述符
一个传感器设备对应一个 iio_dev,由 devm_iio_device_alloc() 分配,同时在其尾部附加驱动私有数据。
struct iio_dev {
const char *name; // sysfs name 属性
const struct iio_info *info; // 驱动操作函数表
const struct iio_chan_spec *channels; // 通道数组
int num_channels; // 通道数量
unsigned long modes; // INDIO_DIRECT_MODE 等
/* ... 内核内部字段 ... */
};
关键 API:
/* 分配:sizeof(*priv) 追加在 iio_dev 后面 */
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*priv));
priv = iio_priv(indio_dev); // 取私有数据指针
/* 注册(devm 版,设备移除时自动注销)*/
ret = devm_iio_device_register(&client->dev, indio_dev);
3.2 struct iio_chan_spec — 通道描述
每个物理测量量对应一个通道(channel),IIO 核心据此自动生成 sysfs 属性文件。
struct iio_chan_spec {
enum iio_chan_type type; // IIO_LIGHT / IIO_ACCEL / ...
int channel; // 通道编号(多通道时使用)
int channel2; // 修饰符编号
unsigned long modified : 1; // 是否使用 channel2 修饰
unsigned long indexed : 1; // 是否使用 channel 编号
long info_mask_separate; // 该通道独有的 info 类型
long info_mask_shared_by_type; // 同类型通道共享的 info
};
通道类型(常用)
iio_chan_type | sysfs 前缀 | 典型场景 |
|---|---|---|
IIO_LIGHT | in_illuminance | 环境光传感器 |
IIO_PROXIMITY | in_proximity | 接近传感器 |
IIO_INTENSITY | in_intensity | 光强(+修饰符区分 IR/可见光) |
IIO_TEMP | in_temp | 温度 |
IIO_ACCEL | in_accel | 加速度 |
IIO_ANGL_VEL | in_anglvel | 角速度(陀螺仪) |
IIO_MAGN | in_magn | 磁场 |
IIO_VOLTAGE | in_voltage | 电压 |
IIO_PRESSURE | in_pressure | 气压 |
IIO_HUMIDITYRELATIVE | in_humidityrelative | 相对湿度 |
通道修饰符(常用)
iio_modifier | 含义 |
|---|---|
IIO_MOD_X/Y/Z | 三轴(加速度计、陀螺仪) |
IIO_MOD_LIGHT_IR | IR 光强 |
IIO_MOD_LIGHT_BOTH | 可见光 + IR 总光强 |
sysfs 属性命名规则
in_{type}[_{modifier}][_{index}]_{info}
举例:
in_illuminance_raw ← ALS (IIO_LIGHT)
in_proximity_raw ← PS (IIO_PROXIMITY)
in_intensity_ir_raw ← IR (IIO_INTENSITY + IIO_MOD_LIGHT_IR)
in_accel_x_raw ← 加速度 X 轴
in_voltage3_raw ← 第3路ADC
in_temp_scale ← 温度比例系数
3.3 struct iio_info — 驱动操作接口
struct iio_info {
/* 读取测量值,最核心的回调 */
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);
struct module *driver_module; // THIS_MODULE
};
read_raw 的 mask 参数
| mask 值 | 含义 | 返回 |
|---|---|---|
IIO_CHAN_INFO_RAW | 原始 ADC 值 | *val = 整数,返回 IIO_VAL_INT |
IIO_CHAN_INFO_SCALE | 量程系数 | *val / *val2,返回 IIO_VAL_FRACTIONAL |
IIO_CHAN_INFO_OFFSET | 零偏 | *val = 整数,返回 IIO_VAL_INT |
IIO_CHAN_INFO_SAMP_FREQ | 采样率 | *val = Hz,返回 IIO_VAL_INT |
3.4 数据结构全景类图
下图展示 IIO 核心层各结构体之间的关系,以及它们如何与上层(用户空间)和下层(总线设备)衔接:
classDiagram
class iio_dev {
+char *name
+unsigned long modes
+struct device dev
+iio_info *info
+iio_chan_spec *channels
+int num_channels
}
class iio_chan_spec {
+iio_chan_type type
+int modified
+int channel2
+long info_mask_separate
+long info_mask_shared_by_type
}
class iio_info {
+read_raw() int
+write_raw() int
+module *driver_module
}
class iio_buffer {
<<可选 触发缓冲>>
+int length
+int bytes_per_datum
+read()
}
class DriverPrivData {
<<驱动私有数据 iio_priv>>
+i2c_client *client
+mutex lock
+其他硬件状态
}
class i2c_client {
<<总线设备>>
+u16 addr
+i2c_adapter *adapter
}
class SysfsAttr {
<<用户空间 sysfs>>
+in_xxx_raw
+in_xxx_scale
+in_xxx_offset
+sampling_frequency
+name
}
class IioCharDev {
<<用户空间 devnode>>
+/dev/iio:deviceX
+read() 二进制流
}
iio_dev --> iio_info : .info 函数表
iio_dev "1" --> "N" iio_chan_spec : .channels[] 通道声明
iio_dev --> iio_buffer : .buffer 可选
iio_dev --> DriverPrivData : iio_priv 内存紧跟
DriverPrivData --> i2c_client : .client 总线设备
iio_dev ..> SysfsAttr : IIO核心按channels自动生成
iio_dev ..> IioCharDev : 启用buffer时注册字符设备
iio_chan_spec ..> SysfsAttr : type+modifier决定属性名关键关系说明:
| 关系 | 说明 |
|---|---|
iio_dev → iio_info | .info 指针,持有 read_raw/write_raw 函数表;IIO 核心通过它调用驱动 |
iio_dev → iio_chan_spec[] | .channels[] 数组声明物理测量通道;IIO 核心据此生成 sysfs 属性文件 |
iio_dev → DriverPrivData | iio_priv() 取到紧跟在 iio_dev 后面的私有数据块(devm_iio_device_alloc 的第二参数决定大小) |
DriverPrivData → i2c_client | 驱动私有数据保存总线设备指针,用于实际的寄存器读写 |
iio_dev ⇢ SysfsAttr | 虚线:IIO 核心在注册时根据 iio_chan_spec.type 和 modifier 自动创建 in_{type}[_{mod}]_{info} 属性 |
iio_dev ⇢ IioCharDev | 虚线:仅当驱动调用 iio_triggered_buffer_setup() 时,IIO 核心才创建 /dev/iio:deviceX 字符设备 |
驱动开发者只需关注三件事:声明
iio_chan_spec[]、实现iio_info.read_raw、在 probe 里调用devm_iio_device_register,其余全由 IIO 核心自动完成。
四、IIO 驱动开发三步走
Step 1:声明通道数组
static const struct iio_chan_spec mysen_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_PRESSURE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
};
Step 2:实现 read_raw
static int mysen_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct mysen_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_TEMP:
*val = read_temperature(data);
return IIO_VAL_INT;
case IIO_PRESSURE:
*val = read_pressure(data);
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 100; /* 0.01 °C/LSB */
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info mysen_info = {
.read_raw = mysen_read_raw,
.driver_module = THIS_MODULE,
};
Step 3:probe 中分配、填充、注册
static int mysen_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct iio_dev *indio_dev;
struct mysen_data *data;
int ret;
/* 1. 分配(私有数据追加在 iio_dev 后面)*/
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, indio_dev);
/* 2. 硬件初始化 */
ret = mysen_hw_init(data);
if (ret)
return ret;
/* 3. 填充描述符 */
indio_dev->dev.parent = &client->dev;
indio_dev->name = "mysen";
indio_dev->info = &mysen_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mysen_channels;
indio_dev->num_channels = ARRAY_SIZE(mysen_channels);
/* 4. 注册 */
return devm_iio_device_register(&client->dev, indio_dev);
}
五、用户空间访问方式
5.1 sysfs(最常用)
# 列出所有 IIO 设备
ls /sys/bus/iio/devices/
# 查看设备名
cat /sys/bus/iio/devices/iio:device0/name
# 读取原始值
cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw
cat /sys/bus/iio/devices/iio:device0/in_proximity_raw
cat /sys/bus/iio/devices/iio:device0/in_intensity_ir_raw
# 读取实际值(需要 scale 和 offset)
# 实际值 = (raw + offset) * scale
5.2 字符设备(用于缓冲读取)
当启用 IIO 缓冲时,数据可通过 /dev/iio:deviceX 以二进制流读取,适合高速采样场景。
iio_readdev -s 512 -b 256 /dev/iio:device0
5.3 iio_info 工具
iio_info # 列出所有设备及其属性(需 libiio 工具包)
六、devm_ 系列函数与资源管理
IIO 驱动几乎全程使用 devm_(device-managed)版本:
| 函数 | 说明 |
|---|---|
devm_iio_device_alloc() | 分配 iio_dev,设备移除时自动释放 |
devm_iio_device_register() | 注册到 IIO 核心,设备移除时自动注销 |
devm_iio_triggered_buffer_setup() | 设置触发缓冲(高级功能) |
devm_ 机制使得 remove() 回调通常只需关闭硬件(掉电),无需手动注销驱动框架资源:
static int mysen_remove(struct i2c_client *client)
{
/* devm_ 自动完成 iio_device_unregister / kfree */
hw_powerdown(client); /* 仅需关闭硬件 */
return 0;
}
七、常见问题
Q1:为什么 in_illuminance_raw 不出现?
- 通道声明中
info_mask_separate未包含BIT(IIO_CHAN_INFO_RAW) indio_dev->info未正确赋值devm_iio_device_register()返回非零
Q2:iio_priv() 返回 NULL?
- 分配时
sizeof(*priv)传了 0 - 或对同一
indio_dev多次调用了iio_priv()(不会,这个函数很简单,就是indio_dev + 1对齐后的指针)
Q3:sysfs 读取时偶发错误值(竞争问题)
read_raw回调内忘记加mutex_lock- I²C 读操作分两次读高低字节,存在寄存器更新竞争;应使用
i2c_transfer一次读取完整字节对
八、参考资料
Documentation/iio/— 内核官方 IIO 文档drivers/iio/light/— 光线/接近传感器驱动参考(bh1750.c、vcnl4000.c)drivers/iio/imu/— IMU 传感器参考(mpu6050.c)- Jonathan Cameron 的 IIO 入门演讲(FOSDEM 2012)
延伸阅读
掌握 IIO 框架后,可通过光照传感器和六轴 IMU 两个驱动验证抽象。
- 返回嵌入式 Linux 学习路线,确认本文在完整知识结构中的位置。
- AP3216C IIO 驱动开发
- ICM-20608 SPI IIO 驱动开发




