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机制、中断触发机制等方面组成,头文件包括如下:

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框架的核心概念和编程模型,开发者可以高效地开发各种传感器驱动,为用户空间提供稳定可靠的传感器数据访问接口。

相关推荐
梁正雄14 小时前
linux服务-Bonding网卡绑定工具
linux·运维·linux bonding·网卡绑定
云边有个稻草人14 小时前
Windows 里用 Linux 不卡顿?WSL + cpolar让跨系统开发变简单
linux·运维·服务器·cpolar
打不了嗝 ᥬ᭄14 小时前
【Linux】网络层协议
linux·网络·c++·网络协议·http
LXY_BUAA14 小时前
将linux操作系统装入U盘20251107
linux·运维·服务器
kaoa00015 小时前
Linux入门攻坚——53、drbd - Distribute Replicated Block Device,分布式复制块设备-2
linux·运维·服务器
落羽的落羽15 小时前
【C++】现代C++的新特性constexpr,及其在C++14、C++17、C++20中的进化
linux·c++·人工智能·学习·机器学习·c++20·c++40周年
RisunJan15 小时前
Linux命令-e2label命令(设置第二扩展文件系统的卷标)
linux·运维·服务器
Claire_ccat15 小时前
2025山西省网络安全职业技能大赛PWN方向题解
linux·安全·网络安全·pwn·栈溢出
小苏兮15 小时前
【把Linux“聊”明白】编译器gcc/g++与调试器gdb/cgdb:从编译原理到高效调试
java·linux·运维·学习·1024程序员节
LCG元15 小时前
Linux 软件安装大全:apt/yum/dpkg/rpm/snap 到底用哪个?
linux