正点原子imx6ull-mini-Linux驱动之Linux IIO 驱动实验

news2024/11/14 15:45:17

工业场合里面也有大量的模拟量和数字量之间的转换,也就是我们常说的 ADC 和 DAC。 而且随着手机、物联网、工业物联网和可穿戴设备的爆发,传感器的需求只持续增强。比如手 机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等,这些传感器本质上都是 ADC,大家注意查看这些传感器的手册,会发现他们内部都会有个 ADC,传感器对外提供 IIC 或者 SPI 接口,SOC 可以通过 IIC 或者 SPI 接口来获取到传感器内部的 ADC 数值,从而得到 想要测量的结果。Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统, 本章我们就来学习如何使用 IIO 子系统来编写 ADC 类传感器驱动。

1:IIO 子系统简介

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,大家不要看到“工业”两个字就觉得 IIO 是只用于工业领域的。大家一般在搜索 IIO 子系统的时候,会发现大多数讲的都是 ADC,这是 因为 IIO 就是为 ADC 类传感器准备的,当然了 DAC 也是可以的。大家常用的陀螺仪、加速度 计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的 模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、SPI 等传输给 SOC。 因此,当你使用的传感器本质是 ADC 或 DAC 器件的时候,可以优先考虑使用 IIO 驱动框 架。

1.1:iio_dev

1.1.1:iio_dev 结构体

IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备,此设备结构体定义在 include/linux/iio/iio.h 文件中,结构体内容如下(有省略):

474 struct iio_dev {
475 int id;
476
477 int modes;
478 int currentmode;
479 struct device dev;
480
481 struct iio_event_interface *event_interface;
482
483 struct iio_buffer *buffer;
484 struct list_head buffer_list;
485 int scan_bytes;
486 struct mutex mlock;
487
488 const unsigned long *available_scan_masks;
489 unsigned masklength;
490 const unsigned long *active_scan_mask;
491 bool scan_timestamp;
492 unsigned scan_index_timestamp;
493 struct iio_trigger *trig;
494 struct iio_poll_func *pollfunc;
495
496 struct iio_chan_spec const *channels;
497 int num_channels;
498
499 struct list_head channel_attr_list;
500 struct attribute_group chan_attr_group;
501 const char *name;
502 const struct iio_info *info;
503 struct mutex info_exist_lock;
504 const struct iio_buffer_setup_ops *setup_ops;
505 struct cdev chrdev;
......
515 };

我们来看一下 iio_dev 结构体中几个比较重要的成员变量: 第 477 行,modes 为设备支持的模式,可选择的模如表 75.1.1.1 所示:

 第 478 行,currentmode 为当前模式。

第 483 行,buffer 为缓冲区。

第 484 行,buffer_list 为当前匹配的缓冲区列表。

第 485 行,scan_bytes 为捕获到,并且提供给缓冲区的字节数。

第 488 行,available_scan_masks 为可选的扫描位掩码,使用触发缓冲区的时候可以通过设 置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。

第 490 行,active_scan_mask 为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才 能被发送到缓冲区。

第 491 行,scan_timestamp 为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。

第 493 行,trig 为 IIO 设备当前触发器,当使用缓冲模式的时候。

第 494 行,pollfunc 为一个函数,在接收到的触发器上运行。

第 496 行,channels 为 IIO 设备通道,为 iio_chan_spec 结构体类型,稍后会详细讲解 IIO 通道。

第 497 行,num_channels 为 IIO 设备的通道数。

第 501 行,name 为 IIO 设备名字。

第 502 行,info 为 iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编 写,非常重要!我们从用户空间读取 IIO 设备内部数据,最终调用的就是 iio_info 里面的函数。 稍后会详细讲解 iio_info 结构体。

第 504 行,setup_ops 为 iio_buffer_setup_ops 结构体类型,内容如下

427 struct iio_buffer_setup_ops {
428 int (*preenable)(struct iio_dev *); /* 缓冲区使能之前调用 */
429 int (*postenable)(struct iio_dev *); /* 缓冲区使能之后调用 */
430 int (*predisable)(struct iio_dev *); /* 缓冲区禁用之前调用 */
431 int (*postdisable)(struct iio_dev *); /* 缓冲区禁用之后调用 */
432 bool (*validate_scan_mask)(struct iio_dev *indio_dev,
433 const unsigned long *scan_mask); /* 检查扫描掩码是否有效 */
434 };

可以看出 iio_buffer_setup_ops 里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定的话就默认使用 iio_triggered_buffer_setup_ops。 继续回到示例代码 75.1.1.1 中第 505 行,chrdev 为字符设备,由 IIO 内核创建。

1.1.2:iio_dev 申请与释放

在使用之前要先申请 iio_dev,申请函数为 iio_device_alloc,函数原型如下:

struct iio_dev *iio_device_alloc(int sizeof_priv)

函数参数和返回值含义如下:

sizeof_priv:私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为 iio_dev 的私有数据,这样可以直接通过 iio_device_alloc 函数同时完成 iio_dev 和设备结构体变量的内 存申请。申请成功以后使用 iio_priv 函数来得到自定义的设备结构体变量首地址。

返回值:如果申请成功就返回 iio_dev 首地址,如果失败就返回 NULL。 一般 iio_device_alloc 和 iio_priv 之间的配合使用如下所示:

1 struct icm20608_dev *dev;
2 struct iio_dev *indio_dev;
3 
4 /* 1、申请 iio_dev 内存 */
5 indio_dev = iio_device_alloc(sizeof(*dev));
6 if (!indio_dev)
7 return -ENOMEM;
8 
9 /* 2、获取设备结构体变量地址 */
10 dev = iio_priv(indio_dev);

第 1 行,icm20608_dev 是自定义的设备结构体。

第 2 行,indio_dev 是 iio_dev 结构体变量指针。

第 5 行,使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 icm2060_dev 的内存。

第 10 行,使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定 义结构体变量首地址。 如果要释放 iio_dev,需要使用 iio_device_free 函数,函数原型如下:

void iio_device_free(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev:需要释放的 iio_dev。

返回值:无。

也 可 以 使 用 devm_iio_device_alloc 来 分 配 iio_dev , 这 样 就 不 需 要 我 们 手 动 调 用 iio_device_free 函数完成 iio_dev 的释放工作。

1.1.3:iio_dev 注册与注销

前面分配好 iio_dev 以后就要初始化各种成员变量,初始化完成以后就需要将 iio_dev 注册 到内核中,需要用到 iio_device_register 函数,函数原型如下:

int iio_device_register(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev:需要注册的 iio_dev。

返回值:0,成功;其他值,失败。

如果要注销 iio_dev 使用 iio_device_unregister 函数,函数原型如下:

void iio_device_unregister(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev:需要注销的 iio_dev。

返回值:0,成功;其他值,失败。

1.2:iio_info

iio_dev 有个成员变量:info,为 iio_info 结构体指针变量,这个是我们在编写 IIO 驱动的时 候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info 里面。iio_info 结构体定义在 include/linux/iio/iio.h 中,结构体定义如下(有省略):

352 struct iio_info {
353 struct module *driver_module;
354 struct attribute_group *event_attrs;
355 const struct attribute_group *attrs;
356
357 int (*read_raw)(struct iio_dev *indio_dev,
358 struct iio_chan_spec const *chan,
359 int *val,
360 int *val2,
361 long mask);
......
369
370 int (*write_raw)(struct iio_dev *indio_dev,
371 struct iio_chan_spec const *chan,
372 int val,
373 int val2,
374 long mask);
375
376 int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
377 struct iio_chan_spec const *chan,
378 long mask);
......
415 };

第 355 行,attrs 是通用的设备属性。

第 357 和 370 行,分别为 read_raw 和 write_raw 函数,这两个函数就是最终读写设备内部 数据的操作函数,需要程序编写人员去实现的。比如应用读取一个陀螺仪传感器的原始数据, 那么最终完成工作的就是 read_raw 函数,我们需要在 read_raw 函数里面实现对陀螺仪芯片的 读取操作。同理,write_raw 是应用程序向陀螺仪芯片写数据,一般用于配置芯片,比如量 程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下: indio_dev:需要读写的 IIO 设备。 chan:需要读取的通道。 val,val2:对于 read_raw 函数来说 val 和 val2 这两个就是应用程序从内核空间读取到数 据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。val 和 val2 共同组成具体值,val 是整数部分,val2 是小数部分。但是 val2 也是对具体的小数部分扩大 N 倍后的整数值,因为不能直接从内核向应用程序返回一个小 数值。比如现在有个值为 1.00236,那么 val 就是 1,vla2 理论上来讲是 0.00236,但是我们需要 对 0.00236 扩大 N 倍,使其变为整数,这里我们扩大 1000000 倍,那么 val2 就是 2360。因此 val=1,val2=2360。扩大的倍数我们不能随便设置,而是要使用 Linux 定义的倍数,Linux 内核 里面定义的数据扩大倍数,或者说数据组合形式如表 75.1.2.1 所示:

mask:掩码,用于指定我们读取的是什么数据,比如 ICM20608 这样的传感器,他既有原 始的测量数据,比如 X,Y,Z 轴的陀螺仪、加速度计等,也有测量范围值,或者分辨率。比如加 速度计测量范围设置为±16g,那么分辨率就是 32/65536≈0.000488,我们只有读出原始值以及 对应的分辨率(量程),才能计算出真实的重力加速度。此时就有两种数据值:传感器原始值、分 辨率。Linux 内核使用 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_SCALE 这两个宏来表示原 始值以及分辨率,这两个宏就是掩码。至于每个通道可以采用哪几种掩码,这个在我们初始化 通道的时候需要驱动编写人员设置好。掩码有很多种,稍后讲解 IIO 通道的时候详细讲解

第 376 行的 write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式, write_raw_get_fmt 函数决定了 wtite_raw 函数中 val 和 val2 的意义,也就是表 75.1.2.1 中的组合 形式。比如我们需要在应用程序中设置 ICM20608 加速度计的量程为±8g,那么分辨率就是 16/65536 ≈0.000244 ,我们 在 write_raw_get_fmt 函数 里面设置 加速度计的数 据格式 为 IIO_VAL_INT_PLUS_MICRO。那么我们在应用程序里面向指定的文件写入 0.000244 以后,最 终传递给内核驱动的就是 0.000244*1000000=244。也就是 write_raw 函数的 val 参数为 0,val2 参数为 244。

1.3:iio_chan_spec

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那 么这个 ADC 就有 8 个通道。我们本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,也就是一共有 7 路数据,因此就 有 7 个通道。注意,三轴陀螺仪或加速度计的 X、Y、Z 这三个轴,每个轴都算一个通道。 Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中,内 容如下:

223 struct iio_chan_spec {
224 enum iio_chan_type type;
225 int channel;
226 int channel2;
227 unsigned long address;
228 int scan_index;
229 struct {
230 char sign;
231 u8 realbits;
232 u8 storagebits;
233 u8 shift;
234 u8 repeat;
235 enum iio_endian endianness;
236 } scan_type;
237 long info_mask_separate;
238 long info_mask_shared_by_type;
239 long info_mask_shared_by_dir;
240 long info_mask_shared_by_all;
241 const struct iio_event_spec *event_spec;
242 unsigned int num_event_specs;
243 const struct iio_chan_spec_ext_info *ext_info;
244 const char *extend_name;
245 const char *datasheet_name;
246 unsigned modified:1;
247 unsigned indexed:1;
248 unsigned output:1;
249 unsigned differential:1;
250 };

来看一下 iio_chan_spec 结构体中一些比较重要的成员变量: 第 224 行,type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类 型,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下:

13 enum iio_chan_type {
14 IIO_VOLTAGE, /* 电压类型 */
15 IIO_CURRENT, /* 电流类型 */
16 IIO_POWER, /* 功率类型 */
17 IIO_ACCEL, /* 加速度类型 */
18 IIO_ANGL_VEL, /* 角度类型(陀螺仪) */
19 IIO_MAGN, /* 电磁类型(磁力计) */
20 IIO_LIGHT, /* 灯光类型 */
21 IIO_INTENSITY, /* 强度类型(光强传感器) */
22 IIO_PROXIMITY, /* 接近类型(接近传感器) */
23 IIO_TEMP, /* 温度类型 */
24 IIO_INCLI, /* 倾角类型(倾角测量传感器) */
25 IIO_ROT, /* 旋转角度类型 */
26 IIO_ANGL, /* 转动角度类型(电机旋转角度测量传感器) */
27 IIO_TIMESTAMP, /* 时间戳类型 */
28 IIO_CAPACITANCE, /* 电容类型 */
29 IIO_ALTVOLTAGE, /* 频率类型 */
30 IIO_CCT, /* 笔者暂时未知的类型 */
31 IIO_PRESSURE, /* 压力类型 */ 
32 IIO_HUMIDITYRELATIVE, /* 湿度类型 */
33 IIO_ACTIVITY, /* 活动类型(计步传感器) */
34 IIO_STEPS, /* 步数类型 */
35 IIO_ENERGY, /* 能量类型(卡路里) */
36 IIO_DISTANCE, /* 距离类型 */
37 IIO_VELOCITY, /* 速度类型 */
38 };

从示例代码 75.1.3.2 可以看出,目前 Linux 内核支持的传感器类型非常丰富,而且支持类 型也会不断的增加。如果是 ADC,那就是 IIO_VOLTAGE 类型。如果是 ICM20608 这样的多轴 传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是 IIO_ACCEL 类型,温度部分就是 IIO_TEMP。 继续来看示例代码 75.1.3.1 中的 iio_chan_spec 结构体,第 225 行,当成员变量 indexed 为 1 时候,channel 为通道索引。

第 226 行,当成员变量 modified 为 1 的时候,channel2 为通道修饰符。Linux 内核给出了 可用的通道修饰符,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下(有省略)

40 enum iio_modifier {
41 IIO_NO_MOD,
42 IIO_MOD_X, /* X 轴 */
43 IIO_MOD_Y, /* Y 轴 */
44 IIO_MOD_Z, /* Z 轴 */
......
73 };

比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL,X、Y、Z 这三个轴就用 channel2 的通道修饰符来区分。IIO_MOD_X、IIO_MOD_Y、IIO_MOD_Z 就分别对应 X、Y、Z 这三个 轴。通道修饰符主要是影响 sysfs 下的通道文件名字,后面我们会讲解 sysfs 下通道文件名字组 成形式。 继续回到示例代码 75.1.3.1,

第 227 行的 address 成员变量用户可以自定义,但是一般会设 置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道,它的数据首地址就是 0X3B。address 也可以用作其他功能,自行选择,也可以不使用 address,一切以实 际情况为准。

第 228 行,当使用触发缓冲区的时候,scan_index 是扫描索引。

第 229~236,scan_type 是一个结构体,描述了扫描数据在缓冲区中的存储格式。我们依次 来看一下 scan_type 各个成员变量的涵义:

scan_type.sign:如果为‘u’表示数据为无符号类型,为‘s’的话为有符号类型。 scan_type.realbits:数据真实的有效位数,比如很多传感器说的 10 位 ADC,其真实有效数 据就是 10 位。 scan_type.storagebits:存储位数,有效位数+填充位。比如有些传感器 ADC 是 12 位的, 那么我们存储的话肯定要用到 2 个字节,也就是 16 位,这 16 位就是存储位数。 scan_type.shift:右移位数,也就是存储位数和有效位数不一致的时候,需要右移的位数,这 个参数不总是需要,一切以实际芯片的数据手册位数。 scan_type.repeat:实际或存储位的重复数量。 scan_type.endianness:数据的大小端模式,可设置为 IIO_CPU、IIO_BE(大端)或 IIO_LE(小 端)。

第 237 行,info_mask_separate 标记某些属性专属于此通道,include/linux/iio/types.h 文件中 的 iio_chan_info_enum 枚举类型描述了可选的属性值,如下所示:

23 enum iio_chan_info_enum {
24 IIO_CHAN_INFO_RAW = 0,
25 IIO_CHAN_INFO_PROCESSED,
26 IIO_CHAN_INFO_SCALE,
27 IIO_CHAN_INFO_OFFSET,
......
45 IIO_CHAN_INFO_DEBOUNCE_TIME,
46 };

比如 ICM20608 加速度计的 X、Y、Z 这三个轴,在 sysfs 下这三个轴肯定是对应三个不同 的文件,我们通过读取这三个文件就能得到每个轴的原始数据。IIO_CHAN_INFO_RAW 这个属 性表示原始数据,当我们在配置 X、Y、Z 这三个通道的时候,在 info_mask_separate 中使能 IIO_CHAN_INFO_RAW 这个属性,那么就表示在 sysfs 下生成三个不同的文件分别对应 X、Y、 Z 轴,这三个轴的 IIO_CHAN_INFO_RAW 属性是相互独立的。

第 238 行,info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。也就是 iio_chan_spec.type 成员变量相同的通道。比如 ICM20608 加速度计的 X、Y、Z 轴他们的 type 都 是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通 道的时候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 这个属性,表示 这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道 都可以使用这一个分辨率文件。

第 239 行,info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。

第 240 行,info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类 型、方向如何,全部共享。

第 246 行,modified 为 1 的时候,channel2 为通道修饰符。

第 247 行,indexed 为 1 的时候,channel 为通道索引。

第 248 行,output 表示为输出通道。

第 249 行,differential 表示为差分通道。

2:IIO 驱动框架创建

前面我们已经对 IIO 设备、IIO 通道进行了详细的讲解,本节我们就来学习如何搭建 IIO 驱 动框架。在上一小节分析 IIO 子系统的时候大家应该看出了,IIO 框架主要用于 ADC 类的传感 器,比如陀螺仪、加速度计、磁力计、光强度计等,这些传感器基本都是 IIC 或者 SPI 接口的。 因此 IIO 驱动的基础框架就是 IIC 或者 SPI,我们可以在 IIC 或 SPI 驱动里面在加上上一章讲解 的 regmap。当然了,有些 SOC 内部的 ADC 也会使用 IIO 框架,那么这个时候驱动的基础框架 就是 platfrom。

2.1:基础驱动框架建立

我们以 SPI 接口为例,首先是 SPI 驱动框架,如下所示:

1 /*
2 * @description : spi 驱动的 probe 函数,当驱动与
3 * 设备匹配以后此函数就会执行
4 * @param - spi : spi 设备
5 * @return : 0,成功;其他值,失败
6 */ 
7 static int xxx_probe(struct spi_device *spi)
8 {
9 return 0;
10 }
11
12 /*
13 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
14 * @param - spi : spi 设备
15 * @return : 0,成功;其他负值,失败
16 */
17 static int xxx_remove(struct spi_device *spi)
18 {
19 return 0;
20 }
21
22 /* 传统匹配方式 ID 列表 */
23 static const struct spi_device_id xxx_id[] = {
24 {"alientek,xxx", 0},
25 {}
26 };
27
28 /* 设备树匹配列表 */
29 static const struct of_device_id xxx_of_match[] = {
30 { .compatible = "alientek,xxx" },
31 { /* Sentinel */ }
32 };
33
34 /* SPI 驱动结构体 */
35 static struct spi_driver xxx_driver = {
36 .probe = xxx_probe,
37 .remove = xxx_remove,
38 .driver = {
39 .owner = THIS_MODULE,
40 .name = "xxx",
41 .of_match_table = xxx_of_match,
42 },
43 .id_table = xxx_id,
44 };
45
46 /*
47 * @description : 驱动入口函数
48 * @param : 无
49 * @return : 无
50 */
51 static int __init xxx_init(void)
52 {
53 return spi_register_driver(&xxx_driver);
54 }
55
56 /*
57 * @description : 驱动出口函数
58 * @param : 无
59 * @return : 无
60 */
61 static void __exit xxx_exit(void)
62 {
63 spi_unregister_driver(&xxx_driver);
64 }
65
66 module_init(xxx_init);
67 module_exit(xxx_exit);
68 MODULE_LICENSE("GPL");
69 MODULE_AUTHOR("ALIENTEK");

示例代码 75.2.1.1 就是标准的 SPI 驱动框架,如果所使用的传感器是 IIC 接口的,那么就 是 IIC 驱动框架,没啥好说的。

2.2:IIO 设备申请与初始化

IIO 设备的申请、初始化以及注册在 probe 函数中完成,在注销驱动的时候还需要在 remove 函数中注销掉 IIO 设备、释放掉申请的一些内存。添加完 IIO 框架以后的 probe 和 remove 函数 如下所示:

1 /* 自定义设备结构体 */
2 struct xxx_dev {
3 struct spi_device *spi; /* spi 设备 */
4 struct regmap *regmap; /* regmap */
5 struct regmap_config regmap_config;
6 struct mutex lock;
7 };
8 
9 /*
10 * 通道数组
11 */
12 static const struct iio_chan_spec xxx_channels[] = {
13 
14 };
15 
16 /*
17 * @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,
18 * :此函数里面会从传感器里面读取各种数据,然后上传给应用。
19 * @param - indio_dev : IIO 设备
20 * @param - chan : 通道
21 * @param - val : 读取的值,如果是小数值的话,val 是整数部分。
22 * @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
23 * @param - mask : 掩码。
24 * @return : 0,成功;其他值,错误
25 */
26 static int xxx_read_raw(struct iio_dev *indio_dev,
27 struct iio_chan_spec const *chan,
28 int *val, int *val2, long mask)
29 {
30 return 0;
31 } 
32 
33 /*
34 * @description : 写函数,当向 sysfs 中的文件写数据的时候最终此函数
35 * :会执行,一般在此函数里面设置传感器,比如量程等。
36 * @param - indio_dev : IIO 设备
37 * @param - chan : 通道
38 * @param - val : 应用程序写入值,如果是小数的话,val 是整数部分。
39 * @param - val2 : 应用程序写入值,如果是小数的话,val2 是小数部分。
40 * @return : 0,成功;其他值,错误
41 */
42 static int xxx_write_raw(struct iio_dev *indio_dev,
43 struct iio_chan_spec const *chan,
44 int val, int val2, long mask)
45 {
46 return 0;
47 }
48 
49 /*
50 * @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设
51 * :置传感器的分辨率,如果分辨率带小数,那么这个小数传递到
52 * : 内核空间应该扩大多少倍,此函数就是用来设置这个的。
53 * @param - indio_dev : iio_dev
54 * @param - chan : 通道
55 * @param - mask : 掩码
56 * @return : 0,成功;其他值,错误
57 */
58 static int xxx_write_raw_get_fmt(struct iio_dev *indio_dev,
59 struct iio_chan_spec const *chan, long mask)
60 {
61 return 0;
62 }
63 
64 /*
65 * iio_info 结构体变量
66 */
67 static const struct iio_info xxx_info = {
68 .read_raw = xxx_read_raw,
69 .write_raw = xxx_write_raw,
70 .write_raw_get_fmt = &xxx_write_raw_get_fmt,
71 };
72 
73 /*
74 * @description : spi 驱动的 probe 函数,当驱动与
75 * 设备匹配以后此函数就会执行
76 * @param - spi : spi 设备
77 * 
78 */ 
79 static int xxx_probe(struct spi_device *spi)
80 {
81 int ret;
82 struct xxx_dev *data;
83 struct iio_dev *indio_dev;
84 
85 /* 1、申请 iio_dev 内存 */
86 indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
87 if (!indio_dev)
88 return -ENOMEM;
89 
90 /* 2、获取 xxx_dev 结构体地址 */
91 data = iio_priv(indio_dev);
92 data->spi = spi;
93 spi_set_drvdata(spi, indio_dev);
94 mutex_init(&data->lock);
95 
96 /* 3、初始化 iio_dev 成员变量 */
97 indio_dev->dev.parent = &spi->dev;
98 indio_dev->info = &xxx_info;
99 indio_dev->name = "xxx";
100 indio_dev->modes = INDIO_DIRECT_MODE; /* 直接模式 /
101 indio_dev->channels = xxx_channels;
102 indio_dev->num_channels = ARRAY_SIZE(xxx_channels);
103
104 iio_device_register(indio_dev);
105
106 /* 4、regmap 相关设置 */
107
108 /* 5、SPI 相关设置*/
109 
110 /* 6、芯片初始化 */
111
112 return 0;
113
114 }
115
116 /*
117 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
118 * @param - spi : spi 设备
119 * @return : 0,成功;其他负值,失败
120 */
121 static int xxx_remove(struct spi_device *spi)
122 {
123 struct iio_dev *indio_dev = spi_get_drvdata(spi);
124 struct xxx_dev *data;
125 
126 data = iio_priv(indio_dev); ;
127
128 /* 1、其他资源的注销以及释放 */
129 
130 /* 2、注销 IIO */
131 iio_device_unregister(indio_dev);
132 
133 return 0;
134 }

第 2~7 行,用户自定义的设备结构体。

第 12 行,IIO 通道数组。

第 16~71 行, 这部分为 iio_info,当应用程序读取相应的驱动文件的时候,xxx_read_raw 函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱 动写数据的时候,xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函 数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。

第 79~114 行,xxx_probe 函数,此函数的核心就是分配并初始化 iio_dev,最后向内核注册 iio_dev。

第 86 行调用 devm_iio_device_alloc 函数分配 iio_dev 内存,这里连用户自定义的设备 结构体变量内存一起申请了。

第 91 行调用 iio_priv 函数从 iio_dev 中提取出私有数据,这个私 有数据就是设备结构体变量。

第 97~102 行初始化 iio_dev,重点是第 98 行设置 iio_dev 的 info 成员变量。

第 101 行设置 iio_dev 的通道。初始化完成以后就要调用 iio_device_register 函数向 内核注册 iio_dev。整个过程就是:申请 iio_dev、初始化、注册,和我们前面讲解的其他驱动框 架步骤一样。

第 121~134 行,xxx_remove 函数里面需要做的就是释放 xxx_probe 函数申请到的 IIO 相关 资源,比如第 131 行,使用 iio_device_unregister 注销掉前面注册的 iio_dev。由于前面我们使用 devm_iio_device_alloc 函数申请的 iio_dev,因此不需要在 remove 函数中手动释放 iio_dev。

IIO 框架示例就讲解到这里,剩下的就是根据所使用的具体传感器,在 IIO 驱动框架里面添 加相关的处理,接下来我们就以正点原子 I.MX6ULL 开发板上的 ICM20608 为例,进行 IIO 驱 动实战!

3:实验程序编写

接下来我们就直接使用 IIO 驱动框架编写 ICM20608 驱动,ICM20608 驱动核心就是 SPI, 上一章我们也讲解了如何在 SPI 总线上使用 regmap。因此本章 ICM20608 驱动底层没啥好讲解 的,重点是如何套上 IIO 驱动框架,因此关于 ICM20608 芯片内部寄存器、SPI 驱动、regmap 等 不会做讲解,有什么不懂的可以看前面相应的实验章节。

本实验对应的例程路径为:开发板光盘→01、程序源码→02、Linux 驱动例程→27_iio→spi

3.1:使能内核 IIO 相关配置

Linux 内核默认使能了 IIO 子系统,但是有一些 IIO 模块没有选择上,这样会导致我们编译 驱动的时候会提示某些 API 函数不存在,需要使能的项目如下:

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

如图 75.3.1.1 所示:

3.2:ICM20608 的 IIO 驱动框架搭建

3.2.1:驱动框架搭建

设备树不需要做任何修改! 首先我们基于上一章实验,搭建出 ICM20608 的 IIO 驱动框架,IIO 框架部分我们已经在示 例代码 75.2.2.1 中进行了详细的讲解。新建名为 icm20608.c 的驱动文件,搭建好的 ICM20608 IIO 驱动框架内容如下所示:

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608.c
作者 : 正点原子 Linux 团队
版本 : V1.0
描述 : ICM20608 SPI 驱动程序
其他 : 无
论坛 : www.openedv.com
日志 : 初版 V1.0 2021/03/22 正点原子 Linux 团队创建
 V1.1 2021/08/10 
 使用 regmap 读写 SPI 外设内部寄存器。
 V1.2 2021/08/13 
 使用 IIO 框架,参考 bma220_spi.c
1 #include <linux/spi/spi.h>
2 #include <linux/kernel.h>
3 #include <linux/module.h>
4 #include <linux/init.h>
5 #include <linux/delay.h>
6 #include <linux/ide.h>
7 #include <linux/errno.h>
8 #include <linux/platform_device.h>
9 #include "icm20608reg.h"
10 #include <linux/gpio.h>
11 #include <linux/device.h>
12 #include <asm/uaccess.h>
13 #include <linux/cdev.h>
14 #include <linux/regmap.h>
15 #include <linux/iio/iio.h>
16 #include <linux/iio/sysfs.h>
17 #include <linux/iio/buffer.h>
18 #include <linux/iio/trigger.h>
19 #include <linux/iio/triggered_buffer.h>
20 #include <linux/iio/trigger_consumer.h>
21 #include <linux/unaligned/be_byteshift.h>
22 
23 #define ICM20608_NAME "icm20608"
24 
25 #define ICM20608_CHAN(_type, _channel2, _index) \
26 { \
27 .type = _type, \
28 .modified = 1, \
29 .channel2 = _channel2, \
30 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
31 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
32 BIT(IIO_CHAN_INFO_CALIBBIAS), \
33 .scan_index = _index, \
34 .scan_type = { \
35 .sign = 's', \
36 .realbits = 16, \
37 .storagebits = 16, \
38 .shift = 0, \
39 .endianness = IIO_BE, \
40 }, \
41 }
42
43 /* 
44 * ICM20608 的扫描元素,3 轴加速度计、
45 * 3 轴陀螺仪、1 路温度传感器,1 路时间戳
46 */
47 enum inv_icm20608_scan {
48 INV_ICM20608_SCAN_ACCL_X,
49 INV_ICM20608_SCAN_ACCL_Y,
50 INV_ICM20608_SCAN_ACCL_Z,
51 INV_ICM20608_SCAN_TEMP,
52 INV_ICM20608_SCAN_GYRO_X,
53 INV_ICM20608_SCAN_GYRO_Y,
54 INV_ICM20608_SCAN_GYRO_Z,
55 INV_ICM20608_SCAN_TIMESTAMP,
56 };
57 
58 struct icm20608_dev {
59 struct spi_device *spi; /* spi 设备 */
60 struct regmap *regmap; /* regmap */
61 struct regmap_config regmap_config;
62 struct mutex lock;
63 };
64 
65 /*
66 * icm20608 通道,1 路温度通道,3 路陀螺仪,3 路加速度计
67 */
68 static const struct iio_chan_spec icm20608_channels[] = {
69 /* 温度通道 */
70 {
71 .type = IIO_TEMP,
72 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
73 | BIT(IIO_CHAN_INFO_OFFSET)
74 | BIT(IIO_CHAN_INFO_SCALE),
75 .scan_index = INV_ICM20608_SCAN_TEMP,
76 .scan_type = {
77 .sign = 's',
78 .realbits = 16,
79 .storagebits = 16,
80 .shift = 0,
81 .endianness = IIO_BE,
82 },
83 },
84 
85 ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X,INV_ICM20608_SCAN_GYRO_X),
86 ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),
87 ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z), 
88 
89 ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), 
90 ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), 
91 ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), 
92 };
93 
94 /*
95 * @description : 读取 icm20608 指定寄存器值,读取一个寄存器
96 * @param – dev : icm20608 设备
97 * @param – reg : 要读取的寄存器
98 * @return : 读取到的寄存器值
99 */
100 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
u8 reg)
101 {
102 u8 ret;
103 unsigned int data;
104
105 ret = regmap_read(dev->regmap, reg, &data);
106 return (u8)data;
107 }
108
109 /*
110 * @description : 向 icm20608 指定寄存器写入指定的值,写一个寄存器
111 * @param – dev : icm20608 设备
112 * @param – reg : 要写的寄存器
113 * @param – data : 要写入的值
114 * @return : 无
115 */
116 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,
u8 value)
117 {
118 regmap_write(dev->regmap, reg, value);
119 }
120
121 /*
122 * @description : ICM20608 内部寄存器初始化函数
123 * @param – spi : 要操作的设备
124 * @return : 无
125 */
126 void icm20608_reginit(struct icm20608_dev *dev)
127 {
128 u8 value = 0;
129 
130 icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);
131 mdelay(50);
132 icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);
133 mdelay(50);
134
135 value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
136 printk("ICM20608 ID = %#X\r\n", value);
137
138 icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); 
139 icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); 
140 icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18);
141 icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); 
142 icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04);
143 icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); 
144 icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); 
145 icm20608_write_onereg(dev, ICM20_INT_ENABLE, 0x01); 
146 }
147
148 /*
149 * @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执
150 * :行,此函数里面会从传感器里面读取各种数据,然后上传给应用。
151 * @param - indio_dev : iio_dev
152 * @param - chan : 通道
153 * @param - val : 读取的值,如果是小数值的话,val 是整数部分。
154 * @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
155 * @param - mask : 掩码。
156 * @return : 0,成功;其他值,错误
157 */
158 static int icm20608_read_raw(struct iio_dev *indio_dev,
159 struct iio_chan_spec const *chan,
160 int *val, int *val2, long mask)
161 {
162 printk("icm20608_read_raw\r\n");
163 return 0;
164 } 
165
166 /*
167 * @descriptio : 写函数,当向 sysfs 中的文件写数据的时候最终此函数会
168 * :执行,一般在此函数里面设置传感器,比如量程等。
169 * @param - indio_dev : iio_dev
170 * @param – chan : 通道
171 * @param – val : 应用程序写入的值,如果是小数值的话,val 是整数部分。
172 * @param - val2 : 应用程序写入的值,如果是小数值的话,val2 是小数部分。
173 * @return : 0,成功;其他值,错误
174 */
175 static int icm20608_write_raw(struct iio_dev *indio_dev,
176 struct iio_chan_spec const *chan,
177 int val, int val2, long mask)
178 {
179 printk("icm20608_write_raw\r\n");
180 return 0;
181
182 }
183
184 /*
185 * @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传
186 * :感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间
187 * : 应该扩大多少倍,此函数就是用来设置这个的。
188 * @param - indio_dev : iio_dev
189 * @param - chan : 通道
190 * @param - mask : 掩码
191 * @return : 0,成功;其他值,错误
192 */
193 static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
194 struct iio_chan_spec const *chan, long mask)
195 {
196 printk("icm20608_write_raw_get_fmt\r\n");
197 return 0;
198
199 }
200
201 /*
202 * iio_info 结构体变量
203 */
204 static const struct iio_info icm20608_info = {
205 .read_raw = icm20608_read_raw,
206 .write_raw = icm20608_write_raw,
207 .write_raw_get_fmt = &icm20608_write_raw_get_fmt,
208 };
209
210 /*
211 * @description : spi 驱动的 probe 函数,当驱动与
212 * 设备匹配以后此函数就会执行
213 * @param – spi : spi 设备
214 * @return : 0,成功;其他值,失败
215 */ 
216 static int icm20608_probe(struct spi_device *spi)
217 {
218 int ret;
219 struct icm20608_dev *dev;
220 struct iio_dev *indio_dev;
221
222 /* 1、申请 iio_dev 内存 */
223 indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));
224 if (!indio_dev)
225 return -ENOMEM;
226
227 /* 2、获取 icm20608_dev 结构体地址 */
228 dev = iio_priv(indio_dev);
229 dev->spi = spi;
230 spi_set_drvdata(spi, indio_dev);
231 mutex_init(&dev->lock);
232
233 /* 3、iio_dev 的其他成员变量 */
234 indio_dev->dev.parent = &spi->dev;
235 indio_dev->info = &icm20608_info;
236 indio_dev->name = ICM20608_NAME; 
237 indio_dev->modes = INDIO_DIRECT_MODE;/* 直接模式,提供 sysfs 接口 */
238 indio_dev->channels = icm20608_channels;
239 indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);
240
241 /* 4、注册 iio_dev */
242 ret = iio_device_register(indio_dev);
243 if (ret < 0) {
244 dev_err(&spi->dev, "iio_device_register failed\n");
245 goto err_iio_register;
246 }
247
248 /* 5、初始化 regmap_config 设置 */
249 dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
250 dev->regmap_config.val_bits = 8; /* 值长度 8bit */
251 dev->regmap_config.read_flag_mask = 0x80; /* 读掩码设置为 0X80 */
252
253 /* 6、初始化 SPI 接口的 regmap */
254 dev->regmap = regmap_init_spi(spi, &dev->regmap_config);
255 if (IS_ERR(dev->regmap)) {
256 ret = PTR_ERR(dev->regmap);
257 goto err_regmap_init;
258 }
259
260 /* 7、初始化 spi_device */
261 spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
262 spi_setup(spi);
263 
264 /* 初始化 ICM20608 内部寄存器 */
265 icm20608_reginit(dev); 
266 return 0;
267
268 err_regmap_init:
269 iio_device_unregister(indio_dev);
270 err_iio_register:
271 return ret;
272 }
273
274 /*
275 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
276 * @param - spi : spi 设备
277 * @return : 0,成功;其他负值,失败
278 */
279 static int icm20608_remove(struct spi_device *spi)
280 {
281 struct iio_dev *indio_dev = spi_get_drvdata(spi);
282 struct icm20608_dev *dev;
283 
284 dev = iio_priv(indio_dev);
285
286 /* 1、删除 regmap */
287 regmap_exit(dev->regmap);
288
289 /* 2、注销 IIO */
290 iio_device_unregister(indio_dev);
291 return 0;
292 }
293
294 /* 传统匹配方式 ID 列表 */
295 static const struct spi_device_id icm20608_id[] = {
296 {"alientek,icm20608", 0},
297 {}
298 };
299
300 /* 设备树匹配列表 */
301 static const struct of_device_id icm20608_of_match[] = {
302 { .compatible = "alientek,icm20608" },
303 { /* Sentinel */ }
304 };
305
306 /* SPI 驱动结构体 */
307 static struct spi_driver icm20608_driver = {
308 .probe = icm20608_probe,
309 .remove = icm20608_remove,
310 .driver = {
311 .owner = THIS_MODULE,
312 .name = "icm20608",
313 .of_match_table = icm20608_of_match,
314 },
315 .id_table = icm20608_id,
316 };
317
318 /*
319 * @description : 驱动入口函数
320 * @param : 无
321 * @return : 无
322 */
323 static int __init icm20608_init(void)
324 {
325 return spi_register_driver(&icm20608_driver);
326 }
327
328 /*
329 * @description : 驱动出口函数
330 * @param : 无
331 * @return : 无
332 */
333 static void __exit icm20608_exit(void)
334 {
335 spi_unregister_driver(&icm20608_driver);
336 }
337
338 module_init(icm20608_init);
339 module_exit(icm20608_exit);
340 MODULE_LICENSE("GPL");
341 MODULE_AUTHOR("ALIENTEK");
342 MODULE_INFO(intree, "Y");

示例代码 75.3.1.1 就是在上一章例程的基础上添加 IIO 驱动框架而来的,这里我们重点讲 解一下 IIO 部分。

第 25~41 行,通道宏定义,用于陀螺仪和加速度计,第 28 行 modified 成员变量为 1,所以 channel2 就是通道修饰符,用来指定 X、Y、Z 轴。第 30 行设置相同类型的通道 IIO_CHAN_INFO_SCALE 属性相同,“scale”是比例的意思,在这里就是量程(分辨率),因为 ICM20608 的陀螺仪和加速度计的量程是可以调整的,量程不同分辨率也就不同。陀螺仪或加 速度计的三个轴也是一起设置的,因此对于陀螺仪或加速度计而言,X、Y、Z 这三个轴的量程 是共享的。

第 31 行,设置每个通道的 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS 这两个属性都是独立的,IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,这个肯定 是每个通道独立的。IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是 ICM20608 的特性,不是所有的传感器都有校准值,一切都要以实际所使用的传感器为准。

第 34 行,设置扫描数据类型,也就是 ICM20608 原始数据类型,ICM20608 的陀螺仪和加速度计都是 16 位 ADC,因此这里是通用的:为有符号类型、实际位数 16bit,存储位数 16bit,大端模式 (ICM20608 数据寄存器为大端模式)。

第 47~56 行,自定义的扫描索引枚举类型 inv_icm20608_scan,包括陀螺仪、加速度计的 6 个通道,温度计的 1 个通道、以及 1 个 ICM20608 时间戳通道。

第 58~63 行,设备结构体,由于采用了 regmap 和 IIO 框架,因此 ICM20608 的设备结构体 非常简单。

第 68~92 行,ICM20608 通道,这里定义了 7 个通道,分别是:1 个温度通道,3 个陀螺仪 通道(X、Y、Z),3 个加速度计通道(X、Y、Z)。温度通道有三个属性,IIO_CHAN_INFO_RAW 为温度通道的原始值,IIO_CHAN_INFO_OFFSET 是 ICM20608 温度 offset 值,这个要查阅数 据手册。IIO_CHAN_INFO_SCALE 是 ICM20608 的比例,也就是一个单位的原始值为多少℃, 这个也要查阅 ICM20608 的数据手册。从这里可以看出,想要得到 ICM20608 的具体温度值, 需要三个数据:原始值、offset 值、比例值,也就是应用程序需要能够从 IIO 驱动框架中的到这 三个值,一般是应用程序读取相应的文件,所以这里就要有三个独立的文件分别表示原始值、 offset 值、比例值,这就是三个属性的来源。剩下的陀螺仪和加速度计通道设置使用宏 ICM20608_CHAN 即可,IIO_MOD_X、IIO_MOD_Y 和 IIO_MOD_Z 分别是 X、Y、Z 这三个轴 的修饰符。

第 204~208 行,这部分就是 iio_info,icm20608_read_raw 为读取函数,应用程序读取相应 文件的时候此函数执行,icm20608_write_raw 为写函数,应用程序向相应的文件写数据的时候 此函数执行。icm20608_write_raw_get_fmt 函数用来设置应用程序向驱动写入的数据格式, icm20608_info 就是具体的 iio_info 变量,初始化 iio_dev 的时候需要用到。

第 216 行,icm20608_probe 函数,一般在此函数里面申请 iio_dev、初始化并注册,初始化 regmap、初始化 ICM20608 等。

第 216 行通过 devm_iio_device_alloc 函数申请 iio_dev 以及自定 义设备结构体内存,本章节就是 icm20608_dev。

第 234~239 行,初始化 iio_dev,

第 242 行,调 用 iio_device_register 函数想内核注册 iio_dev。 关于 ICM20608 的 IIO 驱动框架就讲解到这里,接下来就是测试此驱动框架。

3.2.2:驱动框架测试

我们已经搭建好了 ICM20608 的 IIO 驱动框架,通道也已经设置好了,虽然还不能直接读 取到 ICM20608 的原始数据,但是我们可以通过驱动框架来窥探 IIO 在用户空间的存在方式。 编译驱动,得到 icm20608.ko 驱动文件。入如下命令加载 icm20608.ko 这个驱动模块。

depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块

我们在 icm20608_probe 函数里面设置了打印 ICM20608 ID 值,因此如果驱动加载成功, SPI 工作正常的话就会读取 ICM20608 的 ID 值并打印出来,如图 75.3.2.1 所示:

IIO 驱动框架提供了 sysfs 接口,因此加载成功以后我们可以在用户空间访问对应的 sysfs 目录项,进入目录“/sys/bus/iio/devices/”目录里面,此目录下都是 IIO 框架设备,如图 75.3.2.2 所示:

 从图 75.3.2.2 可以看出,此时有两个 IIO 设备“iio:device0”,iio:device0 是 I.MX6ULL 内 部 ADC,iio:device1 才是 ICM20608。大家进入到对应的设备目录就可以看出对应的 IIO 设 备,我们进入图 75.3.2.2 中的“iio:device1”目录,此目录下的内容如图 75.3.2.3 所示:

 从图 75.3.2.3 可以看出,iio:device1 对应 spi2.0 上的设备,也就是 ICM20608,此目录下有 很多文件,比如 in_accel_scale、in_accel_x_calibias、in_accel_x_raw 等,这些就是我们设置的通 道。in_accel_scale 就是加速度计的比例,也就是分辨率(量程),in_accel_x_calibias 就是加速度 计 X 轴的校准值,in_accel_x_raw 就是加速度计的 X 轴原始值。我们在配置通道的时候,设置 了类型相同的所有通道共用 SCALE,所以这里只有一个 in_accel_scale,而 X、Y、Z 轴的原始 值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

3.2.3:通道文件命名方式

我们来看一下图 75.3.2.3 中这些文件名字组成方式,以 in_accel_x_raw 为例,这是加速度 计的 X 轴原始值,驱动代码中此通道的配置内容展开以后如下(演示代码):

1 .type = IIO_ANGL_VEL, 
2 .modified = 1, 
3 .channel2 = IIO_MOD_X, 
4 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
5 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 
6 BIT(IIO_CHAN_INFO_CALIBBIAS), 
7 .scan_index = INV_ICM20608_SCAN_GYRO_X, 
8 .scan_type = { 
9 .sign = 's', 
10 .realbits = 16, 
11 .storagebits = 16, 
12 .shift = 0, 
13 .endianness = IIO_BE, 
14 }, 

第 5 行设置了此通道有 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS 这两个 专属属性,因此才会有图 75.3.2.2 中的 in_accel_x_raw 和 in_accel_x_calibias 这两个文件。 通 道 属 性 的 命 名 , 也 就 是 图 75.3.2.2 中 文 件 的 命 名 模 式 为 : [direction]_[type]_[index]_[modifier]_[info_mask],我们依次来看一下这些命名组织模块: direction:为属性对应的方向,iio_direction 结构体定义了方向,内容如下:

48 static const char * const iio_direction[] = {
49 [0] = "in",
50 [1] = "out",
51 };

可以看出,就有两个方向:in 和 out。

type:也就是配置通道的时候 type 值,type 对应的字符可以参考 iio_chan_type_name_spec, 如下:

53 static const char * const iio_chan_type_name_spec[] = {
54 [IIO_VOLTAGE] = "voltage",
55 [IIO_CURRENT] = "current",
56 [IIO_POWER] = "power",
57 [IIO_ACCEL] = "accel",
58 [IIO_ANGL_VEL] = "anglvel",
59 [IIO_MAGN] = "magn",
......
85 [IIO_GRAVITY] = "gravity",
86 [IIO_POSITIONRELATIVE] = "positionrelative",
87 [IIO_PHASE] = "phase",
88 [IIO_MASSCONCENTRATION] = "massconcentration",
89 };

所以,当通道的 type 设置为 IIO_ACCEL 的时候,对应的名字就是“accel”。 index:索引,如果配置通道的时候设置了 indexed=1,那么就会使用通道的 channel 成员变 量来替代此部分命名。比如,有个 ADC 芯片支持 8 个通道,那么就可以使用 channel 来表示对 应的通道,最终在用户空间呈现的每个通道文件名的 index 部分就是通道号。

modifier:当通道的 modified 成员变量为 1 的时候,channel2 就是修饰符,修饰符对应的 字符串参考结构体 iio_modifier_names,内容如下:

91 static const char * const iio_modifier_names[] = {
92 [IIO_MOD_X] = "x",
93 [IIO_MOD_Y] = "y",
94 [IIO_MOD_Z] = "z",
......
131 [IIO_MOD_PM4] = "pm4",
132 [IIO_MOD_PM10] = "pm10",
133 };

当通道的修饰符设置为 IIO_MOD_X 的时候,对应的名字就是“x”。 info_mask:属性掩码,也就是属性,不同属性对应的字符如下所示:

136 static const char * const iio_chan_info_postfix[] = {
137 [IIO_CHAN_INFO_RAW] = "raw",
138 [IIO_CHAN_INFO_PROCESSED] = "input",
139 [IIO_CHAN_INFO_SCALE] = "scale",
140 [IIO_CHAN_INFO_OFFSET] = "offset",
141 [IIO_CHAN_INFO_CALIBSCALE] = "calibscale",
142 [IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
......
161 [IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time",
162 [IIO_CHAN_INFO_CALIBEMISSIVITY] = "calibemissivity",
163 [IIO_CHAN_INFO_OVERSAMPLING_RATIO] = "oversampling_ratio",
164 };

可以看出,IIO_CHAN_INFO_RAW 属性对应的就是“raw”,IIO_CHAN_INFO_SCALE 属 性对应的是“scale”。 综上所述,in_accel_x_raw 组成形式如图 75.3.2.4 所示

其他文件的命名组成方式类似,大家自行分析即可。

3.2.4:文件读测试

我们可以测试一下对图 75.3.2.3 中的这些文件进行读写操作,看看有什么效果,比如 in_accel_x_raw 文件是 ICM20608 的加速度计 X 轴原始值通道,使用 cat 命令查看此文件内容, 结果如图 75.3.2.5 所示:

从图 75.3.2.5 可以看出,当用户空间读取相应文件的时候,iio_info 下的 read 函数会被调 用,因此关于传感器数据读取的操作都是在此函数中完成的。由于驱动还不完善,大家就先不要测试向指定文件写操作,否则可能会报错,后面再测试写操作。大家也可以读取一下其他文 件,比如陀螺仪的 X 轴文件 in_anglvel_x_raw,结果最终都是调用的 icm20608_read_raw 这个函 数。 接下来的重点就是完善驱动中的 icm20608_read_raw 函数,实现传感器数据的读取操作。

3.3:完善 icm20608_read_raw 函数

应 用 程 序 所 有 的 读 取 操 作 , 最 终 都 会 汇 总 到 iio_info 的 read 函 数 , 这 里 就 是 icm20608_read_raw 函数。由于所有的读取操作都会触发 icm20608_read_raw 函数,比如加速度 计、陀螺仪、温度计等,因此我们需要做区分。配置通道的时候设置了 type 值,就可以使用 type 值来区分是陀螺仪、加速度计还是温度计,修改后的 icm20608_read_raw 函数如下

1 static int icm20608_read_raw(struct iio_dev *indio_dev,
2 struct iio_chan_spec const *chan,
3 int *val, int *val2, long mask)
4 
5 struct icm20608_dev *dev = iio_priv(indio_dev);
6 int ret = 0;
7 unsigned char regdata = 0;
8 printk("icm20608_read_raw\r\n");
9 
10 switch (mask) {
11 case IIO_CHAN_INFO_RAW: /* 读取加速度计、陀螺仪、温度传感器原始值 */
12 printk("read raw data\r\n"); 
13 return ret;
14 case IIO_CHAN_INFO_SCALE:
15 switch (chan->type) {
16 case IIO_ANGL_VEL:
17 printk("read gyro sacle\r\n");
18 return ret;
19 case IIO_ACCEL:
20 printk("read accel sacle\r\n");
21 return 0;
22 case IIO_TEMP: 
23 printk("read temp sacle\r\n");
24 return ret;
25 default:
26 return -EINVAL;
27 }
28 case IIO_CHAN_INFO_OFFSET: /* ICM20608 温度传感器 offset 值 */
29 switch (chan->type) {
30 case IIO_TEMP:
31 printk("read temp offset\r\n");
32 return ret;
33 default:
34 return -EINVAL;
35 }
36 return ret;
37 case IIO_CHAN_INFO_CALIBBIAS: /* ICM20608 加速度计和陀螺仪校准值 */
38 switch (chan->type) {
39 case IIO_ANGL_VEL: /* 陀螺仪的校准值 */
40 printk("read gyro calibbias\r\n");
41 return ret;
42 case IIO_ACCEL: /* 加速度计的校准值 */
43 printk("read accel calibbias\r\n");
44 return ret;
45 default:
46 return -EINVAL;
47 }
48 default:
49 return ret -EINVAL;
50 } 

示例代码 75.3.3.1 给出了很多分支,这些分支对应不同的文件读操作。比如我们读取 in_accel_scale 这个文件,这个是加速度计的比例文件,对应的就是第 19 行这个分支,结果如图 75.3.3.1 所示:

从图 75.3.3.1 可以看出,输出了“read accelscale”这行字符串,这行字符串正好是第 19 行 的分支。我们就可以在这个分支里面读取 ICM20608 的加速度计比例值,也就是量程。其他文 件也一样,最终都会对应到相应的分支里面,我们只需要在相应的分支里面做具体的操作就行 了。剩下的操作就是读取 ICM20608 的内部寄存器数据,最终修改好的 icm20608_read_raw 函 数和其他一些配套函数如下:

22 #define ICM20608_TEMP_OFFSET 0
23 #define ICM20608_TEMP_SCALE 326800000
......
65 /* icm20608 陀螺仪分辨率,对应 250、500、1000、2000,计算方法:
66 * 以正负 250 度量程为例,500/2^16=0.007629,扩大 1000000 倍,就是 7629
67 */
68 static const int gyro_scale_icm20608[] = {7629, 15258,
30517, 61035};
69 
70 /* icm20608 加速度计分辨率,对应 2、4、8、16 计算方法:
71 * 以正负 2g 量程为例,4/2^16=0.000061035,扩大 1000000000 倍,就是 61035
72 */
73 static const int accel_scale_icm20608[] = {61035, 122070,
 244140, 488281};
74 
......
155
156 /*
157 * @description : 读取 ICM20608 传感器数据,可以用于陀螺仪、加速度计、温度
158 * @param – dev : icm20608 设备
159 * @param - reg : 要读取的通道寄存器首地址。
160 * @param – anix : 需要读取的通道,比如 X,Y,Z。
161 * @param - *val : 保存读取到的值。
162 * @return : 0,成功;其他值,错误
163 */
164 static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,
165 int axis, int *val)
166 {
167 int ind, result;
168 __be16 d;
169
170 ind = (axis - IIO_MOD_X) * 2;
171 result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);
172 if (result)
173 return -EINVAL;
174 *val = (short)be16_to_cpup(&d);
175
176 return IIO_VAL_INT;
177 }
178
179 /*
180 * @description : 读取 ICM20608 陀螺仪、加速度计、温度通道值
181 * @param - indio_dev : iio 设备
182 * @param - chan : 通道。
183 * @param - val : 保存读取到的通道值。
184 * @return : 0,成功;其他值,错误
185 */
186 static int icm20608_read_channel_data(struct iio_dev *indio_dev,
187 struct iio_chan_spec const *chan,
188 int *val)
189 {
190 struct icm20608_dev *dev = iio_priv(indio_dev);
191 int ret = 0;
192
193 switch (chan->type) {
194 case IIO_ANGL_VEL: /* 读取陀螺仪数据 */
195 ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H,
chan->channel2, val); /* channel2 为 X、Y、Z 轴 */
196 break;
197 case IIO_ACCEL: /* 读取加速度计数据 */
198 ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H,
chan->channel2, val); /* channel2 为 X、Y、Z 轴 */
199 break;
200 case IIO_TEMP: /* 读取温度 */
201 ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H,
IIO_MOD_X, val); 
202 break;
203 default:
204 ret = -EINVAL;
205 break;
206 }
207 return ret;
208 }
209
210 /*
211 * @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函
212 * :数里面会从传感器里面读取各种数据,然后上传给应用。
213 * @param - indio_dev : iio_dev
214 * @param - chan : 通道
215 * @param - val : 读取的值,如果是小数值的话,val 是整数部分。
216 * @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
217 * @param - mask : 掩码。
218 * @return : 0,成功;其他值,错误
219 */
220 static int icm20608_read_raw(struct iio_dev *indio_dev,
221 struct iio_chan_spec const *chan,
222 int *val, int *val2, long mask)
223 {
224 struct icm20608_dev *dev = iio_priv(indio_dev);
225 int ret = 0;
226 unsigned char regdata = 0;
227
228 switch (mask) {
229 case IIO_CHAN_INFO_RAW: /* 读取 ICM20608 加速度计、陀螺仪、
温度传感器原始值 */
230 iio_device_claim_direct_mode(indio_dev);/* 保持 direct 模式 */
231 mutex_lock(&dev->lock); /* 上锁 */
232 ret = icm20608_read_channel_data(indio_dev, chan, val); 
233 mutex_unlock(&dev->lock); /* 释放锁 */
234 iio_device_release_direct_mode(indio_dev);
235 return ret;
236 case IIO_CHAN_INFO_SCALE:
237 switch (chan->type) {
238 case IIO_ANGL_VEL:
239 mutex_lock(&dev->lock);
240 regdata = (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG)
& 0X18) >> 3;
241 *val = 0;
242 *val2 = gyro_scale_icm20608[regdata];
243 mutex_unlock(&dev->lock);
244 return IIO_VAL_INT_PLUS_MICRO; /* 值为 val+val2/1000000 */
245 case IIO_ACCEL:
246 mutex_lock(&dev->lock);
247 regdata = (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG)
& 0X18) >> 3;
248 *val = 0;
249 *val2 = accel_scale_icm20608[regdata];;
250 mutex_unlock(&dev->lock);
251 return IIO_VAL_INT_PLUS_NANO;/* 值为 val+val2/1000000000 */
252 case IIO_TEMP: 
253 *val = ICM20608_TEMP_SCALE/ 1000000;
254 *val2 = ICM20608_TEMP_SCALE % 1000000;
255 return IIO_VAL_INT_PLUS_MICRO; /* 值为 val+val2/1000000 */
256 default:
257 return -EINVAL;
258 }
259 return ret;
260 case IIO_CHAN_INFO_OFFSET: /* ICM20608 温度传感器 offset 值 */
261 switch (chan->type) {
262 case IIO_TEMP:
263 *val = ICM20608_TEMP_OFFSET;
264 return IIO_VAL_INT;
265 default:
266 return -EINVAL;
267 }
268 return ret;
269 case IIO_CHAN_INFO_CALIBBIAS: /* ICM20608 加速度计和陀螺仪校准值 */
270 switch (chan->type) {
271 case IIO_ANGL_VEL: /* 陀螺仪的校准值*/
272 mutex_lock(&dev->lock);
273 ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH,
chan->channel2, val);
274 mutex_unlock(&dev->lock);
275 return ret;
276 case IIO_ACCEL: /* 加速度计的校准值*/
277 mutex_lock(&dev->lock);
278 ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H,
chan->channel2, val);
279 mutex_unlock(&dev->lock);
280 return ret;
281 default:
282 return -EINVAL;
283 }
284 
285 default:
286 return ret -EINVAL;
287 }
288 } 

第 68 行,gyro_scale_icm20608 是 ICM20608 的陀螺仪比例,也就是陀螺仪不同量程对应的 分辨率。ICM20608 陀螺仪为 16bit,可选的量程有±250、±500、±1000 和±2000°/s。以± 250 这个量程为例,每个数值对应的度数就是 500/2^10≈0.007629°/s。同理,±500 量程对应 的度数为 0.015258,±1000 量程对应的度数为 0.030517,±2000 量程为 0.061035。假设现在设 置量程为±2000,读取到的原始值为 12540,那么对应的度数就是 12540*0.061035≈765.37°/s。 注意,这里扩大了 1000000 倍

第 73 行,accel_scale_icm20608 是 ICM20608 的加速度计比例,也就是加速度计不同量程 对应的分辨率,计算方法和陀螺仪一样。这里扩大了 1000000000 倍。

第 164~177 行,icm20608_sensor_show 函数用于读取加速度计、陀螺仪、温度的原始数据 的。

第 186~08 行,icm20608_read_channel_data 函数用于读取指定通道的数据,根据 type 类型 的不同,给 icm20608_sensor_show 函数传递不同的参数,读取陀螺仪、加速度计或温度数据。

第 220~288 行,修改完善的 icm20608_read_raw 函数,我们就以读取陀螺仪、加速度计或 温度原始数据为例,当应用程序读取这几个通道的原始数据的时候第 229 行的分支会执行。

第 232 行直接调用 icm20608_read_channel_data 函数来根据通道的 type 值来读取 ICM20608 相应 的寄存器。其他分支内容大同小异,这些就是 ICM20608 寄存器读取内容了,这里就不在多讲。

修改完成以后重新编译驱动文件,然后加载新的驱动,测试是否可以正常读取到相应的内 容。我们读取一下 in_accel_scale 这个文件,这是加速度计的分辨率,我们默认设置了加速度计 量程为±16g,因此分辨率为 0.000488281。结果如图 75.3.3.1 所示:

从图 75.3.3.1 可以看出,此时 in_accel_scale 为 0.000488281,是正确的。这时候有朋友可能 会疑问,在示例代码 75.3.3.2 中,我们设置加速度计±16g 的分辨率为 4882了 1000000000 倍,为啥这里读出来的是 0.000488281 这个原始值?

大家注意看示例代码 75.3.3.2 中第 245~251 行所在的分支,这部分是读取加速度计的分辨率,读取完成以后在第 251 行返回 IIO_VAL_INT_PLUS_NANO 这个值,这里就是告诉用户空间,小数部分(val2)扩大了1000000000 倍 , 因 此 用 户 空 间 得 到 分 辨 率 以 后 会 除 以 1000000000 ,得到真实 的 分 辨 率 , 488281/1000000000=0.000488281,所以图 75.3.3.1 中得到的值就是 0.000488281。 我们在读取一下 in_accel_z_raw 这个文件,这个文件是加速度计的 Z 轴原始值,静态情况 下 Z 轴应该是 1g 的重力加速度计,我们可以读取 in_accel_z_raw 这个文件的值,然后在结合上 面读取到的加速度计分辨率,计算一下对应的 Z 轴重力值,看看是不是 1g 左右。81,也就是是扩大

2074×0.000488281≈1.01g,此时 Z 轴重力为 1g,结果正确。

3.4:完善 icm20608_write_raw 函数

最后我们完善icm20608_write_raw函数,用户空间向驱动写数据的时候icm20608_write_raw 函数会执行。我们可以在用户空间设置陀螺仪、加速度计的量程、校准值等,这时候就需要向 驱动写入数据。本章我们简单一点,就只实现设置陀螺仪和加速度计的量程,至于其他的设置 项大家可以自行实现,修改后的 icm20608_write_raw 函数和其他相关内容如下:

180 /*
181 * @description : 设置 ICM20608 传感器,可以用于陀螺仪、加速度计设置
182 * @param - dev : icm20608 设备
183 * @param - reg : 要设置的通道寄存器首地址。
184 * @param – anix : 要设置的通道,比如 X,Y,Z。
185 * @param - val : 要设置的值。
186 * @return : 0,成功;其他值,错误
187 */
188 static int icm20608_sensor_set(struct icm20608_dev *dev, int reg,
189 int axis, int val)
190 {
191 int ind, result;
192 __be16 d = cpu_to_be16(val);
193
194 ind = (axis - IIO_MOD_X) * 2;
195 result = regmap_bulk_write(dev->regmap, reg + ind, (u8 *)&d, 2);
196 if (result)
197 return -EINVAL;
198
199 return 0;
200 }
......
256 /*
257 * @description : 设置 ICM20608 的陀螺仪计量程(分辨率)
258 * @param - dev : icm20608 设备
259 * @param - val : 量程(分辨率值)。
260 * @return : 0,成功;其他值,错误
261 */
262 static int icm20608_write_gyro_scale(struct icm20608_dev *dev,
int val)
263 {
264 int result, i;
265 u8 d;
266
267 for (i = 0; i < ARRAY_SIZE(gyro_scale_icm20608); ++i) {
268 if (gyro_scale_icm20608[i] == val) {
269 d = (i << 3);
270 result = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);
271 if (result)
272 return result;
273 return 0;
274 }
275 }
276 return -EINVAL;
277 }
278
279 /*
280 * @description : 设置 ICM20608 的加速度计量程(分辨率)
281 * @param - dev : icm20608 设备
282 * @param - val : 量程(分辨率值)。
283 * @return : 0,成功;其他值,错误
284 */
285 static int icm20608_write_accel_scale(struct icm20608_dev *dev,
int val)
286 {
287 int result, i;
288 u8 d;
289
290 for (i = 0; i < ARRAY_SIZE(accel_scale_icm20608); ++i) {
291 if (accel_scale_icm20608[i] == val) {
292 d = (i << 3);
293 result = regmap_write(dev->regmap,
ICM20_ACCEL_CONFIG, d);
294 if (result)
295 return result;
296 return 0;
297 }
298 }
299 return -EINVAL;
300 }
......
382 /*
383 * @description : 写函数,当向 sysfs 中的文件写数据的时候最终此函数会执行,
384 * :一般在此函数里面设置传感器,比如量程等。
385 * @param - indio_dev : iio_dev
386 * @param - chan : 通道
387 * @param - val : 应用程序写入的值,如果是小数值的话,val 是整数部分。
388 * @param - val2 : 应用程序写入的值,如果是小数值的话,val2 是小数部分。
389 * @return : 0,成功;其他值,错误
390 */
391 static int icm20608_write_raw(struct iio_dev *indio_dev,
392 struct iio_chan_spec const *chan,
393 int val, int val2, long mask)
394 {
395 struct icm20608_dev *dev = iio_priv(indio_dev);
396 int ret = 0;
397
398 iio_device_claim_direct_mode(indio_dev); 
399 switch (mask) {
400 case IIO_CHAN_INFO_SCALE: /* 设置陀螺仪和加速度计的分辨率 */
401 switch (chan->type) {
402 case IIO_ANGL_VEL: /* 设置陀螺仪 */
403 mutex_lock(&dev->lock);
404 ret = icm20608_write_gyro_scale(dev, val2);
405 mutex_unlock(&dev->lock);
406 break;
407 case IIO_ACCEL: /* 设置加速度计 */
408 mutex_lock(&dev->lock);
409 ret = icm20608_write_accel_scale(dev, val2);
410 mutex_unlock(&dev->lock);
411 break;
412 default:
413 ret = -EINVAL;
414 break;
415 }
416 break;
417 case IIO_CHAN_INFO_CALIBBIAS: /* 设置陀螺仪和加速度计的校准值 */
418 switch (chan->type) {
419 case IIO_ANGL_VEL: /* 设置陀螺仪校准值 */
420 mutex_lock(&dev->lock);
421 ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,
422 chan->channel2, val);
423 mutex_unlock(&dev->lock);
424 break;
425 case IIO_ACCEL: /* 加速度计校准值 */
426 mutex_lock(&dev->lock);
427 ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,
428 chan->channel2, val);
429 mutex_unlock(&dev->lock);
430 break;
431 default:
432 ret = -EINVAL;
433 break;
434 }
435 break;
436 default:
437 ret = -EINVAL;
438 break;
439 }
440
441 iio_device_release_direct_mode(indio_dev); 
442 return ret;
443 }
444
445 /*
446 * @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传
447 * :感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间
448 * : 应该扩大多少倍,此函数就是用来设置这个的。
449 * @param - indio_dev : iio_dev
450 * @param - chan : 通道
451 * @param - mask : 掩码
452 * @return : 0,成功;其他值,错误
453 */
454 static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
455 struct iio_chan_spec const *chan, long mask)
456 {
457 switch (mask) {
458 case IIO_CHAN_INFO_SCALE:
459 switch (chan->type) {
460 case IIO_ANGL_VEL: 
461 return IIO_VAL_INT_PLUS_MICRO;
462 default: 
463 return IIO_VAL_INT_PLUS_NANO;
464 }
465 default:
466 return IIO_VAL_INT_PLUS_MICRO;
467 }
468 return -EINVAL;
469 }
470
471 /*
472 * iio_info 结构体变量
473 */
474 static const struct iio_info icm20608_info = {
475 .read_raw = icm20608_read_raw,
476 .write_raw = icm20608_write_raw,
477 .write_raw_get_fmt = &icm20608_write_raw_get_fmt, 
478 };

第 188~200 行,icm20608_sensor_set 函数用于设置指定通道,也就是向 ICM20608 的指定 寄存器写入数据,用于设置陀螺仪和加速度计的校准值。

第 262~277 行,icm20608_write_gyro_scale 函数用于设置 ICM20608 陀螺仪量程。

第 285~300 行,icm20608_write_accel_scale 函数用于设置 ICM20608 加速度计量程

第 391~443 行,icm20608_write_raw 函数,也就是 iio_info 的 write 函数,用户空间向驱动 程序写入数据以后此函数就会执行,这里我们用来配置 ICM20608 的陀螺仪和加速度量程和校 准值。

第 400 行的 IIO_CHAN_INFO_SCALE 表示此分支是设置比例(量程的),

第 404 行设置陀 螺仪的量程,

第 409 行设置加速度计的量程。

第 417 行的 IIO_CHAN_INFO_CALIBBIAS 表示 此分支是设置校准值的,

第 421 行设置陀螺仪各轴校准值,

第 427 行设置加速度计各轴校准值。

第 454~469 行,icm20608_write_raw_get_fmt 函数,也就是 iio_info 的 write_raw_get_fmt 函 数。此函数用来指定用户空间写入的数据格式,

第 458 行的 IIO_CHAN_INFO_SCALE 分支表 示要操作的是比例,也就是用户空间要设置陀螺仪和加速度计的量程。

第 460 行表示如果写入 的是陀螺仪比例,那么小数部分就要扩大 1000000 倍。

第 463 行表示如果是其他的就扩 1000000000 倍,比如加速度计。比如我们在用户空间要设置加速度计量程为±4g,只需要向 in_accel_scale 写 入 0.000122070 , 那 么 最 终 传 入 到 驱 动 里 面 的 就 是 0.000122070*1000000000=122070。 ICM20608 的 IIO 驱动就已经编写完成了,接下来就是编写测试 APP。

4:测试应用程序编写

4.1:linux 文件流读取

前面我们都是直接使用 cat 命令读取对应文件的内容,如果要连续不断的读取传感器数据 就不能用 cat 命令了,我们需要编写对应的 APP 软件,在编写 APP 之前我们先了解一下所要用 到的 API 函数。 首先我们要知道,前面使用 cat 命令读取到的文件内容字符串,虽然看起来像是数字。比 如我们使用 cat 命令读取到的 in_accel_scale 如图 75.4.1 所示:

 图 75.4.1 中 in_accel_scale 文件内容为 0.000488281,但是这里的 0.000488281 是字符串, 并不是具体的数字,所以我们需要将其转换为对应的数字。另外 in_accel_scale 是流文件,也叫 做标准文件 I/O 流,因此打开、读写操作要使用文件流操作函数。

4.1.1:打开文件流

打开文件流使用 fopen 函数,函数原型如下:

FILE *fopen(const char *pathname, const char *mode)

函数参数和返回值含义如下:

pathname:需要打开的文件流路径。

mode:打开方式,可选的打开方式如表 75.4.1 所示:

 返回值:NULL,打开错误;其他值,打开成功的文件流指针,为 FILE 类型。

4.1.2:关闭文件流

关闭文件流使用函数 fclose,函数原型如下:

int fclose(FILE *stream)

函数参数和返回值含义如下:

stream:要关闭的文件流指针。

返回值:0,关闭成功;EOF,关闭错误。

4.1.3:读取文件流

要读取文件流使用 fread 函数,函数原型如下:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

fread 函数用于从给定的输入流中读取最多 nmemb 个对象到数组 ptr 中,函数参数和返回值 含义如下:

ptr:要读取的数组中首个对象的指针。

size:每个对象的大小。

nmemb:要读取的对象个数。

stream:要读取的文件流。

返回值:返回读取成功的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值 (或者 0)。

4.1.4:写文件流

要向文件流写入数据,使用 fwrite 函数,函数原型如下

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite 函数用于向给定的文件流中写入最多 nmemb 个对象,函数参数和返回值含义如下:

ptr:要写入的数组中首个对象的指针。

size:每个对象的大小。

nmemb:要写入的对象个数。

stream:要写入的文件流。

返回值:返回成功写入的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值 (或者 0)。

4.1.5:格式化输入文件流

fscanf 函数用于从一个文件流中格式化读取数据,fscanf 函数在遇到空格和换行符的时候 就会结束。前面我们说了 IIO 框架下的 sysfs 文件内容都是字符串,比如 in_accel_scale 文件内 容为“0.000488281”,这是一串字符串,并不是具体的数字,因此我们在读取的时候就需要使 用字符串读取格式。在这里就可以使用 fscanf 函数来格式化读取文件内容,函数原型如下:

int fscanf(FILE *stream, const char *format, ,[argument...])

fscanf 用法和 scanf 类似,函数参数和返回值含义如下: stream:要操作的文件流。 format:格式。 argument:保存读取到的数据。 返回值:成功读取到的数据个数,如果读到文件末尾或者读取错误就返回 EOF。

4.2:编写测试 APP

新建名为 icm20608App.c 的文件,输入如下内容:

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608.c
作者 : 左忠凯
版本 : V1.0
描述 : icm20608 设备 iio 框架测试程序。
其他 : 无
使用方法 :./icm20608App 
论坛 : www.openedv.com
日志 : 初版 V1.0 2021/8/17 左忠凯创建
***************************************************************/
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 
16 /* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
17 #define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
18 ret = file_data_read(file_path[index], str);\
19 dev->member = atof(str);\
20 
21 /* 字符串转数字,将整数字符串转换为整数数值 */
22 #define SENSOR_INT_DATA_GET(ret, index, str, member)\
23 ret = file_data_read(file_path[index], str);\
24 dev->member = atoi(str);\
25 
26 /* icm20608 iio 框架对应的文件路径 */
27 static char *file_path[] = {
28 "/sys/bus/iio/devices/iio:device1/in_accel_scale",
29 "/sys/bus/iio/devices/iio:device1/in_accel_x_calibbias",
30 "/sys/bus/iio/devices/iio:device1/in_accel_x_raw",
31 "/sys/bus/iio/devices/iio:device1/in_accel_y_calibbias",
32 "/sys/bus/iio/devices/iio:device1/in_accel_y_raw",
33 "/sys/bus/iio/devices/iio:device1/in_accel_z_calibbias",
34 "/sys/bus/iio/devices/iio:device1/in_accel_z_raw",
35 "/sys/bus/iio/devices/iio:device1/in_anglvel_scale",
36 "/sys/bus/iio/devices/iio:device1/in_anglvel_x_calibbias",
37 "/sys/bus/iio/devices/iio:device1/in_anglvel_x_raw",
38 "/sys/bus/iio/devices/iio:device1/in_anglvel_y_calibbias",
39 "/sys/bus/iio/devices/iio:device1/in_anglvel_y_raw",
40 "/sys/bus/iio/devices/iio:device1/in_anglvel_z_calibbias",
41 "/sys/bus/iio/devices/iio:device1/in_anglvel_z_raw",
42 "/sys/bus/iio/devices/iio:device1/in_temp_offset",
43 "/sys/bus/iio/devices/iio:device1/in_temp_raw",
44 "/sys/bus/iio/devices/iio:device1/in_temp_scale",
45 };
46 
47 /* 文件路径索引,要和 file_path 里面的文件顺序对应 */
48 enum path_index {
49 IN_ACCEL_SCALE = 0,
50 IN_ACCEL_X_CALIBBIAS,
51 IN_ACCEL_X_RAW,
52 IN_ACCEL_Y_CALIBBIAS,
53 IN_ACCEL_Y_RAW,
54 IN_ACCEL_Z_CALIBBIAS,
55 IN_ACCEL_Z_RAW,
56 IN_ANGLVEL_SCALE,
57 IN_ANGLVEL_X_CALIBBIAS,
58 IN_ANGLVEL_X_RAW,
59 IN_ANGLVEL_Y_CALIBBIAS,
60 IN_ANGLVEL_Y_RAW,
61 IN_ANGLVEL_Z_CALIBBIAS,
62 IN_ANGLVEL_Z_RAW,
63 IN_TEMP_OFFSET,
64 IN_TEMP_RAW,
65 IN_TEMP_SCALE,
66 };
67 
68 /*
69 * icm20608 数据设备结构体
70 */
71 struct icm20608_dev{
72 int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;
73 int accel_x_raw, accel_y_raw, accel_z_raw;
74 
75 int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;
76 int gyro_x_raw, gyro_y_raw, gyro_z_raw;
77 
78 int temp_offset, temp_raw;
79 
80 float accel_scale, gyro_scale, temp_scale;
81 
82 float gyro_x_act, gyro_y_act, gyro_z_act;
83 float accel_x_act, accel_y_act, accel_z_act;
84 float temp_act;
85 };
86 
87 struct icm20608_dev icm20608;
88 
89 /*
90 * @description : 读取指定文件内容
91 * @param – filename : 要读取的文件路径
92 * @param - str : 读取到的文件字符串
93 * @return : 0 成功;其他 失败
94 */
95 static int file_data_read(char *filename, char *str)
96 {
97 int ret = 0;
98 FILE *data_stream;
99 
100 data_stream = fopen(filename, "r"); /* 只读打开 */
101 if(data_stream == NULL) {
102 printf("can't open file %s\r\n", filename);
103 return -1;
104 }
105
106 ret = fscanf(data_stream, "%s", str);
107 if(!ret) {
108 printf("file read error!\r\n");
109 } else if(ret == EOF) {
110 /* 读到文件末尾的话将文件指针重新调整到文件头 */
111 fseek(data_stream, 0, SEEK_SET); 
112 }
113 fclose(data_stream); /* 关闭文件 */ 
114 return 0;
115 }
116
117 /*
118 * @description : 获取 ICM20608 数据
119 * @param - dev : 设备结构体
120 * @return : 0 成功;其他 失败
121 */
122 static int sensor_read(struct icm20608_dev *dev)
123 {
124 int ret = 0;
125 char str[50];
126
127 /* 1、获取陀螺仪原始数据 */
128 SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);
129 SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);
130 SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);
131 SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);
132
133 /* 2、获取加速度计原始数据 */
134 SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);
135 SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);
136 SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);
137 SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);
138
139 /* 3、获取温度值 */
140 SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);
141 SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);
142 SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);
143
144 /* 3、转换为实际数值 */
145 dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;
146 dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;
147 dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;
148
149 dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;
150 dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;
151 dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;
152
153 dev->temp_act = ((dev->temp_raw - dev->temp_offset) /
dev->temp_scale) + 25;
154 return ret;
155 }
156
157 /*
158 * @description : main 主程序
159 * @param – argc : argv 数组元素个数
160 * @param – argv : 具体参数
161 * @return : 0 成功;其他 失败
162 */
163 int main(int argc, char *argv[])
164 {
165 int ret = 0;
166
167 if (argc != 1) {
168 printf("Error Usage!\r\n");
169 return -1;
170 }
171
172 while (1) {
173 ret = sensor_read(&icm20608);
174 if(ret == 0) { /* 数据读取成功 */
175 printf("\r\n 原始值:\r\n");
176 printf("gx = %d, gy = %d, gz = %d\r\n",
icm20608.gyro_x_raw,
icm20608.gyro_y_raw,
icm20608.gyro_z_raw);
177 printf("ax = %d, ay = %d, az = %d\r\n",
icm20608.accel_x_raw,
icm20608.accel_y_raw,
icm20608.accel_z_raw);
178 printf("temp = %d\r\n", icm20608.temp_raw);
179 printf("实际值:");
180 printf("act gx = %.2f°/S, act gy = %.2f°/S, 
act gz = %.2f°/S\r\n", icm20608.gyro_x_act,
icm20608.gyro_y_act, icm20608.gyro_z_act);
181 printf("act ax = %.2fg, act ay = %.2fg, 
act az = %.2fg\r\n", icm20608.accel_x_act,
icm20608.accel_y_act,
icm20608.accel_z_act);
182 printf("act temp = %.2f°C\r\n", icm20608.temp_act);
183 }
184 usleep(100000); /*100ms */
185 }
186
187 return 0;
188 }

第 17~19 行,SENSOR_FLOAT_DATA_GET 宏用读取指定路径的文件内容,然后将读到 的浮点型字符串数据转换为具体的浮点数据。

第 19 行使用 atof 函数将浮点字符串转换为具体 的浮点数值。

第 22~24 行,SENSOR_INT_DATA_GET 宏用于读取指定路径的文件内容,将读取到的整 数型字符串数据转换为具体的整数值,

第 24 行使用 atoi 函数将整数字符串转换为具体的整数 数值。 atof 和 atoi 这两个函数是标准的 C 库函数,并不需要我们自己编写,添加“stdlib.h”头文 件就可以直接调用。这两个函数都只有一个参数,就是要转换的字符串。返回值就是转换成功 以后字符串对应的数值。

第 27~45 行,需要操作的文件路径。

第 48~66 行,文件路径索引,顺序要和 file_path 里面的文件路径对应。

第 71~85 行,icm20608_dev 结构体为自定义设备结构体,

用于保存 ICM20608 传感器数 据。

第 95~115 行,file_data_read 函数用于读取指定文件,第一个参数 filename 是要读取的文 件路径。第二个参数 str 为读取到的文件内容,为字符串类型,因为本章例程所读取的文件内 容都是字符串。

第 100 行调用 fopen 函数打开指定的文件流,

第 106 行调用 fscanf 函数进行格 式化读取,也就是按照字符串方式读取文件,文件内容保存到 str 参数里面。当读取到文件末 尾的时候,

第 111 行调用 fseek 函数将读取指针调整到文件头,以备下次重新读取。

第 113 行 调用 fclose 函数关闭对应的文件流。

第 122~155 行,sensor_read 函数用于读取 ICM20608 传感器数据,包括陀螺仪、加速度和 温度计的原始值,还有加速度计和陀螺仪的分辨率等,最后将获取到的原始值转换为具体的数 值。

第 163~188 行,main 函数,在 while 循环中调用 sensor_read 函数读取 ICM20608 数据, 并将读到的数据打印出来。

4.3:运行测试

输入如下命令编译测试 icm20608App.c 这个测试程序:

arm-linux-gnueabihf-gcc icm20608App.c -o icm20608App

编译成功以后就会生成 icm20608App 这个应用程序。 将 icm20608.ko 和 icm20608App 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,重启 开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 icm20608.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动

驱动加载成功以后使用如下命令来测试:

/icm20608App

如果驱动和 APP 工作正常的话就会不断的打印出 ICM20608 数据,包括陀螺仪和加速度计 的原始数据和转换后的实际数值、温度等,如图 75.4.3.1 所示:

另外,我们也编写 AP3216C 这个光传感器的对应的 IIO 驱动,但是由于 AP3216C 没有数 据准备就绪中断,因此 AP3216C 的 IIO 驱动没有实现触发缓冲功能。AP3216C 的 IIO 驱动程序 已经放到了开发板光盘中,路径为:开发板光盘->01、程序源码->02、Linux 驱动例程->27_iio->iic。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1995285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】关于栈你必须知道的内部原理!!!

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;小编这期将带来关于栈的相关知识&#xff0c;以及代码的实现&#xff0c;希望能够帮到屏幕前的你。 &#x1f4da;️上期双链表博客在这里哟&#xff1a;http://t.csdnimg.cn/xgjK1&#xff1b; &#x1f4…

预测云计算的未来

云计算的未来是否依旧未知&#xff1f; 直到最近&#xff0c;云计算还是软件编程界之外很少有人熟悉的一个概念。如今&#xff0c;云计算已成为一切的基础&#xff0c;从点播电视服务和在线游戏门户网站到电子邮件和社交媒体&#xff0c;这四大现代世界的基石。云计算将继续存在…

Electron 使用Electron-build 进行打包

看完下面两篇就可以完成&#xff01; 基于vue3vite的web项目改为Electron桌面应用&#xff08;一&#xff09;_vue3转electron-CSDN博客 将web项目打包成electron桌面端教程&#xff08;二&#xff09;vue3vitets_vue3 打包桌面端-CSDN博客 打包报错 1. 首先确定依赖包 npm …

合金钢旋转花键:高强度与高耐磨性的选择

旋转花键‌是一种利用装在花键外筒内的滚珠&#xff0c;‌在精密研磨的滚动沟槽中作平滑滚动&#xff0c;‌同时传递力矩的直线运动系统。需要在振动冲击负荷作用过大、‌定位精度要求高的地方&#xff0c;‌以及需要高速运动性能的地方发挥重要作用。 旋转花键的性能跟材质有直…

龙迅#LT86102SXE适用于HDMI转两路HDMI应用方案,分辨率高达4K60HZ,提供技术支持!

1.功能 ⚫HDMI1.4和DVI 1.0规范兼容 ⚫支持3D视频格式和4Kx2K扩展分辨率格式高达3.4Gbps数据速率和4Kx2K 60Hz YCbCr 4&#xff1a;2&#xff1a;0格式 ⚫自适应均衡和失重&#xff0c;以补偿长电缆损耗 ⚫ODTs和校准 ⚫集成HDCP中继器引擎符合HDCP 1.4规范 ⚫完全硬件控制或可…

CSS文本两端对齐

背景 如果我们要写了列表或表单类的样式&#xff0c;名称长短不一&#xff0c;但是想要两端对齐&#xff0c;如下面这样的&#xff1a; 你是怎么写的&#xff1f; 是这样的吗&#xff0c;在HTML里调整加空格&#xff1a; <ul><li>用户名</li><li>账 …

探索算法系列 - 前缀和算法

目录 一维前缀和&#xff08;原题链接&#xff09; 二维前缀和&#xff08;原题链接&#xff09; 寻找数组的中心下标&#xff08;原题链接&#xff09; 除自身以外数组的乘积&#xff08;原题链接&#xff09; 和为 K 的子数组&#xff08;原题链接&#xff09; 和可被 …

电脑开机后出现bootmgr is missing原因及解决方法

最近有网友问我为什么我电脑开机后出现bootmgr is missing&#xff0c;这个提示意思是:意思是启动管理器丢失&#xff0c;说明bootmgr损坏或者丢失&#xff0c;系统无法读取到这个必要的启动信息导致无法启动。原因有很多&#xff0c;比如我们采用的是uefi引导&#xff0c;而第…

【数学建模】简单的优化模型-6 血管分支

背景&#xff1a;机体提供能量维持血液在血管中的流动&#xff0c;给血管壁以营养&#xff1b;克服血液流动的阻力&#xff0c;能量消耗与血管的几何形状有关。在长期进化中动物血管的几何形状已经在能量消耗最小原则下达到最优化。 问题&#xff1a;在能量消耗最小原则下&…

学习!胖东来秋季陈列欣赏

生鲜区 丰满、新鲜、有量感&#xff1b;色彩搭配更显艺术美&#xff01; 每个石榴专门被泡沫网套半包裹&#xff0c;避免果体受伤的同时&#xff0c;方便顾客挑选。 非季节性商品&#xff0c;以精而美的陈列形式呈现。 蔬菜区的商品分层立体陈列&#xff0c;顾客既拿既走&…

【实现100个unity特效之16】unity2022之前或者之后版本实现全屏shader graph的不同方式 —— 适用于人物受伤红屏或者一些其他状态效果

最终效果 文章目录 最终效果前言unity2022版本 Fullscreen shader graph首先&#xff0c;请注意你的Inity版本&#xff0c;是不是2022.2以上&#xff0c;并且项目是URP项且基本配置 修改shader graph边缘效果动起来优化科幻风制作一些变量最终效果最终节点图代码控制 2022之前版…

鸿蒙(API 12 Beta3版)【音频解码】

开发者可以调用本模块的Native API接口&#xff0c;完成音频解码&#xff0c;即将媒体数据解码为PCM码流。 当前支持的解码能力如下: 容器规格音频解码类型mp4AAC、MPEG(MP3)、Flac、Vorbis、AudioViVid11m4aAACflacFlacoggVorbis、opusaacAACmp3MPEG(MP3)amrAMR(amrnb、amrw…

Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC]

文章目录 Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章内的相关技术…

线程池原理(一)线程池核心概述

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 线程回顾 创建线程的方式 继承 Thread 类实现 Runnable 接口 创建后的线程有如下状态&#xff1a; NEW&#xff1a;新建的线程&#xff0c;无任何操作 public static void main(String[] args) {Thread…

嵌入式初学-C语言-十八

#接嵌入式初学-C语言-十七# 变量的生命周期 1. 概念&#xff1a;变量在程序运行中存在的时间 2. 根据变量存在的时间不同&#xff0c;变量可分为静态存储和动态存储 3. 变量的存储类型 变量的完整定义格式&#xff1a;[存储类型] 数据类型 变量列表; 4. 存储类型 auto&…

yolov8人脸识别案例

GitHub - wangWEI201901/YOLOv8-Detection-Project: &#x1f6e3;️基于YOLOv8的智慧校园人脸识别和公路汽车检测

ITSM垂类下,企业如何逐步搭建一个好的AI Agent

随着企业数字化转型的不断深入&#xff0c;智能服务管理&#xff08;ITSM&#xff09;逐渐成为提升企业运营效率和服务质量的关键。企业纷纷探索如何将AI技术融入到IT服务管理中&#xff0c;以提升效率、降低成本并增强用户体验。成功实施AI并非易事&#xff0c;它要求企业在战…

探索全光网技术 | 全光网络技术方案选型建议五 (大厅场景)

目录 一、场景设计需求二、大厅场景拓扑三、部署方式四、产品相关规格说明五、方案优势与特点 注&#xff1a;本文章参考资料为&#xff1a;华三官方资料 - “新华三全光网络3.0解决方案&#xff08;教育&#xff09;”与 锐捷官方资料 - “【锐捷】高校极简以太全光3.X方案设计…

web基础与http协议与配置

目录 一、web基础 1.1 DNS与域名&#xff08;详解看前面章节&#xff09; 1.2 网页的概念&#xff08;HTTP/HTTPS&#xff09; 1.2.1 基本概念 1.2.2 HTML文档结构(了解) 1.2.3 web相关重点 1.2.4 静态资源和动态资源 二、http协议 2.1 概述 2.2 cookie和session&…

【HarmonyOS NEXT星河版开发学习】小型测试案例10-计数器案例

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 前言 鸿蒙开发中的点击事件是一个基础且重要的功能&#xff0c;它使得应用能够响应用户的各种触摸操作。通过对点击事件及其相关参数的深入…