家居网站开发项目计划书,互联网公司市值,什么是网络营销网络营销的目的有哪些内容,h5手机端网站开发工业场合里面也有大量的模拟量和数字量之间的转换#xff0c;也就是我们常说的 ADC 和 DAC。而且随着手机、物联网、工业物联网和可穿戴设备的爆发#xff0c;传感器的需求只持续增强。比如手机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等#xff0c;这些传…工业场合里面也有大量的模拟量和数字量之间的转换也就是我们常说的 ADC 和 DAC。而且随着手机、物联网、工业物联网和可穿戴设备的爆发传感器的需求只持续增强。比如手机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等这些传感器本质上都是ADC大家注意查看这些传感器的手册会发现他们内部都会有个 ADC传感器对外提供 IIC或者 SPI 接口 SOC 可以通过 IIC 或者 SPI 接口来获取到传感器内部的 ADC 数值从而得到想要测量的结果。Linux 内核为了管理这些日益增多的 ADC 类传感器特地推出了 IIO 子系统本章我们就来学习如何使用 IIO 子系统来编写 ADC 类传感器驱动。
IIO 子系统简介
IIO 全称是 Industrial I/O翻译过来就是工业 I/O大家不要看到“工业”两个字就觉得 IIO是只用于工业领域的。大家一般在搜索 IIO 子系统的时候会发现大多数讲的都是 ADC这是因为 IIO 就是为 ADC 类传感器准备的当然了 DAC 也是可以的。大家常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC内部 ADC 将原始的模拟数据转换为数字量然后通过其他的通信接口比如 IIC、 SPI 等传输给 SOC。因此当你使用的传感器本质是 ADC 或 DAC 器件的时候可以优先考虑使用 IIO 驱动框架。
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 为设备支持的模式可选择的模如表所示
模式描述INDIO_DIRECT_MODE提供 sysfs 接口。INDIO_BUFFER_TRIGGERED支持硬件缓冲触发。INDIO_BUFFER_SOFTWARE支持软件缓冲触发。INDIO_BUFFER_HARDWARE支持硬件缓冲区。
第 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。 继续回到示例代码中第 505 行 chrdev 为字符设备由 IIO 内核创建。
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 的释放工作。
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成功其他值失败。
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。因此val1 val22360。扩大的倍数我们不能随便设置而是要使用 Linux 定义的倍数 Linux 内核里面定义的数据扩大倍数或者说数据组合形式如表所示
组合宏描述IIO_VAL_INT整数值没有小数。比如 5000那么就是 val5000不 需要设置 val2IIO_VAL_INT_PLUS_MICRO小数部分扩大 1000000 倍比如 1.00236此时 val1 val22360。IIO_VAL_INT_PLUS_NANO小数部分扩大 1000000000 倍同样是 1.00236此时 val1 val22360000。IIO_VAL_INT_PLUS_MICRO_DBdB 数据和 IIO_VAL_INT_PLUS_MICRO 数据形式一 样只是在后面添加 db。IIO_VAL_INT_MULTIPLE多个整数值比如一次要传回 6 个整数值那么 val 和 val2就不够用了。此宏主要用于iio_info的read_raw_multi 函数。IIO_VAL_FRACTIONAL分数值也就是 val/val2。比如 val1 val24那么实际 值就是 1/4。IIO_VAL_FRACTIONAL_LOG2值为 valval2也就是 val 右移 val2 位。比如 val25600 val24 那 么 真 正 的 值 就 是 25600 右 移 4 位 2560041600.
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 的意义也就是表中的组合形式。比如我们需要在应用程序中设置 ICM20608 加速度计的量程为± 8g那么分辨率就是16/65536 ≈ 0.000244 我们 在 write_raw_get_fmt 函数 里面设置 加速度计的数 据格式 为IIO_VAL_INT_PLUS_MICRO。那么我们在应用程序里面向指定的文件写入 0.000244 以后最终传递给内核驱动的就是 0.000244*1000000244。也就是 write_raw 函数的 val 参数为 0 val2参数为 244。
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 };
从上述类型可以看出目前 Linux 内核支持的传感器类型非常丰富而且支持类型也会不断的增加。如果是 ADC那就是 IIO_VOLTAGE 类型。如果是 ICM20608 这样的多轴传感器那么就是复合类型了陀螺仪部分是 IIO_ANGL_VEL 类型加速度计部分是IIO_ACCEL 类型温度部分就是 IIO_TEMP。 继续来看 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 下通道文件名字组成形式。 继续回到iio_chan_spec 第 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 表示为差分通道。
IIO 驱动框架创建
前面我们已经对 IIO 设备、 IIO 通道进行了详细的讲解本节我们就来学习如何搭建 IIO 驱动框架。在上一小节分析 IIO 子系统的时候大家应该看出了 IIO 框架主要用于 ADC 类的传感器比如陀螺仪、加速度计、磁力计、光强度计等这些传感器基本都是 IIC 或者 SPI 接口的。因此 IIO 驱动的基础框架就是 IIC 或者 SPI我们可以在 IIC 或 SPI 驱动里面在加上上一章讲解的 regmap。当然了有些 SOC 内部的 ADC 也会使用 IIO 框架那么这个时候驱动的基础框架就是 platfrom。
我们以 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);
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 驱动实战
实验
驱动代码
#include linux/spi/spi.h
#include linux/kernel.h
#include linux/module.h
#include linux/init.h
#include linux/delay.h
#include linux/ide.h
#include linux/errno.h
#include linux/platform_device.h
#include icm20608reg.h
#include linux/gpio.h
#include linux/device.h
#include asm/uaccess.h
#include linux/cdev.h
#include linux/regmap.h
#include linux/iio/iio.h
#include linux/iio/sysfs.h
#include linux/iio/buffer.h
#include linux/iio/trigger.h
#include linux/iio/triggered_buffer.h
#include linux/iio/trigger_consumer.h
#include linux/unaligned/be_byteshift.h#define ICM20608_NAME icm20608
#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE 326800000#define ICM20608_CHAN(_type, _channel2, _index) \{ \.type _type, \.modified 1, \.channel2 _channel2, \.info_mask_shared_by_type BIT(IIO_CHAN_INFO_SCALE), \.info_mask_separate BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_CALIBBIAS), \.scan_index _index, \.scan_type { \.sign s, \.realbits 16, \.storagebits 16, \.shift 0, \.endianness IIO_BE, \}, \}/* * ICM20608的扫描元素3轴加速度计、* 3轴陀螺仪、1路温度传感器1路时间戳 */
enum inv_icm20608_scan {INV_ICM20608_SCAN_ACCL_X,INV_ICM20608_SCAN_ACCL_Y,INV_ICM20608_SCAN_ACCL_Z,INV_ICM20608_SCAN_TEMP,INV_ICM20608_SCAN_GYRO_X,INV_ICM20608_SCAN_GYRO_Y,INV_ICM20608_SCAN_GYRO_Z,INV_ICM20608_SCAN_TIMESTAMP,
};struct icm20608_dev {struct spi_device *spi; /* spi设备 */struct regmap *regmap; /* regmap */struct regmap_config regmap_config; struct mutex lock;
};/** icm20608陀螺仪分辨率对应250、500、1000、2000计算方法* 以正负250度量程为例500/2^160.007629扩大1000000倍就是7629*/
static const int gyro_scale_icm20608[] {7629, 15258, 30517, 61035};/* * icm20608加速度计分辨率对应2、4、8、16 计算方法* 以正负2g量程为例4/2^160.000061035扩大1000000000倍就是61035*/
static const int accel_scale_icm20608[] {61035, 122070, 244140, 488281};/** icm20608通道1路温度通道3路陀螺仪3路加速度计*/
static const struct iio_chan_spec icm20608_channels[] {/* 温度通道 */{.type IIO_TEMP,.info_mask_separate BIT(IIO_CHAN_INFO_RAW)| BIT(IIO_CHAN_INFO_OFFSET)| BIT(IIO_CHAN_INFO_SCALE),.scan_index INV_ICM20608_SCAN_TEMP,.scan_type {.sign s,.realbits 16,.storagebits 16,.shift 0,.endianness IIO_BE,},},ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X), /* 陀螺仪X轴 */ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y), /* 陀螺仪Y轴 */ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z), /* 陀螺仪Z轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), /* 加速度X轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), /* 加速度Y轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), /* 加速度Z轴 */
};/** description : 读取icm20608指定寄存器值读取一个寄存器* param - dev: icm20608设备* param - reg: 要读取的寄存器* return : 读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 ret;unsigned int data;ret regmap_read(dev-regmap, reg, data);return (u8)data;
}/** description : 向icm20608指定寄存器写入指定的值写一个寄存器* param - dev: icm20608设备* param - reg: 要写的寄存器* param - data: 要写入的值* return : 无*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{regmap_write(dev-regmap, reg, value);
}/** description : ICM20608内部寄存器初始化函数 * param - spi : 要操作的设备* return : 无*/
void icm20608_reginit(struct icm20608_dev *dev)
{u8 value 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value icm20608_read_onereg(dev, ICM20_WHO_AM_I);printk(ICM20608 ID %#X\r\n, value); icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW20Hz */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW21.2Hz */icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */icm20608_write_onereg(dev, ICM20_INT_ENABLE, 0x01); /* 使能FIFO溢出以及数据就绪中断 */
}/** description : 设置ICM20608传感器可以用于陀螺仪、加速度计设置* param - dev : icm20608设备 * param - reg : 要设置的通道寄存器首地址。* param - anix : 要设置的通道比如XYZ。* param - val : 要设置的值。* return : 0成功其他值错误*/
static int icm20608_sensor_set(struct icm20608_dev *dev, int reg,int axis, int val)
{int ind, result;__be16 d cpu_to_be16(val);ind (axis - IIO_MOD_X) * 2;result regmap_bulk_write(dev-regmap, reg ind, (u8 *)d, 2);if (result)return -EINVAL;return 0;
}/** description : 读取ICM20608传感器数据可以用于陀螺仪、加速度计、温度的读取* param - dev : icm20608设备 * param - reg : 要读取的通道寄存器首地址。* param - anix : 需要读取的通道比如XYZ。* param - val : 保存读取到的值。* return : 0成功其他值错误*/
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,int axis, int *val)
{int ind, result;__be16 d;ind (axis - IIO_MOD_X) * 2;result regmap_bulk_read(dev-regmap, reg ind, (u8 *)d, 2);if (result)return -EINVAL;*val (short)be16_to_cpup(d);return IIO_VAL_INT;
}/** description : 读取ICM20608陀螺仪、加速度计、温度通道值* param - indio_dev : iio设备 * param - chan : 通道。* param - val : 保存读取到的通道值。* return : 0成功其他值错误*/
static int icm20608_read_channel_data(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val)
{struct icm20608_dev *dev iio_priv(indio_dev);int ret 0;switch (chan-type) {case IIO_ANGL_VEL: /* 读取陀螺仪数据 */ret icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan-channel2, val); /* channel2为X、Y、Z轴 */break;case IIO_ACCEL: /* 读取加速度计数据 */ret icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan-channel2, val); /* channel2为X、Y、Z轴 */break;case IIO_TEMP: /* 读取温度 */ret icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val); break;default:ret -EINVAL;break;}return ret;
}/** description : 设置ICM20608的陀螺仪计量程(分辨率)* param - dev : icm20608设备* param - val : 量程(分辨率值)。* return : 0成功其他值错误*/
static int icm20608_write_gyro_scale(struct icm20608_dev *dev, int val)
{int result, i;u8 d;for (i 0; i ARRAY_SIZE(gyro_scale_icm20608); i) {if (gyro_scale_icm20608[i] val) {d (i 3);result regmap_write(dev-regmap, ICM20_GYRO_CONFIG, d);if (result)return result;return 0;}}return -EINVAL;
}/** description : 设置ICM20608的加速度计量程(分辨率)* param - dev : icm20608设备* param - val : 量程(分辨率值)。* return : 0成功其他值错误*/
static int icm20608_write_accel_scale(struct icm20608_dev *dev, int val)
{int result, i;u8 d;for (i 0; i ARRAY_SIZE(accel_scale_icm20608); i) {if (accel_scale_icm20608[i] val) {d (i 3);result regmap_write(dev-regmap, ICM20_ACCEL_CONFIG, d);if (result)return result;return 0;}}return -EINVAL;
}/** description : 读函数当读取sysfs中的文件的时候最终此函数会执行此函数* 里面会从传感器里面读取各种数据然后上传给应用。* param - indio_dev : iio_dev* param - chan : 通道* param - val : 读取的值如果是小数值的话val是整数部分。* param - val2 : 读取的值如果是小数值的话val2是小数部分。* param - mask : 掩码。* return : 0成功其他值错误*/
static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2, long mask)
{struct icm20608_dev *dev iio_priv(indio_dev);int ret 0;unsigned char regdata 0;switch (mask) {case IIO_CHAN_INFO_RAW: /* 读取ICM20608加速度计、陀螺仪、温度传感器原始值 */mutex_lock(dev-lock); /* 上锁 */ret icm20608_read_channel_data(indio_dev, chan, val); /* 读取通道值 */mutex_unlock(dev-lock); /* 释放锁 */return ret;case IIO_CHAN_INFO_SCALE:switch (chan-type) {case IIO_ANGL_VEL:mutex_lock(dev-lock);regdata (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG) 0X18) 3;*val 0;*val2 gyro_scale_icm20608[regdata];mutex_unlock(dev-lock);return IIO_VAL_INT_PLUS_MICRO; /* 值为valval2/1000000 */case IIO_ACCEL:mutex_lock(dev-lock);regdata (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG) 0X18) 3;*val 0;*val2 accel_scale_icm20608[regdata];;mutex_unlock(dev-lock);return IIO_VAL_INT_PLUS_NANO;/* 值为valval2/1000000000 */case IIO_TEMP: *val ICM20608_TEMP_SCALE/ 1000000;*val2 ICM20608_TEMP_SCALE % 1000000;return IIO_VAL_INT_PLUS_MICRO; /* 值为valval2/1000000 */default:return -EINVAL;}return ret;case IIO_CHAN_INFO_OFFSET: /* ICM20608温度传感器offset值 */switch (chan-type) {case IIO_TEMP:*val ICM20608_TEMP_OFFSET;return IIO_VAL_INT;default:return -EINVAL;}return ret;case IIO_CHAN_INFO_CALIBBIAS: /* ICM20608加速度计和陀螺仪校准值 */switch (chan-type) {case IIO_ANGL_VEL: /* 陀螺仪的校准值 */mutex_lock(dev-lock);ret icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan-channel2, val);mutex_unlock(dev-lock);return ret;case IIO_ACCEL: /* 加速度计的校准值 */mutex_lock(dev-lock); ret icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan-channel2, val);mutex_unlock(dev-lock);return ret;default:return -EINVAL;}default:return ret -EINVAL;}
} /** description : 写函数当向sysfs中的文件写数据的时候最终此函数会执行一般在此函数* 里面设置传感器比如量程等。* param - indio_dev : iio_dev* param - chan : 通道* param - val : 应用程序写入的值如果是小数值的话val是整数部分。* param - val2 : 应用程序写入的值如果是小数值的话val2是小数部分。* return : 0成功其他值错误*/
static int icm20608_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val, int val2, long mask)
{struct icm20608_dev *dev iio_priv(indio_dev);int ret 0;switch (mask) {case IIO_CHAN_INFO_SCALE: /* 设置陀螺仪和加速度计的分辨率 */switch (chan-type) {case IIO_ANGL_VEL: /* 设置陀螺仪 */mutex_lock(dev-lock);ret icm20608_write_gyro_scale(dev, val2);mutex_unlock(dev-lock);break;case IIO_ACCEL: /* 设置加速度计 */mutex_lock(dev-lock);ret icm20608_write_accel_scale(dev, val2);mutex_unlock(dev-lock);break;default:ret -EINVAL;break;}break;case IIO_CHAN_INFO_CALIBBIAS: /* 设置陀螺仪和加速度计的校准值*/switch (chan-type) {case IIO_ANGL_VEL: /* 设置陀螺仪校准值 */mutex_lock(dev-lock);ret icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,chan-channel2, val);mutex_unlock(dev-lock);break;case IIO_ACCEL: /* 加速度计校准值 */mutex_lock(dev-lock);ret icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,chan-channel2, val);mutex_unlock(dev-lock);break;default:ret -EINVAL;break;}break;default:ret -EINVAL;break;}return ret;
}/** description : 用户空间写数据格式比如我们在用户空间操作sysfs来设置传感器的分辨率* 如果分辨率带小数那么这个小数传递到内核空间应该扩大多少倍此函数就是* : 用来设置这个的。* param - indio_dev : iio_dev* param - chan : 通道* param - mask : 掩码* return : 0成功其他值错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, long mask)
{switch (mask) {case IIO_CHAN_INFO_SCALE:switch (chan-type) {case IIO_ANGL_VEL: /* 用户空间写的陀螺仪分辨率数据要乘以1000000 */return IIO_VAL_INT_PLUS_MICRO;default: /* 用户空间写的加速度计分辨率数据要乘以1000000000 */return IIO_VAL_INT_PLUS_NANO;}default:return IIO_VAL_INT_PLUS_MICRO;}return -EINVAL;
}/** iio_info结构体变量*/
static const struct iio_info icm20608_info {.read_raw icm20608_read_raw,.write_raw icm20608_write_raw,.write_raw_get_fmt icm20608_write_raw_get_fmt, /* 用户空间写数据格式 */
};/** description : spi驱动的probe函数当驱动与* 设备匹配以后此函数就会执行* param - spi : spi设备* return : 0,成功其他值失败*/
static int icm20608_probe(struct spi_device *spi)
{int ret;struct icm20608_dev *dev;struct iio_dev *indio_dev;/* 1、申请iio_dev内存 */indio_dev devm_iio_device_alloc(spi-dev, sizeof(*dev));if (!indio_dev)return -ENOMEM;/* 2、获取icm20608_dev结构体地址 */dev iio_priv(indio_dev); dev-spi spi;spi_set_drvdata(spi, indio_dev); /* 将indio_de设置为spi-dev的driver_data */mutex_init(dev-lock);/* 3、iio_dev的其他成员变量 */indio_dev-dev.parent spi-dev;indio_dev-info icm20608_info;indio_dev-name ICM20608_NAME; indio_dev-modes INDIO_DIRECT_MODE; /* 直接模式提供sysfs接口 */indio_dev-channels icm20608_channels;indio_dev-num_channels ARRAY_SIZE(icm20608_channels);/* 4、注册iio_dev */ret iio_device_register(indio_dev);if (ret 0) {dev_err(spi-dev, iio_device_register failed\n);goto err_iio_register;}/* 5、初始化regmap_config设置 */dev-regmap_config.reg_bits 8; /* 寄存器长度8bit */dev-regmap_config.val_bits 8; /* 值长度8bit */dev-regmap_config.read_flag_mask 0x80; /* 读掩码设置为0X80ICM20608使用SPI接口读的时候寄存器最高位应该为1 *//* 6、初始化SPI接口的regmap */dev-regmap regmap_init_spi(spi, dev-regmap_config);if (IS_ERR(dev-regmap)) {ret PTR_ERR(dev-regmap);goto err_regmap_init;}/* 7、初始化spi_device */spi-mode SPI_MODE_0; /*MODE0CPOL0CPHA0*/spi_setup(spi);/* 初始化ICM20608内部寄存器 */icm20608_reginit(dev); return 0;err_regmap_init:iio_device_unregister(indio_dev);
err_iio_register:return ret;
}/** description : spi驱动的remove函数移除spi驱动的时候此函数会执行* param - spi : spi设备* return : 0成功;其他负值,失败*/
static int icm20608_remove(struct spi_device *spi)
{struct iio_dev *indio_dev spi_get_drvdata(spi);struct icm20608_dev *dev;dev iio_priv(indio_dev);/* 1、删除regmap */ regmap_exit(dev-regmap);/* 2、注销IIO */iio_device_unregister(indio_dev);return 0;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] {{alientek,icm20608, 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] {{ .compatible alientek,icm20608 },{ /* Sentinel */ }
};/* SPI驱动结构体 */
static struct spi_driver icm20608_driver {.probe icm20608_probe,.remove icm20608_remove,.driver {.owner THIS_MODULE,.name icm20608,.of_match_table icm20608_of_match,},.id_table icm20608_id,
};/** description : 驱动入口函数* param : 无* return : 无*/
static int __init icm20608_init(void)
{return spi_register_driver(icm20608_driver);
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit icm20608_exit(void)
{spi_unregister_driver(icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(ALIENTEK);
MODULE_INFO(intree, Y);测试
IIO 驱动框架提供了 sysfs 接口因此加载成功以后我们可以在用户空间访问对应的 sysfs目录项进入目录“/sys/bus/iio/devices/”目录里面此目录下都是 IIO 框架设备如图所示
从图可以看出此时有两个 IIO 设备“iio:device0” iio:device0 是 I.MX6ULL 内部 ADC iio:device1 才是 ICM20608。大家进入到对应的设备目录就可以看出对应的 IIO 设备我们进入图中的“iio:device1”目录此目录下的内容如图所示
从图可以看出 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 轴的原始值和校准值每个轴都有一个文件陀螺仪和温度计同理。
我们来看一下图中这些文件名字组成方式以 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 这两个专属属性因此才会有图中的 in_accel_x_raw 和 in_accel_x_calibias 这两个文件。 通 道 属 性 的 命 名 也 就 是 图中 文 件 的 命 名 模 式 为 [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索引如果配置通道的时候设置了 indexed1那么就会使用通道的 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 组成形式如图所示
读文件测试
我们读取一下 in_accel_z_raw 这个文件这个文件是加速度计的 Z 轴原始值静态情况下 Z 轴应该是 1g 的重力加速度计我们可以读取 in_accel_z_raw 这个文件的值然后在结合上面读取到的加速度计分辨率计算一下对应的 Z 轴重力值看看是不是 1g 左右。
2074× 0.000488281≈1.01g此时 Z 轴重力为 1g结果正确。