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

相关推荐
jz_ddk3 小时前
[LVGL] 从0开始,学LVGL:进阶应用与项目实战(上)
linux·信息可视化·嵌入式·gui·lvgl·界面设计
望获linux3 小时前
【实时Linux实战系列】Linux 内核的实时组调度(Real-Time Group Scheduling)
java·linux·服务器·前端·数据库·人工智能·深度学习
MC丶科3 小时前
【SpringBoot常见报错与解决方案】端口被占用?Spring Boot 修改端口号的 3 种方法,第 3 种 90% 的人不知道!
java·linux·spring boot
江公望4 小时前
ubuntu kylin(优麒麟)和标准ubuntu的区别浅谈
linux·服务器·ubuntu·kylin
Lynnxiaowen4 小时前
今天我们开始学习python语句和模块
linux·运维·开发语言·python·学习
生态笔记4 小时前
PPT宏代码
linux·服务器·powerpoint
mucheni4 小时前
迅为RK3588开发板Ubuntu 系统开发ubuntu终端密码登录
linux·运维·ubuntu
skywoodsky4 小时前
Ubuntu 24.04环境下的挂起转休眠
linux
小云数据库服务专线4 小时前
GaussDB 应用侧报Read timed out解决方法
linux·服务器·gaussdb