Bootstrap

IIO驱动 Industrial I/O(正点原子笔记)

最近看见很多iio 驱动adc,赶紧找来学习一下。

传感器内部都会有ADC,传感器对外提供 IIC或者 SPI 接口,SOC 可以通过 IIC 或者 SPI 接口来获取到传感器内部的 ADC 数值,从而得到想要测量的结果。Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统

因此,当你使用的传感器本质是 ADC 或 DAC 器件的时候,可以优先考虑使用 IIO 驱动框架。

 iio_dev

1.iio_dev 结构体

IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备

474 struct iio_dev {
475     int id;
476
477     int modes;
478     int currentmode; //currentmode 为当前模式。
479     struct device dev;
480
481     struct iio_event_interface *event_interface;
482
483     struct iio_buffer *buffer; //buffer 为缓冲区。
484     struct list_head buffer_list;   //buffer_list 为当前匹配的缓冲区列表。
485     int scan_bytes;       //scan_bytes 为捕获到,并且提供给缓冲区的字节数。
486     struct mutex mlock;
487
488     const unsigned long *available_scan_masks; //为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。
489     unsigned masklength;
490     const unsigned long *active_scan_mask; //为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。
491     bool scan_timestamp; //为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。
492     unsigned scan_index_timestamp;
493     struct iio_trigger *trig;  // IIO 设备当前触发器,当使用缓冲模式的时候。
494     struct iio_poll_func *pollfunc; //为一个函数,在接收到的触发器上运行。
495
496     struct iio_chan_spec const *channels; //为 IIO 设备通道
497     int num_channels;  //为 IIO 设备的通道数
498
499     struct list_head channel_attr_list; 
500     struct attribute_group chan_attr_group;
501     const char *name; //为 IIO 设备名字
502     const struct iio_info *info;  //nfo 为 iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,很重要!我们从用户空间读取 IIO 设备内部数据,最终调用的就iio_info 里面的函数。
503     struct mutex info_exist_lock; 
504     const struct iio_buffer_setup_ops *setup_ops; //iio_buffer_setup_ops 结构体类型
505     struct cdev chrdev;
    ......
515 };

第 477 行,modes 为设备支持的模式,
INDIO_DIRECT_MODE 提供 sysfs 接口。
INDIO_BUFFER_TRIGGERED 支持硬件缓冲触发。
INDIO_BUFFER_SOFTWARE 支持软件缓冲触发。
INDIO_BUFFER_HARDWARE 支持硬件缓冲区。

第 504 行,setup_ops 为 iio_buffer_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 };

2、iio_dev 申请与释放

申请 iio_dev

struct iio_dev *iio_device_alloc(int sizeof_priv)

返回值:如果申请成功就返回 iio_dev 首地址,如果失败就返回 NULL。

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

void iio_device_free(struct iio_dev *indio_dev)

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);

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

3.iio_dev 注册与注销

int iio_device_register(struct iio_dev *indio_dev)

void iio_device_unregister(struct iio_dev *indio_dev)

iio_info

前面说过这个很重要。

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,struct iio_chan_spec const *chan,
                     int *val,
                     int *val2,
                     long mask);
......
369
370 int (*write_raw)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                     int val,
                     int val2,
                     long mask);
375
376 int (*write_raw_get_fmt)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                             long mask);
......
415 };

第 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 内核里面定义的数据扩大倍数,如下表所示

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

第 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*1000000=244。也就是 write_raw 函数的 val 参数为0,val2参数为 244。

IIO_VAL_INT
整数值,没有小数。比如 5000,那么就是 val=5000,不
需要设置 val2
IIO_VAL_INT_PLUS_MICRO
小数部分扩大 1000000 倍,比如 1.00236,此时 val=1,val2=2360。
IIO_VAL_INT_PLUS_NANO小数部分扩大 1000000000 倍,同样是 1.00236,此时val=1,val2=2360000。
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。比如 val=1,val2=4,那么实际值就是 1/4。
IIO_VAL_FRACTIONAL_LOG2值为 val>>val2,也就是 val 右移 val2 位。比如 val=25600,val2=4 , 那 么 真 正 的 值 就 是 25600 右 移 4 位 25600>>4=1600.

 iio_chan_spec

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那么这个 ADC 就有 8 个通道。

本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,也就是一共有 7 路数据,因此就有 7 个通道。注意,三轴陀螺仪或加速度计的 X、Y、Z 这三个轴,每个轴都算一个通道。

223 struct iio_chan_spec {
224 enum iio_chan_type type; //通道类型, iio_chan_type 是一个枚举类型
225 int channel; //当成员变量 indexed 为 1时候,channel 为通道索引
226 int channel2; //当成员变量 modified 为 1 的时候,channel2 为通道修饰符
227 unsigned long address;
228 int scan_index; //当使用触发缓冲区的时候,scan_index 是扫描索引。
229 struct {
230     char sign; //如果为‘u’表示数据为无符号类型,为‘s’的话为有符号类型
231     u8 realbits; //数据真实的有效位数,比如很多传感器说的 10 位 ADC,其真实有效数据就是 10 位。
232     u8 storagebits; //:存储位数,有效位数+填充位。比如有些传感器 ADC 是 12 位的,那么我们存储的话肯定要用到 2 个字节,也就是 16 位,这 16 位就是存储位数。
233     u8 shift; //右移位数,也就是存储位数和有效位数不一致的时候,需要右移的位数,这个参数不总是需要,一切以实际芯片的数据手册位数。
234     u8 repeat; //实际或存储位的重复数量
235     enum iio_endian endianness; //数据的大小端模式,可设置为 IIO_CPU、IIO_BE(大端)或 IIO_LE(小端)。
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; //modified 为 1 的时候,channel2 为通道修饰符
247 unsigned indexed:1;  //indexed 为 1 的时候,channel 为通道索引。
248 unsigned output:1;   //output 表示为输出通道
249 unsigned differential:1;  //differential 表示为差分通道。
250 };

如果是 ADC,那就是 IIO_VOLTAGE 类型。如果是 ICM20608 这样的多轴传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是IIO_ACCEL 类型,温度部分就是 IIO_TEMP。

 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 内核给出了可用的通道修饰符,比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL,X、Y、Z 这三个轴就用 channel2的通道修饰符来区分。IIO_MOD_X、IIO_MOD_Y、IIO_MOD_Z 就分别对应 X、Y、Z 这三个轴。

 enum iio_modifier {
         IIO_NO_MOD,
         IIO_MOD_X, /* X 轴 */
         IIO_MOD_Y, /* Y 轴 */
         IIO_MOD_Z, /* Z 轴 */
        ......
 };

第 227 行的 address 成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道,它的数据首地址就是 0X3B。address 也可以用作其他功能,自行选择,也可以不使用 address,一切以实际情况为准。
第 237 行,info_mask_separate 标记某些属性专属于此通道,include/linux/iio/types.h 文件中
的 iio_chan_info_enum 枚举类型描述了可选的属性值,比如 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 属性是相互独立的。

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 };

第 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 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。

IIO 驱动框架创建

IIO 框架主要用于 ADC 类的传感器,比如陀螺仪、加速度计、磁力计、光强度计等,这些传感器基本都是 IIC 或者 SPI 接口的。因此 IIO 驱动的基础框架就是 IIC 或者 SPI。不过有些也会用到IIO 框架,这时候platform就是基础框架。

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,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, 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,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 

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 }

实验

需要使能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 }

测试:

进入目录“/sys/bus/iio/devices/”目录里面,此目录下都是 IIO 框架设备

有两个 IIO 设备“iio:device0”,iio:device0 是 I.MX6ULL 内部 ADC,iio:device1 才是 ICM20608

进入其中。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 这两个专属属性,因此才会有图 75.3.2.2 中的 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 };

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

index:索引,如果配置通道的时候设置了 indexed=1,那么就会使用通道的 channel 成员变量来替代此部分命名。比如,有个 ADC 芯片支持 8 个通道,那么就可以使用 channel 来表示对应的通道,最终在用户空间呈现的每个通道文件名的 index 部分就是通道号。
modifier:当通道的 modified 成员变量为 1 的时候,channel2 就是修饰符,修饰符对应的字符串参考结构体 iio_modifier_names,

info_mask:属性掩码,也就是属性,

综上所述,in_accel_x_raw 组成形式:

由于所有的读取操作都会触发 icm20608_read_raw 函数,比如加速度计、陀螺仪、温度计等,因此我们需要做区分。配置通道的时候设置了 type 值,就可以使用 type值来区分是陀螺仪、加速度计还是温度计,后续需要完善 icm20608_read_raw 函数

应用程序


/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#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
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};

  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
85 float temp_act;
86 };
87 struct icm20608_dev icm20608;
88 static int file_data_read(char *filename, char *str)
96 {
97 int ret = 0;
98 FILE *data_stream;
99 data_stream = fopen(filename, "r"); /* 只读打开 */
if(data_stream == NULL) {
100
101
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 }

122 static int sensor_read(struct icm20608_dev *dev)
123 {
124 int ret = 0;
125 char str[50];
126
127
128 /* 1、获取陀螺仪原始数据 */
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);
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
140 /* 3、获取温度值 */
    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
145 /* 3、转换为实际数值 */
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
153dev->temp_act = ((dev->temp_raw - dev->temp_offset) /
                    dev->temp_scale) + 25;
154 return ret;
155 }
163 int main(int argc, char *argv[])
164 {
165 int ret = 0;
166
167 if (argc != 1) {
168 printf("Error Usage!\r\n");
169
170 return -1;
171}
172 while (1) {
173 ret = sensor_read(&icm20608);
174 if(ret == 0) {
175 /* 数据读取成功 */
176 printf("\r\n 原始值:\r\n");

177     printf("gx = %d, gy = %d, gz = %d\r\n",icm20608.gyro_x_raw,icm20608.gyro_y_raw,icm20608.gyro_z_raw);
178     printf("ax = %d, ay = %d, az = %d\r\n",icm20608.accel_x_raw,icm20608.accel_y_raw,icm20608.accel_z_raw);
179     printf("temp = %d\r\n", icm20608.temp_raw);
180     printf("实际值:");
181     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);
182    printf("act ax = %.2fg, act ay = %.2fg,act az = %.2fg\r\n",icm20608.accel_x_act,icm20608.accel_y_act,icm20608.accel_z_act);
183printf("act temp = %.2f°C\r\n", icm20608.temp_act);
184 }
185 usleep(100000); /*100ms */
186}
187return 0;
188}

;