这篇文章我们简单介绍一下关于sensor的知识,这里指的sensor主要是陀螺仪、加速度计等等。
1. IIO框架概述
IIO(Industrial I/O)是Linux内核中专门为模拟数字转换器(ADC)、数字模拟转换器(DAC)、惯性测量单元(IMU)以及各种传感器设计的子系统框架。它提供了一套标准化的接口,使得传感器数据能够被用户空间应用程序方便地访问和处理。
1.1 IIO框架的优势
-
统一的用户空间接口:通过sysfs和字符设备提供标准访问方式
-
硬件抽象层:隔离硬件差异,提供一致的编程接口
-
事件支持:支持阈值触发、数据就绪等事件机制
-
缓冲区管理:提供高效的数据缓冲和读取机制
2. IIO核心架构
2.1 IIO子系统组成
主要包括iio驱动设备、事件、buffer机制、中断触发机制等方面组成,头文件包括如下:
cpp
// 主要头文件
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
2.2 核心数据结构
主要数据结构成员如下所示:
cpp
struct iio_dev {
int modes; // 设备模式
int currentmode; // 当前模式
struct device dev; // 设备结构
struct iio_buffer *buffer; // 数据缓冲区
int scan_bytes; // 扫描字节数
const struct iio_info *info; // 设备操作集
struct iio_chan_spec const *channels; // 通道描述
int num_channels; // 通道数量
const char *name; // 设备名称
// ... 其他成员
};
3. IIO驱动开发步骤
下面简要介绍一下驱动开发移植的步骤:
3.1 设备初始化和注册
具体实现参考如下,需要注册的时候分配iio设备,设置iio设备的相关信息。
cpp
static int my_sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct iio_dev *indio_dev;
struct my_sensor_data *data;
// 分配IIO设备
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
// 设置IIO设备信息
indio_dev->dev.parent = &client->dev;
indio_dev->name = "my_sensor";
indio_dev->channels = my_sensor_channels;
indio_dev->num_channels = ARRAY_SIZE(my_sensor_channels);
indio_dev->info = &my_sensor_info;
indio_dev->modes = INDIO_DIRECT_MODE;
// 注册IIO设备
return devm_iio_device_register(&client->dev, indio_dev);
}
3.2 定义传感器通道
根据不同的传感器,定义传感器通道,应用层通过不同的通道获取数据。
cpp
// 通道定义示例 - 三轴加速度计
static const struct iio_chan_spec my_sensor_channels[] = {
{
.type = IIO_ACCEL, // 加速度类型
.modified = 1, // 使用修改通道
.channel2 = IIO_MOD_X, // X轴
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE), // 独立属性
.info_mask_shared_by_type = 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) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = 1,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_LE,
},
},
// Z轴通道类似定义
IIO_CHAN_SOFT_TIMESTAMP(3), // 时间戳通道
};
3.3 实现设备操作集
这是应用访问驱动的关键接口:
cpp
static const struct iio_info my_sensor_info = {
.read_raw = my_sensor_read_raw, // 读取原始数据
.write_raw = my_sensor_write_raw, // 写入配置
.read_event_value = my_sensor_read_event, // 读取事件值
.write_event_value = my_sensor_write_event, // 写入事件值
.debugfs_reg_access = &my_sensor_reg_access, // 调试寄存器访问
};
3.4 实现数据读取回调
cpp
static int my_sensor_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct my_sensor_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&data->lock);
ret = my_sensor_read_data(data, chan->channel2, val);
mutex_unlock(&data->lock);
if (ret < 0)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = my_sensor_scales[data->range]; // 根据量程返回缩放值
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
*val = data->sampling_frequency;
return IIO_VAL_INT;
}
return -EINVAL;
}
4. IIO缓冲区机制
iio设备有buffer机制,是应用层与设备驱动数据交互的关键。有buf的机制,应用就不需要一直调用读取数据的接口读数据,可以通过buf来获取数据,提高工作的效率。
4.1 缓冲区设置
cpp
// 启用缓冲区支持
static int my_sensor_enable_buffer(struct iio_dev *indio_dev)
{
struct my_sensor_data *data = iio_priv(indio_dev);
int ret;
// 分配缓冲区
ret = iio_triggered_buffer_setup(indio_dev,
&iio_pollfunc_store_time,
&my_sensor_trigger_handler,
&my_sensor_buffer_setup_ops);
if (ret)
return ret;
// 配置硬件触发(如果需要)
data->trig = devm_iio_trigger_alloc(&data->client->dev, "%s-dev%d",
indio_dev->name, indio_dev->id);
if (!data->trig)
return -ENOMEM;
data->trig->ops = &my_sensor_trigger_ops;
ret = devm_iio_trigger_register(&data->client->dev, data->trig);
return ret;
}
4.2 触发处理函数
cpp
static irqreturn_t my_sensor_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct my_sensor_data *data = iio_priv(indio_dev);
struct {
s16 channels[3]; // 三轴数据
s64 timestamp __aligned(8); // 时间戳
} scan;
int ret;
// 读取传感器数据
ret = my_sensor_read_all_data(data, scan.channels);
if (ret < 0)
goto err;
// 获取时间戳
scan.timestamp = iio_get_time_ns(indio_dev);
// 推送到缓冲区
iio_push_to_buffers_with_timestamp(indio_dev, &scan,
scan.timestamp);
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
5. 用户空间接口
5.1 Sysfs接口
bash
# 查看可用设备
ls /sys/bus/iio/devices/
# 读取原始数据
cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw
# 读取缩放因子
cat /sys/bus/iio/devices/iio:device0/in_accel_scale
# 设置采样频率
echo 100 > /sys/bus/iio/devices/iio:device0/sampling_frequency
5.2 字符设备接口
对于缓冲区数据,可以通过字符设备读取:
bash
// 用户空间读取示例
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/types.h>
int fd = open("/dev/iio:device0", O_RDONLY | O_NONBLOCK);
// 配置触发器、使能通道、读取数据...
6. 调试和测试
6.1 调试工具
bash
# 使用iio_info工具查看设备信息
iio_info
# 使用iio_readdev读取数据
iio_readdev -s 100 iio:device0
# 使用通用工具
cat /sys/kernel/debug/iio/iio:device0/registers
7. 总结
IIO框架为传感器驱动开发提供了完整的解决方案,主要特点包括:
-
模块化设计:清晰的层次结构,便于扩展和维护
-
统一接口:标准化的用户空间访问方式
-
高效数据流:支持中断驱动和缓冲区机制
-
灵活配置:丰富的属性和事件支持
-
良好的生态:丰富的工具链和调试支持
通过理解IIO框架的核心概念和编程模型,开发者可以高效地开发各种传感器驱动,为用户空间提供稳定可靠的传感器数据访问接口。