07-ADC与IIO驱动.md 50 KB


title: ADC与IIO驱动 tags: [Linux驱动, ADC, IIO, 传感器, 嵌入式, I.MX6ULL] created: 2026-09-17 updated: 2026-09-17 pdf_ref:

  • "82_第七十六章 Linux ADC驱动实验.pdf"
  • "81_第七十五章 Linux IIO驱动实验.pdf" ---

ADC与IIO驱动

💡 关联知识: [[05-Linux外设驱动实战/02-按键输入驱动]] | [[03-Linux驱动开发核心/08-misc与input子系统]]


Part 1: ADC驱动

一、ADC硬件概述

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第七十六章内容,为扩展知识。原书指出 ADC 原理已在“裸机篇”《ADC 实验》章节讲解,本章直接分析内核自带的 vf610_adc.c 驱动。

1.1 ADC工作原理

ADC(Analog-to-Digital Converter,模数转换器)是将连续的模拟信号转换为离散的数字信号的器件。其工作过程包括:

  1. 采样(Sampling):按固定时间间隔对模拟信号进行取样
  2. 保持(Hold):在转换期间保持采样值稳定
  3. 量化(Quantization):将采样值映射到离散的数字级别
  4. 编码(Encoding):将量化结果编码为二进制数字

1.2 关键参数

参数 说明 示例
分辨率 ADC输出数字位数,决定精度 10位、12位、16位
采样率 每秒采样次数 100KSPS、1MSPS
参考电压 满量程对应的模拟电压 3.3V、5V
INL/DNL 积分/微分非线性误差 ±1 LSB

1.3 电压转换公式

对于N位ADC,参考电压为Vref:

数字输出值 = (Vin / Vref) × 2^N
实际电压 = 数字输出值 × (Vref / 2^N)

I.MX6ULL ADC参数

  • 12位分辨率(vf610_adc.cres_mode 决定,scale 返回 IIO_VAL_FRACTIONAL_LOG2
  • 参考电压:3.3V(由 vref-supply 指定的 regulator 提供)
  • 分辨率(scale):3300mV / 2^12 = 0.805664062 mV(与原书图 76.4.3.3 中 in_voltage_scale=0.805664062 一致)

二、ADC设备树配置

2.1 I.MX6ULL ADC节点(imx6ull.dtsi)

adc1: adc@02198000 {
    compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
    reg = <0x02198000 0x4000>;
    interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_ADC1>;
    num-channels = <2>;
    clock-names = "adc";
    status = "disabled";
};

2.2 设备树属性说明

属性 说明
compatible 兼容性属性,必须设置为"fsl,imx6ul-adc"
reg ADC控制器寄存器基地址和长度
interrupts 中断属性,ADC1和ADC2各对应一个中断
clocks 时钟属性
clock-names 时钟名字,可选"adc"
num-channels ADC通道数量
vref-supply 参考电压句柄(绑定文档要求)

2.3 使能ADC的设备树配置

imx6ull-alientek-emmc.dts中添加:

/* 引脚配置 */
pinctrl_adc1: adc1grp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
    >;
};

/* 参考电源节点(在regulators节点下添加) */
reg_vref_adc: regulator@2 {
    compatible = "regulator-fixed";
    regulator-name = "VREF_3V3";
    regulator-min-microvolt = <3300000>;
    regulator-max-microvolt = <3300000>;
};

/* 使能ADC1 */
&adc1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_adc1>;
    num-channels = <2>;
    vref-supply = <&reg_vref_adc>;
    status = "okay";
};

三、ADC驱动源码分析

3.1 vf610_adc结构体

I.MX6ULL的ADC驱动文件为drivers/iio/adc/vf610_adc.c,主体框架是platform + IIO。

struct vf610_adc {
    struct device *dev;
    void __iomem *regs;
    struct clk *clk;

    u32 vref_uv;              // 参考电压(微伏)
    u32 value;                // ADC转换结果
    struct regulator *vref;
    struct vf610_adc_feature adc_feature;

    u32 sample_freq_avail[5]; // 可用采样频率

    struct completion completion; // 完成量
};

3.2 vf610_adc_probe函数

static int vf610_adc_probe(struct platform_device *pdev)
{
    struct vf610_adc *info;
    struct iio_dev *indio_dev;
    struct resource *mem;
    int irq, ret;
    u32 channels;

    /* 1. 申请iio_dev内存(同时分配vf610_adc) */
    indio_dev = devm_iio_device_alloc(&pdev->dev,
                                       sizeof(struct vf610_adc));
    if (!indio_dev) {
        dev_err(&pdev->dev, "Failed allocating iio device\n");
        return -ENOMEM;
    }

    /* 2. 获取vf610_adc结构体地址 */
    info = iio_priv(indio_dev);
    info->dev = &pdev->dev;

    /* 3. 获取并映射寄存器 */
    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    info->regs = devm_ioremap_resource(&pdev->dev, mem);
    if (IS_ERR(info->regs))
        return PTR_ERR(info->regs);

    /* 4. 获取中断号并注册中断 */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "no irq resource?\n");
        return irq;
    }

    ret = devm_request_irq(info->dev, irq,
                           vf610_adc_isr, 0,
                           dev_name(&pdev->dev), info);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
        return ret;
    }

    /* 5. 获取时钟 */
    info->clk = devm_clk_get(&pdev->dev, "adc");
    if (IS_ERR(info->clk)) {
        dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
                PTR_ERR(info->clk));
        return PTR_ERR(info->clk);
    }

    /* 6. 获取参考电压 */
    info->vref = devm_regulator_get(&pdev->dev, "vref");
    if (IS_ERR(info->vref))
        return PTR_ERR(info->vref);

    ret = regulator_enable(info->vref);
    if (ret)
        return ret;

    info->vref_uv = regulator_get_voltage(info->vref);

    platform_set_drvdata(pdev, indio_dev);
    init_completion(&info->completion);

    /* 7. 读取通道数量 */
    ret = of_property_read_u32(pdev->dev.of_node,
                                "num-channels", &channels);
    if (ret)
        channels = ARRAY_SIZE(vf610_adc_iio_channels);

    /* 8. 初始化iio_dev */
    indio_dev->name = dev_name(&pdev->dev);
    indio_dev->dev.parent = &pdev->dev;
    indio_dev->dev.of_node = pdev->dev.of_node;
    indio_dev->info = &vf610_adc_iio_info;  // 关键:用户空间读取ADC数据的入口
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = vf610_adc_iio_channels;
    indio_dev->num_channels = (int)channels;

    /* 9. 使能时钟 */
    ret = clk_prepare_enable(info->clk);
    if (ret) {
        dev_err(&pdev->dev,
                "Could not prepare or enable the clock.\n");
        goto error_adc_clk_enable;
    }

    /* 10. 初始化ADC配置和硬件 */
    vf610_adc_cfg_init(info);
    vf610_adc_hw_init(info);

    /* 11. 注册iio_dev到内核 */
    ret = iio_device_register(indio_dev);
    if (ret) {
        dev_err(&pdev->dev, "Couldn't register the device.\n");
        goto error_iio_device_register;
    }

    return 0;
    // ... 错误处理
}

关键流程解读

步骤 函数 说明
1 devm_iio_device_alloc 申请iio_dev + 私有数据内存
2 iio_priv 从iio_dev获取私有数据地址
4 devm_request_irq 注册中断,服务函数为vf610_adc_isr
8 初始化iio_dev 设置info、channels、modes等成员
11 iio_device_register 向内核注册IIO设备

3.3 vf610_adc_iio_info结构体

static const struct iio_info vf610_adc_iio_info = {
    .driver_module = THIS_MODULE,
    .read_raw = &vf610_read_raw,
    .write_raw = &vf610_write_raw,
    .debugfs_reg_access = &vf610_adc_reg_access,
    .attrs = &vf610_attribute_group,
};

3.4 vf610_read_raw函数

这是用户空间读取ADC原始数据的核心函数:

static int vf610_read_raw(struct iio_dev *indio_dev,
                          struct iio_chan_spec const *chan,
                          int *val, int *val2, long mask)
{
    struct vf610_adc *info = iio_priv(indio_dev);
    unsigned int hc_cfg;
    long ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
    case IIO_CHAN_INFO_PROCESSED:
        mutex_lock(&indio_dev->mlock);
        reinit_completion(&info->completion);

        /* 配置通道并启动转换 */
        hc_cfg = VF610_ADC_ADCHC(chan->channel);
        hc_cfg |= VF610_ADC_AIEN;
        writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);

        /* 等待转换完成(中断触发completion) */
        ret = wait_for_completion_interruptible_timeout
               (&info->completion, VF610_ADC_TIMEOUT);
        if (ret == 0) {
            mutex_unlock(&indio_dev->mlock);
            return -ETIMEDOUT;
        }
        if (ret < 0) {
            mutex_unlock(&indio_dev->mlock);
            return ret;
        }

        switch (chan->type) {
        case IIO_VOLTAGE:
            *val = info->value;  // 读取ADC转换结果
            break;
        case IIO_TEMP:
            *val = 25000 - ((int)info->value - 864) * 1000000 / 1840;
            break;
        default:
            mutex_unlock(&indio_dev->mlock);
            return -EINVAL;
        }

        mutex_unlock(&indio_dev->mlock);
        return IIO_VAL_INT;

    case IIO_CHAN_INFO_SCALE:
        *val = info->vref_uv / 1000;   // 分辨率(mV)
        *val2 = info->adc_feature.res_mode;
        return IIO_VAL_FRACTIONAL_LOG2;

    case IIO_CHAN_INFO_SAMP_FREQ:
        *val = info->sample_freq_avail[info->adc_feature.sample_rate];
        *val2 = 0;
        return IIO_VAL_INT;

    default:
        break;
    }

    return -EINVAL;
}

返回值编码说明

返回值 含义
IIO_VAL_INT 整数值,*val即为结果
IIO_VAL_INT_PLUS_MICRO *val + *val2/1000000
IIO_VAL_INT_PLUS_NANO *val + *val2/1000000000
IIO_VAL_FRACTIONAL_LOG2 *val >> *val2

3.5 vf610_adc_isr中断服务函数

static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
{
    struct vf610_adc *info = (struct vf610_adc *)dev_id;
    int coco;

    /* 检查转换完成标志 */
    coco = readl(info->regs + VF610_REG_ADC_HS);
    if (coco & VF610_ADC_HS_COCO0) {
        info->value = vf610_adc_read_data(info);  // 读取ADC原始值
        complete(&info->completion);               // 通知read_raw完成
    }

    return IRQ_HANDLED;
}

ADC数据读取流程

用户空间读取sysfs文件
    → 内核调用vf610_read_raw()
        → 写入HC0寄存器启动转换
        → wait_for_completion()等待中断
            → ADC转换完成,触发中断
            → vf610_adc_isr()读取数据寄存器
            → complete()唤醒等待
        → 返回ADC原始值给用户空间

四、ADC实验验证

4.1 使能内核ADC驱动

-> Device Drivers
   -> Industrial I/O support
      -> Analog to digital converters
         -> <*> Freescale vf610 ADC driver  // 选中

4.2 sysfs接口查看

# 进入IIO设备目录
cd /sys/bus/iio/devices/iio:device0

# 查看ADC原始值
cat in_voltage1_raw
# 输出: 991

# 查看ADC比例(分辨率)
cat in_voltage_scale
# 输出: 0.805664062

# 计算实际电压(mV)
# 实际电压 = in_voltage1_raw × in_voltage_scale
# 991 × 0.805664062 ≈ 798.4 mV

4.3 测试APP

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* IIO框架对应的文件路径 */
static char *file_path[] = {
    "/sys/bus/iio/devices/iio:device0/in_voltage_scale",
    "/sys/bus/iio/devices/iio:device0/in_voltage1_raw",
};

enum path_index {
    IN_VOLTAGE_SCALE = 0,
    IN_VOLTAGE_RAW,
};

struct adc_dev {
    int raw;
    float scale;
    float act;
};

struct adc_dev imx6ulladc;

static int file_data_read(char *filename, char *str)
{
    int ret = 0;
    FILE *data_stream;

    data_stream = fopen(filename, "r");
    if (data_stream == NULL) {
        printf("can't open file %s\r\n", filename);
        return -1;
    }

    ret = fscanf(data_stream, "%s", str);
    if (!ret)
        printf("file read error!\r\n");
    else if (ret == EOF)
        fseek(data_stream, 0, SEEK_SET);

    fclose(data_stream);
    return 0;
}

static int adc_read(struct adc_dev *dev)
{
    int ret = 0;
    char str[50];

    /* 读取scale(浮点数) */
    ret = file_data_read(file_path[IN_VOLTAGE_SCALE], str);
    dev->scale = atof(str);

    /* 读取raw(整数) */
    ret = file_data_read(file_path[IN_VOLTAGE_RAW], str);
    dev->raw = atoi(str);

    /* 转换为实际电压值(mV) */
    dev->act = (dev->scale * dev->raw) / 1000.f;
    return ret;
}

int main(int argc, char *argv[])
{
    if (argc != 1) {
        printf("Error Usage!\r\n");
        return -1;
    }

    while (1) {
        if (adc_read(&imx6ulladc) == 0) {
            printf("ADC原始值:%d,电压值:%.3fV\r\n",
                   imx6ulladc.raw, imx6ulladc.act);
        }
        usleep(100000);  /* 100ms */
    }
    return 0;
}

编译:

arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard adcApp.c -o adcApp

Part 2: IIO子系统

五、IIO子系统概述

⚠️ 来源说明:本节架构图与缓冲区/Trigger 分类不属于《I.MX6U嵌入式Linux驱动开发指南》第七十五章内容,为扩展知识。原书 75.1 节只讲解 iio_deviio_infoiio_chan_spec 三个核心数据结构,并说明 IIO 专为 ADC 类传感器设计。

5.1 IIO子系统架构

graph TB
    subgraph "用户空间"
        APP["应用程序"]
        SYSFS["sysfs文件系统<br>/sys/bus/iio/devices/"]
        CHARDEV["字符设备<br>/dev/iio:deviceX"]
    end

    subgraph "IIO核心层"
        IIO_CORE["IIO Core<br>iio_device_register()<br>iio_push_to_buffers()"]
        IIO_TRIGGER["IIO Trigger Framework<br>iio_trigger_register()"]
    end

    subgraph "IIO驱动层"
        DRV_ADC["ADC驱动<br>vf610_adc.c"]
        DRV_IMU["IMU驱动<br>icm20608.c"]
        DRV_LIGHT["光传感器驱动<br>ap3216c.c"]
    end

    subgraph "硬件层"
        HW_ADC["内部ADC<br>I.MX6ULL"]
        HW_SPI["SPI传感器<br>ICM20608"]
        HW_I2C["I2C传感器<br>AP3216C"]
    end

    APP --> SYSFS
    APP --> CHARDEV
    SYSFS --> IIO_CORE
    CHARDEV --> IIO_CORE
    IIO_CORE --> DRV_ADC
    IIO_CORE --> DRV_IMU
    IIO_CORE --> DRV_LIGHT
    IIO_CORE -.-> IIO_TRIGGER
    DRV_ADC --> HW_ADC
    DRV_IMU --> HW_SPI
    DRV_LIGHT --> HW_I2C

5.2 IIO设备 vs IIO缓冲区

特性 IIO设备(Direct Mode) IIO缓冲区(Buffer Mode)
数据读取方式 sysfs文件逐个读取 通过缓冲区批量读取
触发方式 手动触发(读取时转换) 硬件/软件触发器触发
适用场景 低速采样、偶尔读取 高速连续采样
数据格式 单个值 带时间戳的结构化数据
典型应用 温度计、电压测量 ADC连续采样、IMU数据流

5.3 Trigger机制

IIO Trigger用于启动数据采集,支持三种触发源:

  • 硬件触发器:外部GPIO中断触发
  • 软件触发器:定时器触发
  • 数据就绪触发:传感器数据就绪中断触发

六、IIO核心数据结构

6.1 iio_dev结构体

IIO子系统的核心数据结构,定义在include/linux/iio/iio.h中:

struct iio_dev {
    int id;

    int modes;           // 设备支持的模式
    int currentmode;     // 当前模式
    struct device dev;

    struct iio_event_interface *event_interface;

    struct iio_buffer *buffer;           // 缓冲区
    struct list_head buffer_list;
    int scan_bytes;
    struct mutex mlock;

    const unsigned long *available_scan_masks;
    unsigned masklength;
    const unsigned long *active_scan_mask;
    bool scan_timestamp;
    unsigned scan_index_timestamp;
    struct iio_trigger *trig;            // 触发器
    struct iio_poll_func *pollfunc;

    struct iio_chan_spec const *channels; // 通道描述
    int num_channels;                    // 通道数量

    struct list_head channel_attr_list;
    struct attribute_group chan_attr_group;
    const char *name;                    // 设备名称
    const struct iio_info *info;         // 驱动操作函数集
    struct mutex info_exist_lock;
    const struct iio_buffer_setup_ops *setup_ops;
    struct cdev chrdev;                  // 字符设备
    // ...
};

模式说明

模式 说明
INDIO_DIRECT_MODE 提供sysfs接口,直接读取
INDIO_BUFFER_TRIGGERED 支持硬件触发的缓冲区
INDIO_BUFFER_SOFTWARE 支持软件触发的缓冲区
INDIO_BUFFER_HARDWARE 支持硬件缓冲区

iio_devsetup_ops 成员为 iio_buffer_setup_ops 类型(原书示例代码 75.1.1.2),在使能/禁用缓冲区时被调用;未指定时默认使用 iio_triggered_buffer_setup_ops

struct iio_buffer_setup_ops {
    int (*preenable)(struct iio_dev *);    /* 缓冲区使能之前调用 */
    int (*postenable)(struct iio_dev *);   /* 缓冲区使能之后调用 */
    int (*predisable)(struct iio_dev *);   /* 缓冲区禁用之前调用 */
    int (*postdisable)(struct iio_dev *);  /* 缓冲区禁用之后调用 */
    bool (*validate_scan_mask)(struct iio_dev *indio_dev,
                               const unsigned long *scan_mask);
};

6.2 iio_info结构体

驱动层需要实现的操作函数集:

struct iio_info {
    struct module *driver_module;
    struct attribute_group *event_attrs;
    const struct attribute_group *attrs;

    /* 读取传感器数据 */
    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);

    /* 设置写入数据格式 */
    int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
                             struct iio_chan_spec const *chan,
                             long mask);

    int (*debugfs_reg_access)(struct iio_dev *indio_dev,
                              unsigned int reg, unsigned int writeval,
                              unsigned int *readval);
    // ...
};

6.3 iio_chan_spec结构体

通道描述结构体,定义每个采集通道的属性:

struct iio_chan_spec {
    enum iio_chan_type type;    // 通道类型(电压、温度、加速度等)
    int channel;               // 通道编号(indexed=1时有效)
    int channel2;              // 通道修饰符(modified=1时有效,如X/Y/Z轴)
    unsigned long address;     // 自定义地址(可选)
    int scan_index;            // 扫描索引(buffer模式使用)
    struct {
        char sign;             // 符号:'u'无符号,'s'有符号
        u8 realbits;           // 实际有效位数
        u8 storagebits;        // 存储位数(≥realbits)
        u8 shift;              // 移位量
        u8 repeat;             // 重复次数
        enum iio_endian endianness; // 大小端模式
    } scan_type;

    long info_mask_separate;       // 每通道独立属性掩码
    long info_mask_shared_by_type; // 同类型通道共享属性掩码
    long info_mask_shared_by_dir;  // 同方向通道共享属性掩码
    long info_mask_shared_by_all;  // 所有通道共享属性掩码

    const struct iio_event_spec *event_spec;
    unsigned int num_event_specs;
    const struct iio_chan_spec_ext_info *ext_info;
    const char *extend_name;
    const char *datasheet_name;
    unsigned modified:1;    // channel2为修饰符时置1
    unsigned indexed:1;     // channel为通道编号时置1
    unsigned output:1;      // 输出通道
    unsigned differential:1;// 差分通道
};

通道类型枚举include/uapi/linux/iio/types.h):

enum iio_chan_type {
    IIO_VOLTAGE,          // 电压(ADC)
    IIO_CURRENT,          // 电流
    IIO_POWER,            // 功率
    IIO_ACCEL,            // 加速度
    IIO_ANGL_VEL,         // 角速度(陀螺仪)
    IIO_MAGN,             // 磁场
    IIO_LIGHT,            // 光照
    IIO_INTENSITY,        // 强度
    IIO_PROXIMITY,        // 接近
    IIO_TEMP,             // 温度
    IIO_INCLI,            // 倾斜
    IIO_ROT,              // 旋转角度
    IIO_ANGL,             // 转角
    IIO_TIMESTAMP,        // 时间戳
    IIO_CAPACITANCE,      // 电容
    IIO_PRESSURE,         // 气压
    IIO_HUMIDITYRELATIVE, // 湿度
    IIO_STEPS,            // 步数
    IIO_ENERGY,           // 能量
    IIO_DISTANCE,         // 距离
    IIO_VELOCITY,         // 速度
};

info_mask属性掩码

掩码 sysfs文件名 说明
IIO_CHAN_INFO_RAW raw 原始值
IIO_CHAN_INFO_PROCESSED input 处理后的值
IIO_CHAN_INFO_SCALE scale 比例/分辨率
IIO_CHAN_INFO_OFFSET offset 偏移量
IIO_CHAN_INFO_CALIBBIAS calibbias 校准偏置
IIO_CHAN_INFO_SAMP_FREQ sampling_frequency 采样频率

七、IIO设备树配置

7.1 SPI传感器设备树(ICM20608)

原书 75.3.2 明确说明本章“设备树不需要做任何修改”,ICM20608 的 SPI 节点已在 SPI 实验章节中配置。其驱动在 icm20608_probe 中将 SPI 设为 MODE0(CPOL=0,CPHA=0),因此设备树中不应出现 spi-cpol/spi-cpha

icm20608: icm20608@0 {
    compatible = "alientek,icm20608";
    reg = <0>;                       /* SPI片选 */
    spi-max-frequency = <8000000>;   /* 8MHz */
    /* 不配置 spi-cpol / spi-cpha,驱动内使用 SPI_MODE_0 */
};

7.2 IIO绑定文档

参考:Documentation/devicetree/bindings/iio/adc/vf610-adc.txt

常用IIO设备树属性:

属性 说明
compatible 兼容性字符串
reg 寄存器地址/SPI片选/I2C地址
interrupts 中断定义
clocks 时钟源
spi-max-frequency SPI最大频率
#address-cells 子节点地址单元数
#size-cells 子节点大小单元数

八、IIO驱动API

⚠️ 来源说明iio_device_alloc/iio_priv/iio_device_free/iio_device_register/iio_device_unregister 来自原书 75.1.1 小节;iio_push_to_buffers/iio_triggered_buffer_setup 原书第七十五章未涉及(ICM20608 未实现缓冲区),为扩展知识。

8.1 iio_device_alloc — 申请IIO设备

struct iio_dev *iio_device_alloc(int sizeof_priv)
  • 参数sizeof_priv 私有数据大小
  • 返回值:成功返回iio_dev指针,失败返回NULL
  • 说明:同时分配iio_dev和私有数据内存

使用示例:

struct icm20608_dev *dev;
struct iio_dev *indio_dev;

/* 申请iio_dev,同时分配icm20608_dev内存 */
indio_dev = iio_device_alloc(sizeof(*dev));
if (!indio_dev)
    return -ENOMEM;

/* 获取私有数据地址 */
dev = iio_priv(indio_dev);

也可以使用devm_iio_device_alloc()自动管理内存。

8.2 iio_device_register — 注册IIO设备

int iio_device_register(struct iio_dev *indio_dev)
  • 参数indio_dev 要注册的IIO设备
  • 返回值:0成功,负值失败
  • 说明:注册后用户空间可通过sysfs访问

8.3 iio_push_to_buffers — 推送数据到缓冲区

int iio_push_to_buffers(struct iio_dev *indio_dev, const unsigned char *data)
  • 参数
    • indio_dev:IIO设备
    • data:要推送的数据
  • 说明:在buffer模式下使用,将传感器数据推送到缓冲区

8.4 iio_triggered_buffer_setup — 设置触发缓冲区

int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
                               irq_handler_t pollfunc,
                               irq_handler_t thread_fn,
                               const struct iio_buffer_setup_ops *buf_ops)
  • 参数
    • pollfunc:触发中断处理函数
    • thread_fn:线程化中断处理函数
    • buf_ops:缓冲区操作函数集
  • 说明:设置触发缓冲区模式

8.5 iio_device_unregister — 注销IIO设备

void iio_device_unregister(struct iio_dev *indio_dev)
  • 说明:注销IIO设备,释放资源

8.6 iio_device_free — 释放IIO设备

void iio_device_free(struct iio_dev *indio_dev)
  • 说明:释放iio_dev内存(使用devm分配时无需手动调用)

九、IIO驱动源码分析 — ICM20608实例

9.1 设备结构体

struct icm20608_dev {
    struct spi_device *spi;
    struct regmap *regmap;
    struct regmap_config regmap_config;
    struct mutex lock;
};

9.2 通道定义

/* 陀螺仪分辨率:±250、±500、±1000、±2000°/s */
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};

/* 加速度计分辨率:±2、±4、±8、±16g */
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};

/* 温度参数 */
#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE  326800000

/* 扫描索引枚举(原书 75.3.2,注意顺序为 加速度X/Y/Z、温度、陀螺仪X/Y/Z、时间戳) */
enum inv_icm20608_scan {
    INV_ICM20608_SCAN_ACCL_X,
    INV_ICM20608_SCAN_ACCL_Y,
    INV_ICM20608_SCAN_ACCL_Z,
    INV_ICM20608_SCAN_TEMP,
    INV_ICM20608_SCAN_GYRO_X,
    INV_ICM20608_SCAN_GYRO_Y,
    INV_ICM20608_SCAN_GYRO_Z,
    INV_ICM20608_SCAN_TIMESTAMP,
};

/* 通道定义宏(原书为 3 参数形式) */
#define ICM20608_CHAN(_type, _channel2, _index)  \
    {                                            \
        .type = _type,                           \
        .modified = 1,                           \
        .channel2 = _channel2,                   \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |        \
                              BIT(IIO_CHAN_INFO_CALIBBIAS),  \
        .scan_index = _index,                    \
        .scan_type = {                           \
            .sign = 's',                         \
            .realbits = 16,                      \
            .storagebits = 16,                   \
            .shift = 0,                          \
            .endianness = IIO_BE,                \
        },                                       \
    }

/* ICM20608通道数组(7个通道) */
static const struct iio_chan_spec icm20608_channels[] = {
    /* 温度通道 */
    {
        .type = IIO_TEMP,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                              BIT(IIO_CHAN_INFO_OFFSET) |
                              BIT(IIO_CHAN_INFO_SCALE),
        .scan_index = INV_ICM20608_SCAN_TEMP,
        .scan_type = {
            .sign = 's',
            .realbits = 16,
            .storagebits = 16,
            .shift = 0,
            .endianness = IIO_BE,
        },
    },
    /* 陀螺仪X/Y/Z轴 */
    ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X),
    ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),
    ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),
    /* 加速度计:原书顺序为 Y、X、Z */
    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),
    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),
    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),
};

分辨率计算:陀螺仪以 ±250°/s 量程为例,500 / 2^16 ≈ 0.007629,扩大 1000000 倍为 7629;加速度计以 ±2g 量程为例,4 / 2^16 ≈ 0.000061035,扩大 1000000000 倍为 61035。

9.3 iio_info实现

static const struct iio_info icm20608_info = {
    .read_raw = icm20608_read_raw,
    .write_raw = icm20608_write_raw,
    .write_raw_get_fmt = &icm20608_write_raw_get_fmt,
};

9.4 传感器数据读取

static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,
                                int axis, int *val)
{
    int ind, result;
    __be16 d;

    ind = (axis - IIO_MOD_X) * 2;
    result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);
    if (result)
        return -EINVAL;
    *val = (short)be16_to_cpup(&d);

    return IIO_VAL_INT;
}

static int icm20608_read_channel_data(struct iio_dev *indio_dev,
                                      struct iio_chan_spec const *chan,
                                      int *val)
{
    struct icm20608_dev *dev = iio_priv(indio_dev);
    int ret = 0;

    switch (chan->type) {
    case IIO_ANGL_VEL:  /* 读取陀螺仪数据 */
        ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H,
                                   chan->channel2, val);
        break;
    case IIO_ACCEL:     /* 读取加速度计数据 */
        ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H,
                                   chan->channel2, val);
        break;
    case IIO_TEMP:      /* 读取温度 */
        ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H,
                                   IIO_MOD_X, val);
        break;
    default:
        ret = -EINVAL;
        break;
    }
    return ret;
}

9.5 icm20608_read_raw函数

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 = 0;
    unsigned char regdata = 0;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        /* 读取原始值 */
        iio_device_claim_direct_mode(indio_dev);
        mutex_lock(&dev->lock);
        ret = icm20608_read_channel_data(indio_dev, chan, val);
        mutex_unlock(&dev->lock);
        iio_device_release_direct_mode(indio_dev);
        return ret;

    case IIO_CHAN_INFO_SCALE:
        switch (chan->type) {
        case IIO_ANGL_VEL:  /* 陀螺仪分辨率 */
            mutex_lock(&dev->lock);
            regdata = (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG)
                       & 0X18) >> 3;
            *val = 0;
            *val2 = gyro_scale_icm20608[regdata];
            mutex_unlock(&dev->lock);
            return IIO_VAL_INT_PLUS_MICRO;
        case IIO_ACCEL:     /* 加速度计分辨率 */
            mutex_lock(&dev->lock);
            regdata = (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG)
                       & 0X18) >> 3;
            *val = 0;
            *val2 = accel_scale_icm20608[regdata];
            mutex_unlock(&dev->lock);
            return IIO_VAL_INT_PLUS_NANO;
        case IIO_TEMP:      /* 温度分辨率 */
            *val = ICM20608_TEMP_SCALE / 1000000;
            *val2 = ICM20608_TEMP_SCALE % 1000000;
            return IIO_VAL_INT_PLUS_MICRO;
        default:
            return -EINVAL;
        }

    case IIO_CHAN_INFO_OFFSET:  /* 温度偏移值 */
        switch (chan->type) {
        case IIO_TEMP:
            *val = ICM20608_TEMP_OFFSET;
            return IIO_VAL_INT;
        default:
            return -EINVAL;
        }

    case IIO_CHAN_INFO_CALIBBIAS:  /* 校准偏置 */
        switch (chan->type) {
        case IIO_ANGL_VEL:
            mutex_lock(&dev->lock);
            ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH,
                                       chan->channel2, val);
            mutex_unlock(&dev->lock);
            return ret;
        case IIO_ACCEL:
            mutex_lock(&dev->lock);
            ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H,
                                       chan->channel2, val);
            mutex_unlock(&dev->lock);
            return ret;
        default:
            return -EINVAL;
        }

    default:
        return ret - EINVAL;
    }
}

9.6 icm20608_probe函数

static int icm20608_probe(struct spi_device *spi)
{
    int ret;
    struct icm20608_dev *dev;
    struct iio_dev *indio_dev;

    /* 1. 申请iio_dev内存 */
    indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));
    if (!indio_dev)
        return -ENOMEM;

    /* 2. 获取设备结构体地址 */
    dev = iio_priv(indio_dev);
    dev->spi = spi;
    spi_set_drvdata(spi, indio_dev);
    mutex_init(&dev->lock);

    /* 3. 初始化iio_dev */
    indio_dev->dev.parent = &spi->dev;
    indio_dev->info = &icm20608_info;
    indio_dev->name = "icm20608";
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = icm20608_channels;
    indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);

    /* 4. 注册iio_dev */
    ret = iio_device_register(indio_dev);
    if (ret < 0) {
        dev_err(&spi->dev, "iio_device_register failed\n");
        goto err_iio_register;
    }

    /* 5. 初始化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);
    if (IS_ERR(dev->regmap)) {
        ret = PTR_ERR(dev->regmap);
        goto err_regmap_init;
    }

    /* 6. 初始化SPI */
    spi->mode = SPI_MODE_0;
    spi_setup(spi);

    /* 7. 初始化ICM20608寄存器 */
    icm20608_reginit(dev);
    return 0;

err_regmap_init:
    iio_device_unregister(indio_dev);
err_iio_register:
    return ret;
}

9.7 icm20608_remove函数

static int icm20608_remove(struct spi_device *spi)
{
    struct iio_dev *indio_dev = spi_get_drvdata(spi);
    struct icm20608_dev *dev;

    dev = iio_priv(indio_dev);

    /* 1. 删除regmap */
    regmap_exit(dev->regmap);

    /* 2. 注销IIO */
    iio_device_unregister(indio_dev);
    return 0;
}

9.8 SPI驱动框架

static const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608", 0},
    {}
};

static const struct of_device_id icm20608_of_match[] = {
    { .compatible = "alientek,icm20608" },
    { /* Sentinel */ }
};

static struct spi_driver icm20608_driver = {
    .probe = icm20608_probe,
    .remove = icm20608_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "icm20608",
        .of_match_table = icm20608_of_match,
    },
    .id_table = icm20608_id,
};

static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}

static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");

9.9 写通道数据 — icm20608_write_raw(量程与校准值)

原书 75.3.4 实现用户空间往驱动写数据,用于设置陀螺仪/加速度计的量程(分辨率)和校准值。配套函数如下:

/* 向指定通道寄存器写入数据,用于设置校准值 */
static int icm20608_sensor_set(struct icm20608_dev *dev, int reg,
                               int axis, int val)
{
    int ind, result;
    __be16 d = cpu_to_be16(val);

    ind = (axis - IIO_MOD_X) * 2;
    result = regmap_bulk_write(dev->regmap, reg + ind, (u8 *)&d, 2);
    if (result)
        return -EINVAL;
    return 0;
}

/* 设置陀螺仪量程:在 gyro_scale_icm20608[] 中查找匹配值,换算成 GYRO_CONFIG 的 FS_SEL */
static int icm20608_write_gyro_scale(struct icm20608_dev *dev, int val)
{
    int result, i;
    u8 d;

    for (i = 0; i < ARRAY_SIZE(gyro_scale_icm20608); ++i) {
        if (gyro_scale_icm20608[i] == val) {
            d = (i << 3);
            result = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);
            if (result)
                return result;
            return 0;
        }
    }
    return -EINVAL;
}

/* 设置加速度计量程:在 accel_scale_icm20608[] 中查找匹配值,换算成 ACCEL_CONFIG 的 AFS_SEL */
static int icm20608_write_accel_scale(struct icm20608_dev *dev, int val)
{
    int result, i;
    u8 d;

    for (i = 0; i < ARRAY_SIZE(accel_scale_icm20608); ++i) {
        if (accel_scale_icm20608[i] == val) {
            d = (i << 3);
            result = regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, d);
            if (result)
                return result;
            return 0;
        }
    }
    return -EINVAL;
}

static int icm20608_write_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 = 0;

    iio_device_claim_direct_mode(indio_dev);
    switch (mask) {
    case IIO_CHAN_INFO_SCALE:            /* 设置陀螺仪和加速度计的分辨率 */
        switch (chan->type) {
        case IIO_ANGL_VEL:
            mutex_lock(&dev->lock);
            ret = icm20608_write_gyro_scale(dev, val2);
            mutex_unlock(&dev->lock);
            break;
        case IIO_ACCEL:
            mutex_lock(&dev->lock);
            ret = icm20608_write_accel_scale(dev, val2);
            mutex_unlock(&dev->lock);
            break;
        default:
            ret = -EINVAL;
            break;
        }
        break;
    case IIO_CHAN_INFO_CALIBBIAS:        /* 设置陀螺仪和加速度计的校准值 */
        switch (chan->type) {
        case IIO_ANGL_VEL:
            mutex_lock(&dev->lock);
            ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);
            mutex_unlock(&dev->lock);
            break;
        case IIO_ACCEL:
            mutex_lock(&dev->lock);
            ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H, chan->channel2, val);
            mutex_unlock(&dev->lock);
            break;
        default:
            ret = -EINVAL;
            break;
        }
        break;
    default:
        ret = -EINVAL;
        break;
    }
    iio_device_release_direct_mode(indio_dev);
    return ret;
}

/* 指定用户空间写入的数据格式:陀螺仪扩大1e6倍,其余(加速度计)扩大1e9倍 */
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
                                      struct iio_chan_spec const *chan, long mask)
{
    switch (mask) {
    case IIO_CHAN_INFO_SCALE:
        switch (chan->type) {
        case IIO_ANGL_VEL:
            return IIO_VAL_INT_PLUS_MICRO;
        default:
            return IIO_VAL_INT_PLUS_NANO;
        }
    default:
        return IIO_VAL_INT_PLUS_MICRO;
    }
}

要点:ICM20_GYRO_CONFIG 的 bit4:3(FS_SEL)和 ICM20_ACCEL_CONFIG 的 bit4:3(AFS_SEL)决定量程,写值 i << 3in_accel_scale 返回 IIO_VAL_INT_PLUS_NANO,所以用户空间写 0.000122070(±4g)时传入驱动的是 122070


十、IIO实验验证

10.1 内核配置与驱动加载

内核需使能 IIO 及缓冲区相关选项(原书 75.3.1):

-> Device Drivers
    -> Industrial I/O support (IIO [=y])
        -> [*] Enable buffer support within IIO
        -> <*> Industrial I/O buffering based on kfifo

编译并加载驱动、运行测试 APP(原书 75.3.2 / 75.4.3):

depmod                  # 第一次加载驱动时需要
modprobe icm20608.ko    # 加载驱动模块

arm-linux-gnueabihf-gcc icm20608App.c -o icm20608App   # 编译测试APP
./icm20608App          # 运行,持续打印陀螺仪/加速度计/温度数据

10.2 IIO设备目录结构

# 查看所有IIO设备
ls /sys/bus/iio/devices/
# iio:device0  →  I.MX6ULL内部ADC
# iio:device1  →  ICM20608 (SPI)

# 进入ICM20608设备目录
cd /sys/bus/iio/devices/iio:device1

# 查看文件列表
ls
# in_accel_scale          加速度计比例
# in_accel_x_raw         加速度计X轴原始值
# in_accel_x_calibbias   加速度计X轴校准值
# in_accel_y_raw         加速度计Y轴原始值
# in_anglvel_scale       陀螺仪比例
# in_anglvel_x_raw       陀螺仪X轴原始值
# in_temp_raw            温度原始值
# in_temp_offset         温度偏移
# in_temp_scale          温度比例

10.3 sysfs文件命名规则

文件命名模式:[direction]_[type]_[index]_[modifier]_[info_mask]

组成部分 说明 示例
direction 方向(in/out) in
type 通道类型 accelanglvelvoltage
index 通道编号 xyz1
modifier 修饰符 xyz
info_mask 属性掩码 rawscalecalibbias

示例in_accel_x_raw

  • in → 输入方向
  • accel → 加速度类型(IIO_ACCEL)
  • x → X轴(IIO_MOD_X)
  • raw → 原始值(IIO_CHAN_INFO_RAW)

10.4 读取测试

# 读取加速度计比例(默认量程±16g)
cat in_accel_scale
# 输出: 0.000488281

# 读取加速度计Z轴原始值(静止时Z轴约为1g)
cat in_accel_z_raw
# 输出: 2074(原书图 75.3.3.2)

# 计算实际加速度
# 2074 × 0.000488281 ≈ 1.01g

# 读取陀螺仪X轴原始值
cat in_anglvel_x_raw

# 读取温度原始值
cat in_temp_raw

10.5 iio_info工具

# 安装libiio
apt-get install libiio-dev iio-utils

# 查看IIO设备信息
iio_info -s

# 读取指定设备
iio_readdev -u iio:device0 voltage0

# 查看/设置设备属性
iio_attr -d iio:device1

10.6 测试APP(icm20608App.c)

IIO 的 sysfs 文件内容都是字符串(如 in_accel_scale 内容为 "0.000488281"),因此读取时要先按字符串取出,再用 atof/atoi 转换。原书 75.4.1 用到的标准 I/O 文件流 API:

函数 原型 说明
fopen FILE *fopen(const char *pathname, const char *mode) 打开文件流,mode 可取 r/r+/w/w+/a/a+
fclose int fclose(FILE *stream) 关闭文件流,成功返回0,失败返回EOF
fread size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从流读取对象
fwrite size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 向流写入对象
fscanf int fscanf(FILE *stream, const char *format, ...) 格式化读取,遇空格/换行结束

核心转换逻辑(原书示例代码 75.4.2.1):

/* 读取文件路径(注意 ICM20608 是 iio:device1) */
static int sensor_read(struct icm20608_dev *dev)
{
    int ret = 0;
    char str[50];

    /* 1、获取陀螺仪原始数据与比例 */
    SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);
    SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);
    SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);
    SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);

    /* 2、获取加速度计原始数据与比例 */
    SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);
    SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);
    SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);
    SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);

    /* 3、获取温度原始值、offset、scale */
    SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);
    SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);
    SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);

    /* 4、换算为实际值 */
    dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;
    dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;
    dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;

    dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;
    dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;
    dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;

    dev->temp_act = ((dev->temp_raw - dev->temp_offset) / dev->temp_scale) + 25;
    return ret;
}
  • SENSOR_FLOAT_DATA_GET / SENSOR_INT_DATA_GET 是形参化宏,内部依次调用 file_data_read() + atof() / atoi()
  • 温度换算:temp_act = (temp_raw - temp_offset) / temp_scale + 25

十一、跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

11.1 I.MX6ULL vs STM32 vs RK3568 ADC/IIO差异

特性 I.MX6ULL STM32MP1 RK3568
ADC分辨率 12位 12位 10位
ADC通道数 2通道 6通道 8通道
ADC控制器 vf610_adc stm32-adc rk30_adc
IIO驱动 drivers/iio/adc/vf610_adc.c drivers/iio/adc/stm32-adc.c drivers/iio/adc/rk3x-adc.c
参考电压 外部regulator 内部参考 外部regulator
设备树compatible "fsl,imx6ul-adc" "st,stm32h7-adc" "rockchip,rk3066-adc"
触发方式 软件触发 定时器/外部触发 软件触发
buffer支持 支持 支持 支持
IIO驱动框架 platform + IIO platform + IIO platform + IIO

11.2 驱动编写差异

I.MX6ULL(vf610_adc)

  • 使用regulator获取参考电压
  • 中断方式读取数据
  • completion机制同步

STM32MP1(stm32-adc)

  • 支持DMA传输
  • 支持定时器触发
  • 多通道扫描模式

RK3568(rk3x-adc)

  • 简单的轮询方式
  • 软件触发转换
  • 分辨率较低(10位)

11.3 IIO通用性

尽管各平台ADC硬件不同,但IIO框架提供了统一的用户空间接口:

  • 相同的sysfs文件结构
  • 相同的ioctl接口
  • 相同的缓冲区机制
  • 相同的trigger框架

十二、面试精选

⚠️ 来源说明:本节面试题不属于《I.MX6U嵌入式Linux驱动开发指南》正文内容,为扩展知识(部分结论可对应原书 75.1 小节)。

Q1: IIO子系统与input子系统有什么区别?

  • input子系统:主要用于输入设备(键盘、鼠标、触摸屏等),处理的是离散的事件(按键按下/释放)
  • IIO子系统:主要用于模拟量传感器(ADC、陀螺仪、加速度计等),处理的是连续的模拟量数字化数据
  • 关键区别:input输出事件流,IIO输出数据流;IIO支持buffer和trigger机制,适合高速连续采样

Q2: IIO驱动中read_raw函数的返回值如何编码小数?

: IIO使用valval2两个整数参数编码小数:

  • IIO_VAL_INT:纯整数,结果=val
  • IIO_VAL_INT_PLUS_MICRO:结果=val + val2/1000000(微精度)
  • IIO_VAL_INT_PLUS_NANO:结果=val + val2/1000000000(纳精度)
  • IIO_VAL_FRACTIONAL_LOG2:结果=val >> val2(用于2的幂次比例)

例如:分辨率0.000488281返回IIO_VAL_INT_PLUS_NANO,val=0,val2=488281

Q3: IIO通道的info_mask_separate和info_mask_shared_by_type有什么区别?

  • info_mask_separate:每个通道独立的属性,在sysfs中为每个通道创建独立文件(如in_accel_x_rawin_accel_y_raw
  • info_mask_shared_by_type:同类型通道共享的属性,只创建一个文件(如in_accel_scale所有轴共用)

设计意义:原始值每个轴独立,所以用separate;分辨率/量程所有轴相同,所以用shared_by_type

Q4: IIO驱动中如何处理多通道数据采集?

  1. 通道定义:在iio_chan_spec数组中定义每个通道的type、channel、scan_index等
  2. scan_index:buffer模式下,通过scan_index确定数据在缓冲区中的位置
  3. active_scan_mask:指定哪些通道参与扫描
  4. 数据打包iio_push_to_buffers将多通道数据按scan_index顺序打包
  5. 时间戳:可选地在数据末尾添加64位时间戳

Q5: IIO缓冲区模式与直接模式的性能差异?

特性 直接模式(INDIO_DIRECT_MODE) 缓冲区模式
数据获取 每次read触发一次转换 连续转换,批量读取
延迟 高(每次转换+系统调用开销) 低(硬件DMA传输)
CPU占用 高(轮询/中断等待) 低(DMA+中断批量处理)
采样率 受限于系统调用频率 可达硬件最高采样率
适用场景 低速、偶发读取 高速连续采集

缓冲区模式适合IMU、ADC连续采样等高带宽场景,直接模式适合温度计、电压监测等低速场景。


参考资料

  • 正点原子 I.MX6U嵌入式Linux驱动开发指南 V2.0.1
  • 第七十五章 Linux IIO驱动实验
  • 第七十六章 Linux ADC驱动实验
  • Linux内核文档:Documentation/driver-api/iio/index.rst
  • 内核源码:drivers/iio/adc/vf610_adc.c

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第七十五章 Linux IIO驱动实验

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第七十六章 Linux ADC驱动实验