1. Linux IIO驱动框架概述
工业输入/输出(Industrial I/O,简称IIO)子系统是Linux内核中专门为传感器类设备设计的驱动框架。它提供了一套标准化的接口,使得各种传感器(如加速度计、陀螺仪、温度传感器等)能够以统一的方式与用户空间交互。
IIO框架的核心优势在于:
- 统一的sysfs接口:所有传感器数据通过标准化的文件节点暴露给用户空间
- 缓冲区支持:支持硬件和软件触发的数据采集模式
- 事件机制:可以处理阈值触发等传感器事件
- 丰富的工具链支持:与libiio等工具库完美配合
2. IIO核心数据结构解析
2.1 iio_dev结构体
struct iio_dev是IIO框架的核心数据结构,代表一个IIO设备。它的主要成员包括:
c复制struct iio_dev {
int modes; // 设备支持的操作模式
struct device *dev; // 关联的设备结构
struct iio_chan_spec const *channels; // 通道描述数组
int num_channels; // 通道数量
const struct iio_info *info; // 设备操作回调函数集
const char *name; // 设备名称
// ...其他成员省略...
};
设备支持的模式包括:
INDIO_DIRECT_MODE:提供基本的sysfs接口INDIO_BUFFER_TRIGGERED:支持硬件触发缓冲INDIO_BUFFER_SOFTWARE:支持软件触发缓冲
2.2 设备申请与注册
IIO设备的生命周期管理主要通过以下函数实现:
c复制// 申请IIO设备内存
struct iio_dev *iio_device_alloc(int sizeof_priv);
// 释放IIO设备
void iio_device_free(struct iio_dev *indio_dev);
// 注册IIO设备
int iio_device_register(struct iio_dev *indio_dev);
// 注销IIO设备
void iio_device_unregister(struct iio_dev *indio_dev);
实际开发中,我们通常会使用资源管理版本:
c复制struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv);
2.3 iio_info结构体
struct iio_info定义了设备的操作回调函数,其中最重要的是read_raw和write_raw:
c复制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);
// ...其他成员省略...
};
mask参数用于指定读取的数据类型,常用值包括:
IIO_CHAN_INFO_RAW:原始数据IIO_CHAN_INFO_SCALE:比例因子IIO_CHAN_INFO_OFFSET:偏移量
3. IIO通道配置
3.1 iio_chan_spec结构体
每个传感器通道通过struct iio_chan_spec描述:
c复制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; // 右移位数
enum iio_endian endianness; // 字节序
} scan_type;
long info_mask_separate; // 独立属性掩码
long info_mask_shared_by_type; // 同类型通道共享属性
// ...其他成员省略...
};
3.2 通道类型与修饰符
通道类型(type)定义了传感器的测量类型:
c复制enum iio_chan_type {
IIO_VOLTAGE, // 电压
IIO_CURRENT, // 电流
IIO_ACCEL, // 加速度
IIO_ANGL_VEL, // 角速度(陀螺仪)
IIO_MAGN, // 磁场
IIO_LIGHT, // 光强
IIO_TEMP, // 温度
// ...其他类型省略...
};
通道修饰符(channel2)用于区分同类通道的不同方向或属性:
c复制enum iio_modifier {
IIO_MOD_X, // X轴
IIO_MOD_Y, // Y轴
IIO_MOD_Z, // Z轴
IIO_MOD_LIGHT_RED, // 红光
IIO_MOD_LIGHT_GREEN, // 绿光
// ...其他修饰符省略...
};
4. ICM20608驱动实现
4.1 设备初始化
ICM20608是一款6轴运动跟踪设备,包含3轴陀螺仪和3轴加速度计。驱动初始化流程如下:
- 申请IIO设备内存
- 初始化SPI接口和寄存器映射
- 配置设备工作模式
- 设置IIO通道信息
- 注册IIO设备
c复制static int icm20608_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct icm20608_dev *dev;
int ret;
// 1. 申请IIO设备
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));
if (!indio_dev)
return -ENOMEM;
dev = iio_priv(indio_dev);
dev->spi = spi;
spi_set_drvdata(spi, indio_dev);
// 2. 初始化regmap
dev->regmap_config.reg_bits = 8;
dev->regmap_config.val_bits = 8;
dev->regmap_config.read_flag_mask = 0x80;
dev->regmap = regmap_init_spi(spi, &dev->regmap_config);
// 3. 设备硬件初始化
ret = icm20608_hw_init(dev);
if (ret)
goto error_regmap;
// 4. 配置IIO设备
indio_dev->dev.parent = &spi->dev;
indio_dev->name = "icm20608";
indio_dev->channels = icm20608_channels;
indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);
indio_dev->info = &icm20608_info;
indio_dev->modes = INDIO_DIRECT_MODE;
// 5. 注册IIO设备
ret = iio_device_register(indio_dev);
if (ret)
goto error_hw;
return 0;
error_hw:
icm20608_hw_deinit(dev);
error_regmap:
regmap_exit(dev->regmap);
return ret;
}
4.2 通道配置示例
ICM20608的通道配置如下:
c复制static const struct iio_chan_spec icm20608_channels[] = {
/* 温度通道 */
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET),
.scan_index = INV_ICM20608_SCAN_TEMP,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.shift = 0,
.endianness = IIO_BE,
},
},
/* 陀螺仪X轴 */
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_X,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
.scan_index = INV_ICM20608_SCAN_GYRO_X,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.shift = 0,
.endianness = IIO_BE,
},
},
/* 类似配置陀螺仪Y/Z轴... */
/* 加速度计X轴 */
{
.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),
.scan_index = INV_ICM20608_SCAN_ACCEL_X,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.shift = 0,
.endianness = IIO_BE,
},
},
/* 类似配置加速度计Y/Z轴... */
};
4.3 数据读取实现
read_raw回调函数负责处理用户空间的数据读取请求:
c复制static int icm20608_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct icm20608_dev *dev = iio_priv(indio_dev);
int ret;
__be16 raw_data;
switch (mask) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&dev->lock);
ret = icm20608_read_reg(dev, chan->address, &raw_data, sizeof(raw_data));
mutex_unlock(&dev->lock);
if (ret)
return ret;
*val = (s16)be16_to_cpu(raw_data);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_TEMP:
*val = 32680; // 326.8 LSB/°C * 100
return IIO_VAL_INT_PLUS_MICRO;
case IIO_ANGL_VEL:
*val = 0;
*val2 = gyro_scale_icm20608[dev->gyro_fs];
return IIO_VAL_INT_PLUS_MICRO;
case IIO_ACCEL:
*val = 0;
*val2 = accel_scale_icm20608[dev->accel_fs];
return IIO_VAL_INT_PLUS_NANO;
}
break;
case IIO_CHAN_INFO_OFFSET:
if (chan->type == IIO_TEMP) {
*val = ICM20608_TEMP_OFFSET;
return IIO_VAL_INT;
}
break;
}
return -EINVAL;
}
5. 用户空间接口使用
驱动加载后,会在/sys/bus/iio/devices/下创建对应的设备节点。以ICM20608为例:
bash复制# 查看设备节点
ls /sys/bus/iio/devices/iio:device0/
# 读取X轴加速度原始值
cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw
# 读取加速度比例因子
cat /sys/bus/iio/devices/iio:device0/in_accel_scale
# 计算实际加速度值(单位:m/s²)
raw=$(cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw)
scale=$(cat /sys/bus/iio/devices/iio:device0/in_accel_scale)
accel=$(echo "$raw * $scale * 9.80665 / 1000000000" | bc -l)
echo "X轴加速度: ${accel}m/s²"
6. 调试与问题排查
6.1 常见问题
-
设备节点未创建:
- 检查IIO子系统是否编译进内核
- 确认驱动probe函数成功执行
- 查看dmesg日志是否有错误信息
-
读取数据不正确:
- 确认SPI通信正常
- 检查寄存器配置是否正确
- 验证字节序处理是否正确
-
权限问题:
- 确保用户有访问
/sys/bus/iio/devices/的权限 - 可以考虑使用udev规则设置默认权限
- 确保用户有访问
6.2 调试技巧
-
使用
iio_info工具查看设备信息:bash复制iio_info -u local: -
实时监控传感器数据:
bash复制iio_readdev -u local:iio:device0 -s 64 -b 256 | hexdump -
使用内核动态调试:
bash复制echo "file drivers/iio/* +p" > /sys/kernel/debug/dynamic_debug/control dmesg -w
7. 性能优化建议
-
使用硬件缓冲模式:
- 对于高采样率应用,启用
INDIO_BUFFER_TRIGGERED模式 - 配置DMA传输减少CPU开销
- 对于高采样率应用,启用
-
合理设置采样率:
- 根据应用需求调整采样率,避免不必要的资源消耗
- 使用
SMPLRT_DIV寄存器优化采样间隔
-
电源管理:
- 在非活动时段进入低功耗模式
- 使用运动唤醒功能
-
数据处理优化:
- 在内核空间完成单位转换等简单计算
- 使用IIO缓冲区减少用户空间-内核空间切换
8. 扩展功能实现
8.1 触发缓冲模式
要实现硬件触发的数据采集,需要:
-
配置设备支持缓冲模式:
c复制
indio_dev->modes |= INDIO_BUFFER_TRIGGERED; -
实现缓冲区设置回调:
c复制static const struct iio_buffer_setup_ops icm20608_buffer_setup_ops = { .preenable = &icm20608_buffer_preenable, .postenable = &icm20608_buffer_postenable, .predisable = &icm20608_buffer_predisable, .postdisable = &icm20608_buffer_postdisable, }; -
配置触发器和轮询函数:
c复制static const struct iio_trigger_ops icm20608_trigger_ops = { .set_trigger_state = &icm20608_data_rdy_trigger_set_state, .validate_device = &iio_trigger_validate_own_device, };
8.2 事件支持
要支持阈值触发等事件,需要:
-
定义事件规格:
c复制static const struct iio_event_spec icm20608_event[] = { { .type = IIO_EV_TYPE_THRESH, .dir = IIO_EV_DIR_RISING, .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), }, }; -
添加到通道配置:
c复制
.event_spec = icm20608_event, .num_event_specs = ARRAY_SIZE(icm20608_event), -
实现事件回调函数:
c复制static int icm20608_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, enum iio_event_direction dir, enum iio_event_info info, int *val, int *val2) { // 实现读取事件阈值等 }
9. 跨平台考虑
为了使驱动具有良好的可移植性,需要注意:
-
设备树兼容性:
- 使用标准的设备树绑定
- 提供合理的默认配置
-
端序处理:
- 明确传感器数据的字节序
- 使用
be16_to_cpu等函数进行转换
-
时钟和电源管理:
- 正确处理不同平台的时钟和电源控制
- 使用标准的PM框架
-
寄存器差异:
- 处理不同版本芯片的寄存器差异
- 使用条件编译或运行时检测
10. 实际开发经验分享
在开发ICM20608驱动过程中,积累了一些实用经验:
-
寄存器访问同步:
- 使用互斥锁保护寄存器访问
- 对于频繁访问的寄存器,考虑缓存其值
-
数据处理技巧:
- 原始数据通常是有符号的,注意符号扩展
- 比例因子计算时注意单位统一
-
调试手段:
- 使用
regmap的调试功能检查SPI通信 - 通过sysfs手动读写寄存器验证功能
- 使用
-
性能权衡:
- 高采样率时,减少sysfs访问,使用缓冲区模式
- 低功耗应用中,合理配置传感器的唤醒间隔
-
用户空间兼容性:
- 确保单位与标准工具兼容
- 提供合理的默认配置减少用户配置工作
附录A:IIO sysfs文件命名规则
IIO子系统生成的sysfs文件遵循以下命名规则:
code复制in_<type>[_<modifier>]_<info>
其中:
<type>:通道类型,如accel、anglvel、temp等<modifier>:可选,通道修饰符,如x、y、z等<info>:信息类型,如raw、scale、offset等
例如:
in_accel_x_raw:X轴加速度原始值in_temp_scale:温度比例因子in_anglvel_y_raw:Y轴角速度原始值
附录B:比例因子计算详解
比例因子用于将原始数据转换为实际物理量。以ICM20608加速度计为例:
- 确定量程:例如±16g
- 计算满量程范围:16g - (-16g) = 32g
- 确定分辨率:16位ADC,2^16 = 65536个值
- 计算比例因子:32g / 65536 ≈ 0.00048828125 g/LSB
在驱动中,我们通常将比例因子放大为整数存储:
- 0.00048828125 g/LSB → 488281 (纳g/LSB)
- 在用户空间使用时再转换回实际值
温度传感器的比例因子计算类似:
- 数据手册给出326.8 LSB/°C
- 驱动中存储为326800000 (微LSB/°C)
- 用户空间使用时除以10^6
附录C:ICM20608寄存器配置参考
以下是ICM20608关键寄存器的配置参考:
-
电源管理1 (0x6B):
- 复位后默认值:0x40
- 典型配置:0x01 (解除睡眠模式)
-
陀螺仪配置 (0x1B):
- 量程选择:
- ±250dps: 0x00
- ±500dps: 0x08
- ±1000dps: 0x10
- ±2000dps: 0x18
- 量程选择:
-
加速度计配置 (0x1C):
- 量程选择:
- ±2g: 0x00
- ±4g: 0x08
- ±8g: 0x10
- ±16g: 0x18
- 量程选择:
-
采样率分频器 (0x19):
- 采样率 = 1kHz / (1 + SMPLRT_DIV)
-
配置寄存器 (0x1A):
- 数字低通滤波器配置
- 外部帧同步输入配置
实际开发中,应根据具体应用需求优化这些配置参数。
