13. Linux内核IIO子系统

IIO(Industrial I/O,工业I/O)专门用于管理模拟量和混合信号设备,核心目标是为传感器、ADC(模数转换器)、DAC(数模转换器)、陀螺仪、加速度计、温度传感器等设备提供统一的驱动开发接口和用户层访问方式, 简化驱动开发流程,实现设备的标准化管理。无论是嵌入式设备中的各类传感器,还是工业场景中的模拟量采集设备,都可以基于IIO子系统快速实现驱动开发,降低开发难度,提升驱动的可移植性和兼容性。

13.1. IIO子系统核心概念

13.1.1. 核心定义

IIO子系统(Industrial I/O Subsystem)是Linux内核中用于管理模拟量输入/输出设备和混合信号设备的子系统,其核心定位是“统一模拟量设备的驱动开发和用户层访问接口”。

与Input子系统相比,IIO子系统的适用场景更聚焦于“数据采集与模拟量控制”:Input子系统主要处理离散的人机交互事件(如按键按下、触摸滑动), 而IIO子系统主要处理连续的模拟量数据(如温度、加速度、电压、电流等);

与通用字符设备驱动相比,IIO子系统提供了标准化的接口和框架,无需开发者从零实现设备注册、数据读写、sysfs节点创建等重复工作,大幅提升开发效率。

13.1.2. 架构分层

Linux内核IIO子系统采用分层架构,从上到下分为应用层、核心层、驱动层,具体如下:

  1. 应用层:通过sysfs接口或IIO字符设备接口访问IIO设备,获取传感器数据、配置设备参数;

  2. 核心层:IIO子系统的核心,负责设备注册、sysfs节点创建、数据管理、事件触发等通用功能,为驱动层提供标准化的接口;

  3. 驱动层:针对具体的IIO设备开发的驱动程序,实现设备的硬件初始化、数据读取/写入、参数配置等硬件相关逻辑,调用核心层接口完成设备注册。

分层架构的优势在于:驱动层只需关注硬件细节,核心层提供通用功能,应用层通过统一接口访问设备,降低了驱动开发难度,提升了系统的可维护性。

13.1.3. IIO设备

IIO设备(IIO Device)是IIO子系统管理的核心对象,对应一个实际的硬件设备。在Linux内核中,IIO设备通过struct iio_dev结构体描述,驱动开发者需初始化该结构体,并注册到IIO核心层,完成设备的注册。

IIO设备分为两种类型:

  • 模拟量输入设备(IIO_INPUT):用于采集模拟量数据(如传感器、ADC);

  • 模拟量输出设备(IIO_OUTPUT):用于输出模拟量信号(如DAC)。

13.1.4. IIO通道

IIO通道(IIO Channel)是IIO设备的最小功能单元,对应设备的一个模拟量输入/输出通道。 例如,一个加速度计有X、Y、Z三个加速度通道,一个ADC芯片有4个模拟量输入通道,每个通道对应一个独立的数据流。

IIO通道通过struct iio_chan_spec结构体描述,每个通道需指定通道类型、通道方向、缩放系数等参数,驱动开发者需将通道信息注册到IIO设备中。

13.1.5. IIO缓冲

IIO缓冲(IIO Buffer)用于缓存设备采集的连续数据,适用于高采样率的场景(如高速ADC采集)。通过缓冲机制,可减少CPU的中断次数,提升数据采集效率。 IIO缓冲支持环形缓冲,当缓冲满时,会触发中断或通知用户层读取数据。

IIO缓冲通过struct iio_buffer结构体描述,核心层提供了缓冲的创建、销毁、数据读写等通用接口,驱动层只需配置缓冲参数,即可使用缓冲功能。

13.1.6. IIO事件

IIO事件(IIO Event)用于通知用户层设备的状态变化。驱动层可配置事件触发条件(如数据阈值、缓冲满),当条件满足时,IIO核心层会向用户层发送事件通知,用户层通过poll()等机制监听事件。

13.1.7. sysfs接口

IIO子系统通过sysfs文件系统为用户层提供访问接口,当IIO设备注册成功后,核心层会自动在/sys/bus/iio/devices/目录下创建对应的设备目录(如iio:device0),目录下包含多个文件,用于读取数据、配置设备参数。

常见的sysfs接口文件:

  • in_xxx_raw:读取原始数据;

  • in_xxx_scale:读取缩放系数;

13.2. IIO子系统核心结构体

IIO子系统的核心结构体定义在内核头文件中,以下重点解析最常用的核心结构体。

13.2.1. IIO设备核心结构体

IIO设备核心结构体(struct iio_dev)是IIO设备的核心描述结构体,驱动开发者需通过devm_iio_device_alloc函数分配该结构体,初始化其成员(如name、info、channels等),然后通过iio_device_register函数注册到IIO核心层,完成设备的注册。

iio_dev结构体(内核源码/include/linux/iio/iio.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct iio_dev {
    int id;                               /* IIO设备ID,系统自动分配,用于区分多个IIO设备 */
    struct module *driver_module;         /* 驱动模块指针,关联驱动所属模块 */
    int modes;                            /* IIO设备工作模式,如INDIO_DIRECT_MODE */
    int currentmode;                      /* 设备当前工作模式 */
    struct device dev;                    /* 设备结构体,关联Linux设备模型 */
    struct iio_buffer *buffer;            /* IIO缓冲结构体,用于数据缓存 */
    struct iio_trigger *trig;             /* 关联的IIO事件触发结构体 */
    const struct iio_chan_spec *channels; /* IIO通道描述数组,存储设备的所有通道 */
    int num_channels;                     /* 通道数量,即channels数组的长度 */
    const char *name;                     /* IIO设备名称 */
    const struct iio_info *info;          /* IIO设备操作函数集,绑定数据读写等回调 */
    struct mutex mlock;                   /* 互斥锁,保护设备并发访问 */
    unsigned long flags;                  /* 设备状态标志位 */
    /* 其他成员省略 */
};

13.2.2. IIO通道描述结构体

IIO通道描述结构体(struct iio_chan_spec)用于描述IIO设备的单个通道,驱动开发者需根据设备的硬件特性,定义通道数组,指定每个通道的类型、修饰符、支持的sysfs节点等参数。

iio_chan_spec结构体(内核源码/include/linux/iio/iio.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct iio_chan_spec {
    enum iio_chan_type type;           /* 通道类型,如IIO_ACCEL、IIO_VOLTAGE */
    int channel;                       /* 通道编号,区分同一类型的多个通道 */
    int channel2;                      /* 通道修饰符,如IIO_MOD_X、IIO_MOD_Y */
    unsigned long address;             /* 通道地址/索引,供驱动层区分不同通道 */
    int scan_index;                    /* 通道在缓冲中的索引,用于缓冲数据排序 */
    struct {                           /* 扫描类型配置,描述通道数据格式 */
        char    sign;                  /* 数据符号(有符号/无符号) */
        u8      realbits;              /* 数据有效位数 */
        u8      storagebits;           /* 数据存储位数 */
        u8      shift;                 /* 数据移位量 */
        enum iio_endian endianness;    /* 数据字节序 */
    } scan_type;
    long info_mask_separate;           /* 独立通道信息掩码,指定支持的sysfs节点(如RAW、SCALE) */
    const struct iio_chan_spec_ext_info *ext_info; /* 扩展信息,用于添加自定义sysfs节点 */
    unsigned modified:1;               /* 是否启用通道修饰符,1=启用 */
    unsigned indexed:1;                /* 是否启用通道索引,1=启用 */
    unsigned output:1;                 /* 是否为输出通道(DAC),1=输出 */
    unsigned differential:1;           /* 是否为差分通道,1=差分 */
    /* 其他成员省略 */
};

例如,加速度计的X轴通道,type为IIO_ACCEL,modified为1,channel2为IIO_MOD_X,info_mask_separate为BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),表示该通道支持读取原始数据和缩放系数。

13.2.3. IIO设备操作函数集

IIO设备操作函数集(struct iio_info)是IIO设备的操作函数集,驱动开发者需实现对应的回调函数,供IIO核心层调用。

iio_info结构体(内核源码/include/linux/iio/iio.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct iio_info {
    const 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 (*read_event_config)(struct iio_dev *indio_dev,
                    const struct iio_chan_spec *chan,
                    enum iio_event_type type,
                    enum iio_event_direction dir);

    /* 写入事件配置回调 */
    int (*write_event_config)(struct iio_dev *indio_dev,
                    const struct iio_chan_spec *chan,
                    enum iio_event_type type,
                    enum iio_event_direction dir,
                    int state);

    /* 更新扫描模式回调 */
    int (*update_scan_mode)(struct iio_dev *indio_dev,
                    const unsigned long *scan_mask);
    /* 其他成员省略 */
};

其中,read_raw是最核心的回调函数,用于读取通道的原始数据,如传感器的原始采样值;write_raw用于向通道写入数据,适用于DAC设备。

规定read_raw/write_raw回调返回以下宏后,内核IIO子系统自动完成对val和val2两个参数数据格式化:

IIO数据返回格式枚举宏(内核源码/include/linux/iio/types.h)
1
2
3
4
5
6
7
#define IIO_VAL_INT 1               /* 纯整数格式:val = 有效数据,val2 未使用,如ADC原始值=17607 -> 仅设置 val=17607 */
#define IIO_VAL_INT_PLUS_MICRO 2    /* 整数+微位:val=整数部分,val2=微位(10⁻⁶)部分,如时间1.234567秒 -> 设置 val=1, val2=234567 */
#define IIO_VAL_INT_PLUS_NANO 3     /* 整数+纳位:val=整数部分,val2=纳位(10⁻⁹)部分,如时间1.234567890秒 -> 设置 val=1, val2=234567890 */
#define IIO_VAL_INT_PLUS_MICRO_DB 4 /* 整数+微分贝:val=整数,val2=微分贝,如幅度10.1234 dB -> 设置 val=10, val2=1234 */
#define IIO_VAL_INT_MULTIPLE 5      /* 整数倍数值:val=基准值,val2=倍数,如基准值100 × 5倍 -> 设置 val=100, val2=5 */
#define IIO_VAL_FRACTIONAL 10       /* 普通分数:val=分子,val2=分母,如分数3/4 -> 设置 val=3, val2=4 */
#define IIO_VAL_FRACTIONAL_LOG2 11  /* 2的指数分数:val=分子,val2=2的指数,如ADC缩放系数=6144/32768 -> 设置 val=6144, val2=15(2^15=32768)*/

13.2.4. IIO缓冲结构体

IIO缓冲结构体(struct iio_buffer)用于描述IIO设备的缓冲,驱动开发者可通过iio_buffer_alloc函数创建缓冲,配置缓冲长度、数据项大小等参数,然后通过iio_buffer_attach_device函数将缓冲绑定到IIO设备,实现数据的缓存功能。

iio_buffer结构体(内核源码/include/linux/iio/buffer_impl.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct iio_buffer {
    unsigned int length;               /* 缓冲中的数据项数量 */
    size_t bytes_per_datum;            /* 单个数据项大小 */
    const struct iio_buffer_access_funcs *access; /* 缓冲访问函数集 */
    long *scan_mask;                   /* 扫描模式元素掩码 */
    wait_queue_head_t pollq;           /* 等待队列,用于缓冲轮询 */
    unsigned int watermark;            /* 轮询/读取需等待的数据项数量 */
    bool scan_timestamp;               /* 扫描模式是否包含时间戳 */
    struct kref ref;                   /* 缓冲引用计数 */
    struct list_head buffer_list;      /* 设备当前缓冲链表的节点 */
    /* 其他成员省略 */
};

13.2.5. IIO事件触发结构体

IIO事件触发结构体(struct iio_trigger)用于描述IIO事件触发源,触发源可以是硬件中断(如ADC采集完成中断)、定时器等。驱动开发者需实现触发的回调函数,当触发条件满足时,调用event回调发送事件通知,触发缓冲数据读取或用户层事件监听。

iio_trigger结构体(内核源码/include/linux/iio/trigger.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct iio_trigger {
    const struct iio_trigger_ops    *ops;        /* 触发操作函数集,绑定触发相关回调 */
    struct module                   *owner;      /* 模块所有者 */
    int                             id;          /* 触发ID,系统自动分配 */
    const char                      *name;       /* 触发名称,用于标识触发源 */
    struct device                   dev;         /* 触发对应的设备结构体 */
    struct list_head                list;        /* 链表节点,加入IIO核心的触发链表 */
    atomic_t                        use_count;   /* 触发引用计数 */
    /* 其他成员省略 */
};

13.3. IIO子系统核心枚举类型

IIO子系统中定义了多个核心枚举类型,用于描述通道类型、通道修饰符、事件类型和事件方向,是IIO驱动开发中配置通道、事件的基础,以下详细解析常用枚举的定义及用途。

13.3.1. IIO通道类型枚举

IIO通道类型枚举(enum iio_chan_type)用于定义IIO通道的类型,对应设备的核心功能,驱动开发者在配置struct iio_chan_spec结构体时,需通过type成员指定通道类型。

IIO通道类型枚举(内核源码/include/uapi/linux/iio/types.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
enum iio_chan_type {
    IIO_VOLTAGE,              /* 电压通道 */
    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_ALTVOLTAGE,           /* 交流电压通道 */
    IIO_CCT,                  /* 相关色温通道 */
    IIO_PRESSURE,             /* 压力通道 */
    IIO_HUMIDITYRELATIVE,     /* 相对湿度通道 */
    IIO_ACTIVITY,             /* 活动状态通道 */
    IIO_STEPS,                /* 步数通道 */
    IIO_ENERGY,               /* 能量通道 */
    IIO_DISTANCE,             /* 距离通道 */
    IIO_VELOCITY,             /* 速度通道 */
    IIO_CONCENTRATION,        /* 浓度通道 */
    IIO_RESISTANCE,           /* 电阻通道 */
    IIO_PH,                   /* pH值通道 */
    IIO_UVINDEX,              /* 紫外线指数通道 */
    IIO_ELECTRICALCONDUCTIVITY, /* 电导率通道 */
    IIO_COUNT,                /* 计数通道 */
    IIO_INDEX,                /* 索引通道 */
    IIO_GRAVITY,              /* 重力通道 */
    IIO_POSITIONRELATIVE,     /* 相对位置通道 */
    IIO_PHASE,                /* 相位通道 */
};

常用类型包括IIO_ACCEL(加速度)、IIO_TEMP(温度)、IIO_VOLTAGE(电压)、IIO_PRESSURE(压力)等,与具体的传感器设备功能对应。

13.3.2. IIO通道修饰符枚举

IIO通道修饰符枚举(enum iio_modifier)用于修饰通道的具体属性,配合enum iio_chan_type使用,明确通道的细分功能,驱动开发者在配置struct iio_chan_spec的channel2成员时使用。

IIO通道类型枚举(内核源码/include/uapi/linux/iio/types.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
enum iio_modifier {
    IIO_NO_MOD,               /* 无修饰符 */
    IIO_MOD_X,                /* X轴修饰符 */
    IIO_MOD_Y,                /* Y轴修饰符 */
    IIO_MOD_Z,                /* Z轴修饰符 */
    IIO_MOD_X_AND_Y,          /* X轴和Y轴联合修饰符 */
    IIO_MOD_X_AND_Z,          /* X轴和Z轴联合修饰符 */
    IIO_MOD_Y_AND_Z,          /* Y轴和Z轴联合修饰符 */
    IIO_MOD_X_AND_Y_AND_Z,    /* X、Y、Z三轴联合修饰符 */
    IIO_MOD_X_OR_Y,           /* X轴或Y轴修饰符 */
    IIO_MOD_X_OR_Z,           /* X轴或Z轴修饰符 */
    IIO_MOD_Y_OR_Z,           /* Y轴或Z轴修饰符 */
    IIO_MOD_X_OR_Y_OR_Z,      /* X、Y、Z三轴任意一个修饰符 */
    IIO_MOD_LIGHT_BOTH,       /* 光传感器双路修饰符 */
    IIO_MOD_LIGHT_IR,         /* 红外光修饰符 */
    IIO_MOD_ROOT_SUM_SQUARED_X_Y, /* X、Y轴平方和开根号修饰符 */
    IIO_MOD_SUM_SQUARED_X_Y_Z, /* X、Y、Z三轴平方和修饰符 */
    IIO_MOD_LIGHT_CLEAR,      /* 可见光修饰符 */
    IIO_MOD_LIGHT_RED,        /* 红光修饰符 */
    IIO_MOD_LIGHT_GREEN,      /* 绿光修饰符 */
    IIO_MOD_LIGHT_BLUE,       /* 蓝光修饰符 */
    IIO_MOD_QUATERNION,       /* 四元数修饰符 */
    IIO_MOD_TEMP_AMBIENT,     /* 环境温度修饰符 */
    IIO_MOD_TEMP_OBJECT,      /* 物体温度修饰符 */
    IIO_MOD_NORTH_MAGN,       /* 磁北修饰符 */
    IIO_MOD_NORTH_TRUE,       /* 真北修饰符 */
    IIO_MOD_NORTH_MAGN_TILT_COMP, /* 磁北倾斜补偿修饰符 */
    IIO_MOD_NORTH_TRUE_TILT_COMP, /* 真北倾斜补偿修饰符 */
    IIO_MOD_RUNNING,          /* 跑步状态修饰符 */
    IIO_MOD_JOGGING,          /* 慢跑状态修饰符 */
    IIO_MOD_WALKING,          /* 行走状态修饰符 */
    IIO_MOD_STILL,            /* 静止状态修饰符 */
    IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z, /* X、Y、Z三轴平方和开根号修饰符 */
    IIO_MOD_I,                /* 电流I路修饰符 */
    IIO_MOD_Q,                /* 电流Q路修饰符 */
    IIO_MOD_CO2,              /* 二氧化碳修饰符 */
    IIO_MOD_VOC,              /* 挥发性有机物修饰符 */
    IIO_MOD_LIGHT_UV,         /* 紫外线修饰符 */
    IIO_MOD_LIGHT_DUV,        /* 深紫外线修饰符 */
};

常用修饰符包括IIO_MOD_X/Y/Z(三轴修饰符,用于加速度计、陀螺仪等)、IIO_MOD_TEMP_AMBIENT(环境温度)、IIO_MOD_LIGHT_RED/GREEN/BLUE(色光修饰符)等。

13.3.3. IIO事件类型枚举

IIO事件类型枚举(enum iio_event_type)用于定义IIO事件的类型,描述设备状态变化的具体形式,驱动开发者在实现事件相关回调函数时使用。

IIO事件类型枚举(内核源码/include/uapi/linux/iio/types.h)
1
2
3
4
5
6
enum iio_event_direction {
    IIO_EV_DIR_EITHER,    /* 双向事件,上升沿和下降沿均触发 */
    IIO_EV_DIR_RISING,    /* 上升沿事件,数据从低到高变化触发 */
    IIO_EV_DIR_FALLING,   /* 下降沿事件,数据从高到低变化触发 */
    IIO_EV_DIR_NONE,      /* 无方向事件,不触发任何方向 */
};

例如配置“温度高于30℃触发事件”时,使用IIO_EV_DIR_RISING。

13.4. IIO子系统驱动核心函数

IIO子系统提供了丰富的核心函数,用于IIO设备的分配、初始化、注册等操作,以下重点解析驱动开发中最常用的核心函数。

13.4.1. 分配IIO设备结构体

13.4.1.1. devm_iio_device_alloc函数

devm_iio_device_alloc函数使用devres机制(设备资源管理)分配struct iio_dev结构体,并为驱动私有数据分配指定大小的内存,避免手动释放内存,减少资源泄漏风险。

函数原型:

1
struct iio_dev *devm_iio_device_alloc(struct device *dev, unsigned int sizeof_priv);

参数说明:

  • dev:指向IIO设备关联的父设备结构体,如I2C客户端的dev成员;

  • sizeof_priv:驱动私有数据的大小,单位为字节,若无需私有数据,可设为0。

返回值:

  • 成功:返回分配的struct iio_dev结构体指针;

  • 失败:返回NULL。

13.4.2. 注册IIO设备

13.4.2.1. iio_device_register函数

iio_device_register函数用于将初始化完成的IIO设备注册到IIO核心层,核心层会自动创建对应的sysfs节点,完成设备的初始化,使设备可以被用户层访问。

函数原型:

1
int iio_device_register(struct iio_dev *indio_dev);

参数说明:

  • indio_dev:指向已初始化完成的struct iio_dev结构体指针。

返回值:

  • 成功:返回0;

  • 失败:返回负数错误码,如-EINVAL:参数无效;-ENODEV:设备注册失败。

13.4.3. devres机制注册IIO设备

13.4.3.1. devm_iio_device_register函数

devm_iio_device_register函数与iio_device_register功能一致,用于注册IIO设备,但使用devres机制,当设备被移除时,内核会自动注销IIO设备,无需手动调用iio_device_unregister。

函数原型:

1
int devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev);

参数说明:

  • dev:指向IIO设备关联的父设备结构体;

  • indio_dev:指向已初始化完成的struct iio_dev结构体指针。

返回值:

  • 成功:返回0;

  • 失败:返回负数错误码,如-EINVAL:参数无效;-ENODEV:设备注册失败。

13.4.4. 注销IIO设备

13.4.4.1. iio_device_unregister函数

iio_device_unregister函数用于将IIO设备从IIO核心层注销,删除对应的sysfs节点,释放设备占用的资源。若使用devm_iio_device_register注册设备,无需手动调用该函数。

函数原型:

1
void iio_device_unregister(struct iio_dev *indio_dev);

参数说明:

  • indio_dev:指向需要注销的struct iio_dev结构体指针。

13.4.5. 获取IIO设备私有数据

13.4.5.1. iio_priv函数

iio_priv函数用于获取IIO设备结构体(struct iio_dev)关联的驱动私有数据。通常会在分配IIO设备时(如调用devm_iio_device_alloc)指定私有数据大小,该函数可快速获取这部分私有数据,避免手动计算内存偏移,简化代码编写。

函数原型:

1
void *iio_priv(struct iio_dev *indio_dev);

参数说明:

  • indio_dev:指向已分配的struct iio_dev结构体指针,该结构体需在分配时指定了私有数据大小。

返回值:

  • 成功:返回IIO设备关联的私有数据指针;

  • 失败:若indio_dev为NULL或未分配私有数据,返回NULL。

13.5. IO子系统实验

本实验基于“IIO子系统 + I2C子系统 + Regmap API”的架构实现对MPU6050数据采集, 实验整体逻辑为“设备树配置->驱动加载->IIO节点生成->应用程序读取->数据转换”。

本章的示例代码目录为: linux_driver/31_iio_subsystem

13.5.1. 关键参数与公式说明

本实验配置的MPU6050参数和I2C通信速率如下:

  • 加速度计量程:±2G,ADC精度为16位(有符号),灵敏度=量程 ADC精度(16位有符号数最大值是32768)=2G/32768 = 0.000061035 g/LSB。

  • 陀螺仪量程:±2000°/s,ADC精度为16位(有符号),灵敏度= 量程 / ADC精度(16位有符号数最大值是32768)=2000°/s/32768 = 0.061035156 °/s/LSB。

  • 温度传感器:灵敏度=340 LSB/℃ = 1 / 340 ℃/LSB = 0.002941176 ℃/LSB,零点偏移为36.53℃,原始数据需经过偏移校正得到实际温度。

  • I2C通信速率:100KHz,MPU6050支持I2C通信速率为100KHz~400KHz。

数据转换公式如下:

  • 加速度(m/s²)= 原始数据(raw) x 加速度缩放系数(灵敏度) x 标准重力加速度(9.8m/s²)

  • 温度(℃)= 原始数据(raw) x 温度缩放系数(灵敏度) + 零点偏移(36.53℃)

  • 陀螺仪(°/s)= 原始数据(raw) x 陀螺仪缩放系数(灵敏度)

13.5.2. 设备树插件详解

本实验设备树插件(lubancat-iio-mpu6050-overlay.dts)和I2C子系统实验的基本无异,仅修改了节点名称和compatible属性,完整代码如下:

设备树插件(位于linux_driver/31_iio_subsystem/lubancat-iio-mpu6050-overlay.dts)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/dts-v1/;
/plugin/;

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>

/ {
    fragment@0 {
        target = <&i2c3>;

        __overlay__ {
            clock-frequency = <100000>;
            pinctrl-names = "default";
            pinctrl-0 = <&i2c3m1_xfer>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            iio_mpu6050@68 {
                compatible = "fire,iio_mpu6050";
                reg = <0x68>;
                status = "okay";
            };
        };
    };
};

关键配置说明:

  • target = &i2c3:指定要修改的I2C适配器为I2C3,需与实际要使用的硬件接口一致;

  • clock-frequency = <100000>:配置I2C通信速率为100KHz,MPU6050支持标准模式(100KHz)和快速模式(400KHz);

  • pinctrl-0 = <&i2c3m1_xfer>:此处使用的具体I2C引脚为i2c3m1;

  • iio_mpu6050@68:从设备节点名称,@后的0x68是MPU6050的7位I2C地址,必须与传感器实际地址一致(默认0x68);

  • compatible = “fire,iio_mpu6050”:驱动匹配的核心标识,需与I2C驱动中of_match_table的属性完全一致,否则驱动无法匹配设备;

  • reg = <0x68>:明确MPU6050的I2C从设备地址,内核通过该属性识别I2C总线上的具体设备。

注意

以上设备树插件是以I2C3_M1为例,需根据板卡实际接口进行修改!

如果不清楚自己板卡有哪些可用的I2C接口,可在板卡执行以下命令确认:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#查看配置文件可用i2c插件
cat /boot/uEnv/uEnv.txt | grep i2c

#以LubanCat2-V2板卡为例,信息打印如下
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m0-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m1-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c5-m0-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m0-rtc-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m1-rtc-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c5-m0-rtc-overlay.dtbo

从可用的设备树插件可以确认,LubanCat2-V2支持i2c3-m0、i2c3-m1、i2c5-m0,从内核源码中找到对应的设备树插件源码如下,以i2c3-m1为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#查看i2c3-m1设备树插件源码内容
cat kernel/arch/arm64/boot/dts/rockchip/overlay/rk356x-lubancat-i2c3-m1-overlay.dts

#信息打印如下
/dts-v1/;
/plugin/;

/ {
        compatible = "rockchip,rk3568";

        fragment@0 {
                target = <&i2c3>;

                __overlay__ {
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <&i2c3m1_xfer>;
                };
        };
};

可知,如果LubanCat2-V2使用i2c3-m1接口,lubancat-i2c-mpu6050-overlay.dts配置的target和pinctrl-0就是 target = <&i2c3>;pinctrl-0 = <&i2c3m1_xfer>;

结合板卡配套的快速使用手册的40pin引脚对照图章节,可确认i2c3-m1接口对应的物理引脚,如下图:

../_images/subsystem_iio_subsystem_0.jpg

13.5.3. 驱动代码详解

驱动基于I2C子系统和IIO子系统开发,核心功能是初始化MPU6050硬件、注册IIO设备、实现原始数据读取和缩放系数提供,通过regmap机制简化寄存器操作。

核心定义与数据结构

核心定义与数据结构(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* 驱动私有数据结构体 */
struct mpu6050_data {
    struct i2c_client *client;   /* 指向I2C从设备客户端,存储设备信息 */
    struct regmap *regmap;       /* regmap映射句柄,简化寄存器读写 */
};

/* regmap配置结构体 */
static const struct regmap_config mpu6050_regmap_config = {
    .reg_bits = 8,               /* 寄存器地址位宽:8位 */
    .val_bits = 8,               /* 寄存器值位宽:8位 */
    .can_multi_write = true,     /* 支持多字节批量写入 */
};
  • struct i2c_client *client:I2C子系统分配的核心设备句柄,存储I2C设备地址、总线编号、设备树匹配信息等硬件关键参数;

  • struct regmap *regmap:regmap子系统抽象句柄,封装I2C底层读写细节,屏蔽硬件总线差异,提供统一的寄存器操作接口;

  • regmap_config配置参数:.reg_bits = 8 对应芯片8位寄存器地址空间,.val_bits = 8 对应芯片单字节(8位)寄存器数据位宽;.can_multi_write = true 启用多寄存器批量写入功能,适配MPU6050的I2C连续写时序,有效提升驱动执行效率;

IIO通道配置

通过IIO通道描述数组(mpu6050_channels)定义7个通道(3轴加速度、1路温度、3轴陀螺仪),每个通道指定类型、轴方向、索引和sysfs节点属性:

IIO通道配置(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* IIO通道索引枚举 */
enum mpu6050_channel_idx {
    CHAN_ACCEL_X,                /* 加速度X轴通道索引 */
    CHAN_ACCEL_Y,                /* 加速度Y轴通道索引 */
    CHAN_ACCEL_Z,                /* 加速度Z轴通道索引 */
    CHAN_TEMP,                   /* 温度通道索引 */
    CHAN_ANGL_VEL_X,             /* 陀螺仪X轴通道索引 */
    CHAN_ANGL_VEL_Y,             /* 陀螺仪Y轴通道索引 */
    CHAN_ANGL_VEL_Z,             /* 陀螺仪Z轴通道索引 */
};

/* IIO通道描述数组:定义每个传感器通道的类型、属性、sysfs节点 */
static const struct iio_chan_spec mpu6050_channels[] = {
    /* 加速度计X轴通道配置 */
    {
        .type = IIO_ACCEL,                    /* 通道类型:加速度计 */
        .modified = 1,                        /* 启用轴方向修饰符 */
        .channel2 = IIO_MOD_X,                /* 轴方向:X轴 */
        .address = CHAN_ACCEL_X,              /* 通道索引:对应枚举值 */
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),    /* 生成sysfs节点:原始值(raw) + 缩放系数(scale) */
    },
    /* 加速度计Y轴通道配置 */
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_Y,
        .address = CHAN_ACCEL_Y,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
    /* 加速度计Z轴通道配置 */
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_Z,
        .address = CHAN_ACCEL_Z,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
    /* 温度传感器通道配置 */
    {
        .type = IIO_TEMP,                     /* 通道类型:温度 */
        .address = CHAN_TEMP,                 /* 通道索引 */
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
    /* 陀螺仪X轴通道配置 */
    {
        .type = IIO_ANGL_VEL,                 /* 通道类型:陀螺仪 */
        .modified = 1,
        .channel2 = IIO_MOD_X,
        .address = CHAN_ANGL_VEL_X,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
    /* 陀螺仪Y轴通道配置 */
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Y,
        .address = CHAN_ANGL_VEL_Y,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
    /* 陀螺仪Z轴通道配置 */
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Z,
        .address = CHAN_ANGL_VEL_Z,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    },
};
  • mpu6050_channel_idx 为MPU6050 7个通道分配固定编号,区分加速度/陀螺仪/温度不同通道,用于iio_chan_spec.address绑定,在read_raw回调中匹配传感器寄存器;

  • struct iio_chan_spec是IIO通道描述结构体,内核通过该结构体自动生成/sys/bus/iio/devices/下的系统文件节点,实现应用层与传感器的数据交互。

    • .type:传感器类型,需要从iio_chan_type中选择。

    • .modified = 1 :启用通道修饰符。

    • .channel2:通道修饰符,用于带轴向的传感器(加速度/陀螺仪),标识X/Y/Z三维方向,定义需要从iio_modifier中选择;温度传感器无方向属性,省略该配置。

    • .info_mask_separate:配置通道开放的属性,RAW生成传感器原始数据节点,SCALE生成灵敏度缩放系数节点,两个节点配合完成原始值到物理单位的转换。

    • sysfs文件名固定拼接规则:固定前缀 + 传感器类型 + 轴方向 + 属性后缀,如加速度计X轴:in_ + ACCEL + X + RAW/SCALE -> in_accel_x_raw/in_accel_x_scale。

probe函数

probe函数(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    /* IIO设备结构体指针 */
    struct iio_dev *indio_dev;
    /* 驱动私有数据指针 */
    struct mpu6050_data *data;
    /* 函数返回值 */
    int ret;

    /* 动态分配IIO设备内存 + 私有数据内存 */
    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
    if (!indio_dev)
        return -ENOMEM;

    /* 获取私有数据结构体地址 */
    data = iio_priv(indio_dev);

    /* 绑定I2C客户端 */
    data->client = client;

    /* 初始化I2C regmap映射 */
    data->regmap = devm_regmap_init_i2c(client, &mpu6050_regmap_config);
    if (IS_ERR(data->regmap)) {
        dev_err(&client->dev, "regmap init failed\n");
        return PTR_ERR(data->regmap);
    }

    /* 调用MPU6050硬件初始化函数 */
    ret = mpu6050_init(data);
    if (ret)
        return ret;

    /* 配置IIO设备参数 */
    indio_dev->info = &mpu6050_iio_info;       /* 绑定IIO操作函数集 */
    indio_dev->channels = mpu6050_channels;    /* 绑定通道描述数组 */
    indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels); /* 通道数量:自动计算数组大小 */
    indio_dev->name = "mpu6050";                /* IIO设备名称 */
    indio_dev->modes = INDIO_DIRECT_MODE;       /* 直接访问模式,无缓冲 */

    /* 注册IIO设备到内核,自动生成sysfs节点 */
    ret = devm_iio_device_register(&client->dev, indio_dev);
    if (ret) {
        dev_err(&client->dev, "IIO register failed\n");
        return ret;
    }

    dev_info(&client->dev, "MPU6050 IIO driver probe success\n");
    return 0;
}
  • 第11行:devm_iio_device_alloc:使用devres机制(设备资源管理)分配struct iio_dev结构体,并为驱动私有数据分配指定大小的内存,避免手动释放内存。

  • 第16行:iio_priv:从已分配的IIO设备结构体中,获取专属的mpu6050_data私有数据指针,用于存储I2C、regmap等硬件相关句柄。

  • 第22行:devm_regmap_init_i2c:基于I2C客户端初始化regmap寄存器映射,封装底层I2C读写细节,通过预定义的regmap配置实现寄存器统一操作。

  • 第29行:mpu6050_init:MPU6050硬件初始化入口函数,完成芯片唤醒、量程配置、滤波设置等寄存器配置,让传感器进入工作模式。

  • 第34~38行:IIO设备参数配置:填充IIO设备核心属性,绑定驱动操作函数集、传感器通道配置、通道数量、设备名称和工作模式。

  • 第41行:devm_iio_device_register:将配置完成的IIO设备注册到Linux内核,内核自动识别并生成/sys/bus/iio/devices/下的sysfs文件节点,完成驱动的最终注册,应用层可通过sysfs访问传感器数据。

MPU6050硬件初始化函数

mpu6050_init函数(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static int mpu6050_init(struct mpu6050_data *data)
{
    /* 函数返回值 */
    int ret;
    /* 存储寄存器读取值 */
    unsigned int val;

    /* 读取WHO_AM_I寄存器,校验芯片是否正常 */
    ret = regmap_read(data->regmap, WHO_AM_I, &val);
    if (ret || val != 0x68) {
        dev_err(&data->client->dev, "WHO_AM_I error: 0x%x\n", val);
        return -ENODEV;
    }

    /* 配置电源管理寄存器:唤醒MPU6050,使用内部8Mhz时钟源 */
    ret = regmap_write(data->regmap, PWR_MGMT_1, 0x00);
    if (ret < 0) goto init_fail;

    /* 配置采样率分频寄存器:陀螺仪采样率,1KHz */
    ret = regmap_write(data->regmap, SMPLRT_DIV, 0x07);
    if (ret < 0) goto init_fail;

    /* 配置通用配置寄存器:低通滤波器的设置,截止频率是1K,带宽是5K */
    ret = regmap_write(data->regmap, CONFIG, 0x06);
    if (ret < 0) goto init_fail;

    /* 配置加速度计配置寄存器:配置加速度传感器工作在2G模式,不自检 */
    ret = regmap_write(data->regmap, ACCEL_CONFIG, 0x00);
    if (ret < 0) goto init_fail;

    /* 配置陀螺仪配置寄存器:陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s) */
    ret = regmap_write(data->regmap, GYRO_CONFIG, 0x18);
    if (ret < 0) goto init_fail;

    dev_info(&data->client->dev, "MPU6050 init success\n");
    return 0;

init_fail:
    /* 初始化错误 */
    printk(KERN_ERR "mpu6050 init error \n");
    return ret;
}

该部分配置流程和I2C子系统实验一致,只是改用regmap_read/regmap_write API操作传感器寄存器,初始化成功后,传感器进入正常工作状态,可输出加速度、温度、陀螺仪的原始数据。

IIO原始数据读取回调函数

mpu6050_read_raw函数是IIO子系统与MPU6050硬件数据交互的唯一回调函数,当应用程序读取/sys/bus/iio/devices/xxx/下的in_accel_x_raw/in_accel_x_scale文件时,内核会自动调用该函数。

IIO原始数据读取回调函数(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
* 函数:IIO 原始数据读取回调函数
* indio_dev: IIO设备结构体
* chan: 当前读取的通道描述符
* val: 存储读取的整数值
* val2: 存储小数/缩放值
* mask: 读取类型(RAW/scale)
*/
static int mpu6050_read_raw(struct iio_dev *indio_dev,
                        struct iio_chan_spec const *chan,
                        int *val, int *val2, long mask)
{
    /* 获取驱动私有数据结构体 */
    struct mpu6050_data *data = iio_priv(indio_dev);
    /* 存储16位原始传感器数据 */
    u16 raw_data;
    /* 存储要读取的寄存器地址 */
    u8 reg;

    /* 判断读取类型:原始数据/缩放系数 */
    switch (mask) {
        /* 读取传感器原始数据(RAW) */
        case IIO_CHAN_INFO_RAW:
            /* 根据通道索引选择对应寄存器地址 */
            switch (chan->address) {
                case CHAN_ACCEL_X: reg = ACCEL_XOUT_H; break;       /* 加速度计X轴数据高8位地址 */
                case CHAN_ACCEL_Y: reg = ACCEL_YOUT_H; break;
                case CHAN_ACCEL_Z: reg = ACCEL_ZOUT_H; break;
                case CHAN_TEMP:    reg = TEMP_OUT_H; break;         /* 温度传感器数据高8位地址 */
                case CHAN_ANGL_VEL_X:  reg = GYRO_XOUT_H; break;    /* 陀螺仪X轴数据高8位地址 */
                case CHAN_ANGL_VEL_Y:  reg = GYRO_YOUT_H; break;
                case CHAN_ANGL_VEL_Z:  reg = GYRO_ZOUT_H; break;

                /* 无效通道,返回参数错误 */
                default: return -EINVAL;
            }

            /* 连续读取2字节寄存器数据,高8位+低8位 */
            regmap_bulk_read(data->regmap, reg, &raw_data, 2);

            /* be16_to_cpu将把传感器的大端16位数转换成CPU能识别的小端数,(s16)强转为有符号数,适配传感器正负值 */
            *val = (s16)be16_to_cpu(raw_data);

            /* 返回值类型:整数值,没有小数 */
            return IIO_VAL_INT;

        /* 读取传感器缩放系数(scale) */
        case IIO_CHAN_INFO_SCALE:
            /* 根据通道类型返回对应缩放系数 */
            switch (chan->type) {
                case IIO_ACCEL:
                    *val = 0;    *val2 = 61035;    /* 加速度灵敏度 = 量程 / ADC精度(16位有符号数最大值是32768) = 2g / 32768 = 0.000061035 g/LSB */
                    break;
                case IIO_ANGL_VEL:
                    *val = 0;    *val2 = 61035156; /* 陀螺仪灵敏度 = 量程 / ADC精度(16位有符号数最大值是32768) = 2000°/s / 32768 = 0.061035156 °/s/LSB */
                    break;
                case IIO_TEMP:
                    *val = 0;    *val2 = 2941176;  /* 温度灵敏度 = 340 LSB/℃ = 1 / 340 ℃/LSB = 0.002941176 ℃/LSB */
                    break;

                /* 无效类型,返回参数错误 */
                default: return -EINVAL;
            }

            /* 返回值类型:整数+纳位小数,即val + val2 / 1000000000 */
            return IIO_VAL_INT_PLUS_NANO;

        default: return -EINVAL;    /* 无效命令,返回参数错误 */
    }
}

/* IIO操作函数集:绑定read_raw回调,供IIO子系统调用 */
static const struct iio_info mpu6050_iio_info = {
    .read_raw = mpu6050_read_raw,
};
  • 第21行:switch (mask):根据传入的参数类型,区分处理原始数据读取(_raw)和缩放系数读取(_scale)两种请求。

  • 第23行:case IIO_CHAN_INFO_RAW:匹配应用层读取传感器原始值的请求,对应sysfs中所有以“_raw”结尾的文件。

  • 第25行:switch (chan->address):通过通道描述符中的索引编号(mpu6050_channel_idx枚举),匹配当前需要读取的传感器通道。

  • 第26行:将软件通道索引与MPU6050硬件寄存器地址一一绑定,实现软件逻辑与硬件寄存器的对应。

  • 第39行:regmap_bulk_read:调用regmap批量读取接口,连续读取2字节寄存器(高8位 + 低8位),直接获取传感器16位原始数据,无需分次读取。

  • 第42行:be16_to_cpu:MPU6050输出大端字节序数据,ARM架构CPU默认使用小端字节序,该函数完成字节序强制转换,保证数据解析正确;(s16)强转为有符号数,适配传感器正负值。

  • 第45行:return IIO_VAL_INT:告知IIO内核:返回的原始数据是纯整数格式,内核直接读取val的值。

  • 第48行:case IIO_CHAN_INFO_SCALE:匹配应用层读取传感器灵敏度系数的请求,对应sysfs中所有以“_scale”结尾的文件。

  • 第50/51/52行:缩放系数赋值:按照传感器量程、ADC 精度计算的标准灵敏度,val存储整数部分,val2存储纳单位(10⁻⁹)小数部分。

  • 第66行:return IIO_VAL_INT_PLUS_NANO:告知IIO内核:返回值为整数 + 纳单位小数组合格式,内核自动计算最终物理系数。

  • 第73行:.read_raw = mpu6050_read_raw:将自定义的读取回调函数绑定到IIO标准操作函数集。

驱动注册

驱动注册(位于linux_driver/31_iio_subsystem/iio_mpu6050.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* 定义设备树匹配表 */
static const struct of_device_id mpu6050_of_match_table[] = {
    { .compatible = "fire,iio_mpu6050" },
    { }
};

/* 声明设备树匹配表,供内核识别 */
MODULE_DEVICE_TABLE(of, mpu6050_of_match_table);

/* 定义i2c总线设备结构体 */
static struct i2c_driver mpu6050_driver = {
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
    .driver = {
        .name = "iio-mpu6050",
        .of_match_table = mpu6050_of_match_table,
    },
};

/* 注册/注销I2C驱动,简化模块入口/出口函数 */
module_i2c_driver(mpu6050_driver);

module_i2c_driver替代传统的module_init和module_exit函数,无需手动编写驱动注册/注销逻辑。

13.5.4. 应用代码详解

应用程序的功能是读取IIO sysfs节点的原始数据和缩放系数,完成数据转换并循环打印,完整代码如下:

iio_mpu6050_app.c(位于linux_driver/31_iio_subsystem/iio_mpu6050_app.c)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>

/*
*  ======================== 计算说明 ========================
* 1. 加速度(m/s²) = 原始值 * 加速度scale * 重力加速度(9.8)
* 2. 温度(℃)     = 原始值 * 温度scale + 36.53
* 3. 陀螺仪(°/s)  = 原始值 * 陀螺仪scale
*
*/

/* 物理常量 */
#define TEMP_OFFSET 36.53f     // 温度传感器零点偏移:36.53°C
#define TEMP_DIV    340.0f     // 温度传感器灵敏度:340 LSB/°C
#define GRAVITY     9.8f       // 标准重力加速度

/* 全局标志,用于Ctrl+C退出循环读取 */
static int exit_flag = 0;

/**
* @brief  Ctrl+C信号处理函数
* @param  sig: 信号编号
*/
static void sigint_handler(int sig)
{
    if (sig == SIGINT)
    {
        exit_flag = 1;
        printf("\n收到退出信号,即将停止读取...\n");
    }
}

/**
* @brief  读取IIO原始整型数据(raw节点)
* @param  iio_path: IIO设备路径
* @param  name: 节点文件名
* @return 成功返回整型数据,失败返回INT_MIN
*/
static int read_iio_raw(const char *iio_path, const char *name)
{
    char file_path[PATH_MAX];  // 存储拼接后的完整文件路径
    char buf[16] = {0};        // 存储读取到的字符串数据
    int fd, ret;               // 文件描述符、函数返回值

    // 拼接完整的 sysfs 文件路径
    snprintf(file_path, sizeof(file_path), "%s/%s", iio_path, name);
    fd = open(file_path, O_RDONLY);
    if (fd < 0) return INT_MIN;

    // 以只读方式打开节点文件
    ret = read(fd, buf, sizeof(buf)-1);
    close(fd);
    if (ret < 0) return INT_MIN;    // 读取数据失败,返回错误标识

    return atoi(buf);   // 将字符串转为整型,返回原始数据
}

/**
* @brief  读取IIO浮点缩放系数(scale节点)
* @param  iio_path: IIO设备路径
* @param  name: scale文件名
* @return 成功返回浮点系数,失败返回-1.0f
*/
static float read_iio_scale(const char *iio_path, const char *name)
{
    char file_path[PATH_MAX];   // 存储拼接后的完整文件路径
    char buf[32] = {0};         // 存储读取到的字符串数据
    int fd, ret;                // 文件描述符、函数返回值

    // 拼接完整的 sysfs 文件路径
    snprintf(file_path, sizeof(file_path), "%s/%s", iio_path, name);

    // 以只读方式打开节点文件
    fd = open(file_path, O_RDONLY);
    if (fd < 0) return -1.0f;   // 失败返回-1.0f

    ret = read(fd, buf, sizeof(buf)-1);
    close(fd);
    if (ret < 0) return -1.0f;  // 失败返回-1.0f

    return atof(buf);   // 将字符串转为整型,返回原始数据
}

int main(int argc, char *argv[])
{
    // 传感器原始数据
    int accel_x, accel_y, accel_z;
    int temp_raw;
    int gyro_x, gyro_y, gyro_z;

    // 读取的缩放系数
    float accel_scale;
    float temp_scale;
    float gyro_scale;

    // 最终物理量
    float ax_ms2, ay_ms2, az_ms2;
    float temp_c;
    float gx_dps, gy_dps, gz_dps;

    // 注册信号
    signal(SIGINT, sigint_handler);

    // 参数校验
    if (argc != 2) {
        printf("使用方法: ./iio_mpu6050_app /sys/bus/iio/devices/iio:device1\n");
        return -1;
    }

    // 启动提示
    printf("===== MPU6050 IIO 数据读取程序 =====\n");
    printf("设备路径: %s\n", argv[1]);
    printf("按 Ctrl+C 退出程序\n");
    printf("===================================================\n");

    // 打印表头
    printf("AX(m/s²)   AY(m/s²)   AZ(m/s²)   TEMP(°C)   GX(°/s)    GY(°/s)    GZ(°/s)\n");
    printf("---------------------------------------------------------------------------\n");

    // 循环读取
    while (!exit_flag)
    {
        // 1. 读取加速度原始值
        accel_x = read_iio_raw(argv[1], "in_accel_x_raw");
        accel_y = read_iio_raw(argv[1], "in_accel_y_raw");
        accel_z = read_iio_raw(argv[1], "in_accel_z_raw");

        // 2. 读取温度原始值
        temp_raw = read_iio_raw(argv[1], "in_temp_raw");

        // 3. 读取陀螺仪原始值
        gyro_x = read_iio_raw(argv[1], "in_anglvel_x_raw");
        gyro_y = read_iio_raw(argv[1], "in_anglvel_y_raw");
        gyro_z = read_iio_raw(argv[1], "in_anglvel_z_raw");

        // 4. 读取scale(加速度/陀螺仪)
        accel_scale = read_iio_scale(argv[1], "in_accel_x_scale");
        temp_scale = read_iio_scale(argv[1], "in_temp_scale");
        gyro_scale  = read_iio_scale(argv[1], "in_anglvel_x_scale");

        // 数据校验
        if (accel_x == INT_MIN || accel_y == INT_MIN || accel_z == INT_MIN ||
            temp_raw == INT_MIN || gyro_x == INT_MIN || gyro_y == INT_MIN || gyro_z == INT_MIN ||
            accel_scale <= 0.0f || gyro_scale <= 0.0f)
        {
            sleep(1);
            continue;
        }

        // ===================== 原始数据转物理值 =====================
        // 加速度:通过scale计算
        ax_ms2 = (float)accel_x * accel_scale * GRAVITY;
        ay_ms2 = (float)accel_y * accel_scale * GRAVITY;
        az_ms2 = (float)accel_z * accel_scale * GRAVITY;

        // 温度:MPU6050硬件标准公式
        temp_c = (float)temp_raw * temp_scale + TEMP_OFFSET;

        // 陀螺仪:通过scale计算
        gx_dps = (float)gyro_x * gyro_scale;
        gy_dps = (float)gyro_y * gyro_scale;
        gz_dps = (float)gyro_z * gyro_scale;

        // 打印结果
        printf("%-10.2f %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f\n",
            ax_ms2, ay_ms2, az_ms2,
            temp_c,
            gx_dps, gy_dps, gz_dps);

        sleep(1);
    }

    printf("程序已退出!\n");
    return 0;
}
  • read_iio_raw函数:拼接sysfs节点路径,以只读方式打开raw节点,读取字符串格式的原始数据,转换为整型后返回。

  • read_iio_scale函数:拼接sysfs节点路径,以只读方式打开scale节点,读取字符串格式的缩放系数,转换为浮点型后返回。

  • 主函数循环读取各通道的raw原始数据和scale缩放系数,判断读取的数据是否有效,若无效则跳过本次循环,按照预设公式,将raw数据转换为实际物理量,循环打印转换后的物理量数据,每秒打印一次,直至收到退出信号。

13.5.5. Makefile说明

本节实验使用的Makefile如下所示,编写该Makefile时,只需要根据实际情况修改变量KERNEL_DIR、obj-m和test_app即可。

Makefile(位于linux_driver/31_iio_subsystem/Makefile)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#指定内核路径,可以是相对路径或绝对路径
KERNEL_DIR=../../kernel/
#KERNEL_DIR=/home/guest/LubanCat_Linux_rk356x_SDK/kernel/

#指定目标架构为arm64
ARCH=arm64

#指定交叉编译工具链的前缀
CROSS_COMPILE=aarch64-linux-gnu-

#导出为环境变量
export  ARCH  CROSS_COMPILE

#指定要编译的内核模块目标文件
obj-m := iio_mpu6050.o

test_app = iio_mpu6050_app

#all :默认目标,执行时会编译驱动模块
#$(MAKE) :调用make工具
#-C $(KERNEL_DIR) :指定的内核源码目录
#M=$(CURDIR) :模块的源码位于当前目录
#modules :编译模块
all:
    $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
    $(CROSS_COMPILE)gcc -o $(test_app) $(test_app).c

.PHONE:clean

#清理编译生成的文件
clean:
    $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
    rm $(test_app)

13.5.6. 编译设备树和驱动

13.5.6.1. 编译设备树

修改内核目录/arch/arm64/boot/dts/rockchip/overlays下的Makefile文件,添加我们编辑好的设备树插件,并把设备树插件文件放在和Makefile文件同级目录下,以进行设备树插件的编译。

../_images/subsystem_iio_subsystem_1.jpg

然后在内核源码顶层目录执行以下命令编译设备树插件:

1
2
3
#这里以rk356x系列4.19.232内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs

提示

其余系列板卡参考 使用内核的构建脚本编译设备树插件 章节进行编译。

13.5.6.2. 加载设备树

编译出来的设备树插件位于 内核源码/arch/arm64/boot/dts/rockchip/overlay/lubancat-iio-mpu6050-overlay.dtbo, 将设备树插件先传到板卡,再拷贝到板卡的 /boot/dtb/overlay/ 目录下。

1
2
3
4
#先传输到板卡

#再拷贝到板卡的/boot/dtb/overlay/目录下
sudo cp -f lubancat-iio-mpu6050-overlay.dtbo /boot/dtb/overlay/

然后在 /boot/uEnv/uEnv.txt 按照格式添加我们的设备树插件,需要在#overlay_start和#overlay_end之间添加,然后重启开发板,那么系统就会加载我们编译的设备树插件。

../_images/subsystem_iio_subsystem_2.jpg

13.5.6.3. 编译驱动

在实验目录下输入 make 即可编译驱动和应用程序,编译得到内核模块iio_mpu6050.ko和应用程序iio_mpu6050_app。

13.5.7. 模块接线说明

通过杜邦线连接MPU6050模块和板卡,接线如下:

1
2
3
4
5
6
7
MPU6050模块         板卡
   VCC   --------   3.3V
   GND   --------   GND
   SCL   --------   SCL
   SDA   --------   SDA

// 其余引脚不接

13.5.8. 程序运行结果

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

13.5.8.1. 实验操作

使用以下命令加载驱动和运行测试程序,加载驱动前需确保MPU6050模块已经连接到板卡:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#先确认设备地址存在,使用i2cdetect命令,其中3表示是i2c3,需要根据实际使用的i2c修改
sudo i2cdetect -a -y 3

#信息打印如下,可以看到68地址
    0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

#加载驱动
sudo insmod iio_mpu6050.ko

#信息打印如下
[  257.962564] iio-mpu6050 3-0068: MPU6050 init success
[  257.963151] iio-mpu6050 3-0068: MPU6050 IIO driver probe success

#查看iio sysfs节点
ls /sys/bus/iio/devices/

#信息打印如下
iio:device0  iio:device1  iio_sysfs_trigger

#查看设备名称
cat /sys/bus/iio/devices/iio:device*/name

#信息打印如下
fe720000.saradc
mpu6050

可以看到当前系统有俩个iio:device,分别对应iio:device0、iio:device1,其中iio:device1就是我们注册生成的mpu6050对应的sysfs节点。

可以通过cat命令直接读取数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#进入mpu6050对应的sysfs节点目录
cd /sys/bus/iio/devices/iio:device1

#查看目录文件
ls -l

#信息打印如下
total 0
-r--r--r-- 1 root root 4096 Jun 18 04:52 dev
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_x_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_x_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_y_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_y_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_z_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_accel_z_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_x_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_x_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_y_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_y_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_z_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_anglvel_z_scale
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_temp_raw
-rw-r--r-- 1 root root 4096 Jun 18 04:52 in_temp_scale
-r--r--r-- 1 root root 4096 Jun 18 04:52 name
drwxr-xr-x 2 root root    0 Jun 18 04:52 power
lrwxrwxrwx 1 root root    0 Jun 18 04:52 subsystem -> ../../bus/iio
-rw-r--r-- 1 root root 4096 Jun 18 04:51 uevent

#查看加速度X轴原始数据
cat in_accel_x_raw

#信息打印如下
-13656

#查看加速度X轴灵敏度系数
cat in_accel_x_scale

#信息打印如下
0.000061035

#查看温度原始数据
cat in_temp_raw

#信息打印如下
-3052

#查看温度灵敏度系数
cat in_temp_scale

#信息打印如下
0.002941176

#查看陀螺仪X轴原始数据
cat in_anglvel_x_raw

#信息打印如下
-48

#查看陀螺仪X轴灵敏度系数
cat in_anglvel_x_scale

#信息打印如下
0.061035156

可以看到原始数据是带正负号的数据,灵敏度系数和驱动设置的值一致。

使用应用程序进行测试,读取原始数据并转换为物理值进行打印,等待打印几次静止状态数据,然后晃动模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#运行应用程序,指定设备sysfs路径
sudo ./iio_mpu6050_app /sys/bus/iio/devices/iio:device1

#信息打印如下
===== MPU6050 IIO 数据读取程序 =====
设备路径: /sys/bus/iio/devices/iio:device1
按 Ctrl+C 退出程序
===================================================
AX(m/s²)   AY(m/s²)   AZ(m/s²)   TEMP(°C)   GX(°/s)    GY(°/s)    GZ(°/s)
---------------------------------------------------------------------------
-8.18      1.08       5.30       27.35      -3.05      0.61       -0.67     //模块静止
-8.18      1.08       5.31       27.35      -3.11      1.04       -0.43
-8.17      1.08       5.32       27.34      -2.93      0.79       -0.61
-8.18      1.07       5.33       27.34      -2.99      0.85       -0.67
-8.07      1.48       5.28       27.32      0.98       -22.95     -45.35    //开始晃动模块
-7.93      4.44       4.42       27.34      -8.06      -2.75      -8.06
-7.86      3.62       4.74       27.36      -32.71     -1.22      -69.89
-8.20      2.17       5.11       27.39      -13.85     8.73       -6.47
-8.18      2.00       4.96       27.39      -3.60      0.98       -2.14

选取晃动前后的各1组数据进行解析:

1
2
3
4
5
AX(m/s²)   AY(m/s²)   AZ(m/s²)   TEMP(°C)   GX(°/s)    GY(°/s)    GZ(°/s)
----------------------------------------------------------------------------------------
-8.18      1.08       5.30       27.35      -3.05      0.61       -0.67     // 静止数据
...
-8.07      1.48       5.28       27.32      0.98       -22.95     -45.35     // 晃动数据
  1. 静止数据:

  • 合加速度计算:√((-8.18)^2+(1.08)^2+(5.30)^2) ≈ 9.81 m/s²,接近标准重力加速度 9.8m/s²。

  • 温度27℃左右,室温恒定,数值无波动。

  • 因为陀螺仪有零漂特性,3个轴角速度不会为0,实际数值稳定在±6 °/s以内,并无剧烈跳动。

  1. 晃动数据:

  • 合加速度计算:√((-8.07)^2+(1.48)^2+(5.28)^2) ≈ 9.75 m/s²,合加速度发生变化,响应运动状态,数据正常。

  • 温度27℃左右,室温恒定,数值无波动。

  • 陀螺仪3个轴角数值随晃动实时变化,响应旋转动作,数据正常。

13.5.9. 实验注意事项

  1. I2C地址正确性:MPU6050的7位I2C地址默认是0x68,设备树中reg属性需设置为0x68,若传感器AD0引脚接VCC,地址会变为0x69,需同步修改设备树和驱动;

  2. 硬件连接正确性:MPU6050的SDA、SCL引脚需正确连接到开发板的I2C对应的SDA、SCL引脚,同时需接入VCC(3.3V)和GND,避免接反电源损坏传感器;

  3. 数据解析:驱动需要使用be16_to_cpu将MPU6050输出大端字节序数据转换成ARM架构CPU默认使用的小端字节序;

  4. 缩放系数计算:scale缩放系数对应灵敏度,需要根据传感器实际配置进行计算得到。