Linux 内核IIO sensor驱动
这篇文章我们简单介绍一下关于sensor的知识,这里指的sensor主要是陀螺仪、加速度计等等。
1. IIO框架概述
IIO(Industrial I/O)是Linux内核中专门为模拟数字转换器(ADC)、数字模拟转换器(DAC)、惯性测量单元(IMU)以及各种传感器设计的子系统框架。它提供了一套标准化的接口,使得传感器数据能够被用户空间应用程序方便地访问和处理。
1.1 IIO框架的优势
统一的用户空间接口:通过sysfs和字符设备提供标准访问方式
硬件抽象层:隔离硬件差异,提供一致的编程接口
事件支持:支持阈值触发、数据就绪等事件机制
缓冲区管理:提供高效的数据缓冲和读取机制
2. IIO核心架构
2.1 IIO子系统组成
主要包括iio驱动设备、事件、buffer机制、中断触发机制等方面组成,头文件包括如下:
// 主要头文件
#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 核心数据结构
主要数据结构成员如下所示:
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设备的相关信息。
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 定义传感器通道
根据不同的传感器,定义传感器通道,应用层通过不同的通道获取数据。
// 通道定义示例 - 三轴加速度计
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 实现设备操作集
这是应用访问驱动的关键接口:
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 实现数据读取回调
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 缓冲区设置
// 启用缓冲区支持
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 触发处理函数
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接口
# 查看可用设备
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 字符设备接口
对于缓冲区数据,可以通过字符设备读取:
// 用户空间读取示例
#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 调试工具
# 使用iio_info工具查看设备信息
iio_info# 使用iio_readdev读取数据
iio_readdev -s 100 iio:device0# 使用通用工具
cat /sys/kernel/debug/iio/iio:device0/registers
7. 总结
IIO框架为传感器驱动开发提供了完整的解决方案,主要特点包括:
模块化设计:清晰的层次结构,便于扩展和维护
统一接口:标准化的用户空间访问方式
高效数据流:支持中断驱动和缓冲区机制
灵活配置:丰富的属性和事件支持
良好的生态:丰富的工具链和调试支持
通过理解IIO框架的核心概念和编程模型,开发者可以高效地开发各种传感器驱动,为用户空间提供稳定可靠的传感器数据访问接口。