Linux学习第59天:Linux IIO 驱动

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长


本章的思维导图如下:

一、IIO子系统简介

1、iio_dev

1)、iio_dev 结构体

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

cpp 复制代码
474 struct iio_dev {
475 int id;
476
477 int modes;//第 477 行, 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;//第 485 行, scan_bytes 为捕获到,并且提供给缓冲区的字节数。
486 struct mutex mlock;
487
488 const unsigned long *available_scan_masks;
/*
第 488 行, available_scan_masks 为可选的扫描位掩码,使用触发缓冲区的时候可以通过设
置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。
*/
489 unsigned masklength;
490 const unsigned long *active_scan_mask;
/*
第 490 行, active_scan_mask 为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才
能被发送到缓冲区。
*/
491 bool scan_timestamp;
492 unsigned scan_index_timestamp;
493 struct iio_trigger *trig;/*第 493 行, trig 为 IIO 设备当前触发器,当使用缓冲模式的时候。*/
494 struct iio_poll_func *pollfunc;/*第 494 行, 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;
/*
第 502 行, info 为 iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编
写,非常重要!我们从用户空间读取 IIO 设备内部数据,最终调用的就是 iio_info 里面的函数。
*/

503 struct mutex info_exist_lock;
504 const struct iio_buffer_setup_ops *setup_ops;
/*
第 504 行, setup_ops 为 iio_buffer_setup_ops 结构体类型,内容如下:
示例代码 75.1.1.2 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 struct cdev chrdev;
......
515 };

2)、 iio_dev 申请与释放

申请 iio_dev,函数为iio_device_alloc:

cpp 复制代码
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 之间的配合使用如下所示:

cpp 复制代码
1 struct icm20608_dev *dev;/*icm20608_dev 是自定义的设备结构体。*/
2 struct iio_dev *indio_dev;/*indio_dev 是 iio_dev 结构体变量指针。*/
3 4
/* 1、申请 iio_dev 内存 */
5 indio_dev = iio_device_alloc(sizeof(*dev));/*使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 icm2060_dev 的内存。*/
6 if (!indio_dev)
7 return -ENOMEM;
8 9
/* 2、获取设备结构体变量地址 */
10 dev = iio_priv(indio_dev);/*使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址。
indio_dev: 需要释放的 iio_dev。
*/

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

3)iio_dev 注册与注销

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

cpp 复制代码
int iio_device_register(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev: 需要注册的 iio_dev。
返回值: 0,成功;其他值,失败。

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

cpp 复制代码
void iio_device_unregister(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev: 需要注销的 iio_dev。
返回值: 0,成功;其他值,失败。

2、iio_info

iio_dev 有个成员变量: info。用户空间对设备的具体操作最终都会反映到 iio_info 里面。

cpp 复制代码
352 struct iio_info {
353 struct module *driver_module;
354 struct attribute_group *event_attrs;
355 const struct attribute_group *attrs;/*第 355 行, attrs 是通用的设备属性。*/
356
357 int (*read_raw)(struct iio_dev *indio_dev,
/*
indio_dev: 需要读写的 IIO 设备。
chan:需要读取的通道。
val, val2:对于 read_raw 函数来说 val 和 val2 这两个就是应用程序从内核空间读取到数
据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。 val 和 val2 共同组成具体值, val 是整数部分, val2 是小数部分。但是
val2 也是对具体的小数部分扩大 N 倍后的整数值.扩大的倍数我们不能随便设置,而是要使用 Linux 定义的倍数.
mask: 掩码,用于指定我们读取的是什么数据
*/
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,
/*
write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式,决定了 wtite_raw 函数中 val 和 val2 的意义.
*/
377 struct iio_chan_spec const *chan,
378 long mask);
......
415 };

3、ii0_chan_spec

Linux 内核使用 iio_chan_spec 结构体来描述通道:

cpp 复制代码
223 struct iio_chan_spec {
224 enum iio_chan_type type;/*第 224 行, type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类
型*/
225 int channel;/*第 225 行,当成员变量 indexed 为 1
时候, channel 为通道索引。*/
226 int channel2;/*第 226 行,当成员变量 modified 为 1 的时候, channel2 为通道修饰符。 
通道修饰符主要是影响 sysfs 下的通道文件名字*/
227 unsigned long address;
/*
第 227 行的 address 成员变量用户可以自定义,但是一般会设
置为此通道对应的芯片数据寄存器地址。
*/
228 int scan_index;/*第 228 行,当使用触发缓冲区的时候, scan_index 是扫描索引。*/

/*第 229~236, scan_type 是一个结构体,描述了扫描数据在缓冲区中的存储格式。*/
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;/*第 237 行, info_mask_separate 标记某些属性专属于此通道*/
238 long info_mask_shared_by_type;
/*
第 238 行, info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。也就是
iio_chan_spec.type 成员变量相同的通道。
*/
239 long info_mask_shared_by_dir;/*第 239 行, info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。*/
240 long info_mask_shared_by_all;
/*
第 240 行, 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;/*第 246 行, modified 为 1 的时候, channel2 为通道修饰符。*/
247 unsigned indexed:1;/*第 247 行, indexed 为 1 的时候, channel 为通道索引。*/
248 unsigned output:1;/*第 248 行, output 表示为输出通道。*/
249 unsigned differential:1;/*第 249 行, differential 表示为差分通道。*/
250 };

二、IIO驱动框架创建

1、基础驱动框架建立

SPI 驱动框架:

cpp 复制代码
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")

2、IIO设备申请与初始化

IIO 设备的申请、初始化以及注册在 probe 函数中完成,在注销驱动的时候还需要在 remove函数中注销掉 IIO 设备、释放掉申请的一些内存。

cpp 复制代码
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 }

三、驱动程序编写

1、使能内核IIO相关配置

-> Device Drivers

-> Industrial I/O support (IIO [=y])

-> [*]Enable buffer support within IIO

-> <*>Industrial I/O buffering based on kfifo

2、ICM20608的IIO驱动框架搭建

1)驱动框架搭建

设备树不需要做任何修改。

新建名为 icm20608.c 的驱动文件,搭建好的 ICM20608 IIO 驱动框架内容如下所示:

cpp 复制代码
/***************************************************************
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~41 行,通道宏定义,用于陀螺仪和加速度计
*/
25 #define ICM20608_CHAN(_type, _channel2, _index) \
26 { \
27 .type = _type, \
28 .modified = 1, \/*第 28 行 modified 成员变量为 1,所以
channel2 就是通道修饰符,用来指定 X、 Y、 Z 轴。*/
29 .channel2 = _channel2, \
30 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
/*
第 30 行设置相同类型的通道
IIO_CHAN_INFO_SCALE 属性相同,"scale"是比例的意思,在这里就是量程(分辨率),因为
ICM20608 的陀螺仪和加速度计的量程是可以调整的,量程不同分辨率也就不同。陀螺仪或加
速度计的三个轴也是一起设置的,因此对于陀螺仪或加速度计而言, X、 Y、 Z 这三个轴的量程
是共享的。
*/
31 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
/*
第 31 行,设置每个通道的 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS
这两个属性都是独立的, IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,这个肯定
是每个通道独立的。 IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是
ICM20608 的特性,不是所有的传感器都有校准值,一切都要以实际所使用的传感器为准。
*/

32 BIT(IIO_CHAN_INFO_CALIBBIAS), \
33 .scan_index = _index, \

/*
第 34
行,设置扫描数据类型,也就是 ICM20608 原始数据类型, ICM20608 的陀螺仪和加速度计都是
16 位 ADC,因此这里是通用的:为有符号类型、实际位数 16bit,存储位数 16bit,大端模式
(ICM20608 数据寄存器为大端模式)。
*/
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~63 行,设备结构体,由于采用了 regmap 和 IIO 框架,因此 ICM20608 的设备结构体
非常简单。
*/
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~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 这三个轴
的修饰符。
*/
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~208 行,这部分就是 iio_info, icm20608_read_raw 为读取函数,应用程序读取相应
文件的时候此函数执行, icm20608_write_raw 为写函数,应用程序向相应的文件写数据的时候
此函数执行。 icm20608_write_raw_get_fmt 函数用来设置应用程序向驱动写入的数据格式,
icm20608_info 就是具体的 iio_info 变量,初始化 iio_dev 的时候需要用到。
*/
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 行, icm20608_probe 函数,一般在此函数里面申请 iio_dev、初始化并注册,初始化
regmap、初始化 ICM20608 等。第 216 行通过 devm_iio_device_alloc 函数申请 iio_dev 以及自定
义设备结构体内存,本章节就是 icm20608_dev。
*/
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~239 行,初始化 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");

2)驱动框架搭建

通过驱动框架来窥探 IIO 在用户空间的存在方式。

编译驱动,得到 icm20608.ko 驱动文件。输入如下命令加载icm20608.ko驱动模块。

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

如果驱动加载成功, SPI 工作正常的话就会读取 ICM20608 的 ID 值并打印出来。

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

进入上图 中的"iio:device1"目录,此目录下的内容如图 所示:

3)通道文件的命名方式

通道属性的命名:[direction][type][index][modifier][info_mask]

direction:in / out

type :

cpp 复制代码
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 };

index:索引,如果配置通道的时候设置了 indexed=1,那么就会使用通道的 channel 成员变量来替代此部分命名。

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

cpp 复制代码
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 };

info_mask: 属性掩码,也就是属性,不同属性对应的字符如下所示:

cpp 复制代码
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 };

4)文件读测试

使用cat指令查看in_accel_x_raw文件:

3、完善icm20608-read_raw函数

cpp 复制代码
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 }

读取 ICM20608 的内部寄存器数据,最终修改好的 icm20608_read_raw 函数和其他一些配套函数如下:

cpp 复制代码
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};/*第 73 行, accel_scale_icm20608 是 ICM20608 的加速度计比例*/
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 }

4、完善icm20608_write_raw函数

实现设置陀螺仪和加速度计的量程:

cpp 复制代码
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 };

四、测试应用程序编写

1、Linux文件流读取

连续不断的读取传感器数值,字符串转换为数字。

1)打开文件流

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

2)关闭文件流

int fclose(FILE *stream)

3)读取读取文件流

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

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

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

size: 每个对象的大小。

nmemb: 要读取的对象个数。

stream: 要读取的文件流。

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

4)写文件流

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

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

size: 每个对象的大小。

nmemb: 要写入的对象个数。

stream: 要写入的文件流。

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

5)格式化输入文件流

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

stream: 要操作的文件流。

format: 格式。argument: 保存读取到的数据。

返回值: 成功读取到的数据个数, 如果读到文件末尾或者读取错误就返回 EOF。

2、编写测试APP

cpp 复制代码
/***************************************************************
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 /* 字符串转数字,将整数字符串转换为整数数值 
atof 和 atoi 这两个函数是标准的 C 库函数,并不需要我们自己编写,添加"stdlib.h"头文
件就可以直接调用。
*/
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 }

3、运行测试

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

cpp 复制代码
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 驱动模块:

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

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

cpp 复制代码
./icm20608App

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


本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,未经允许不得用于商业用途。

相关推荐
北岛寒沫4 分钟前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
CoolTiger、1 小时前
【Vmware16安装教程】
linux·虚拟机·vmware16
铁匠匠匠2 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
学习3人组2 小时前
CentOS 中配置 OpenJDK以及多版本管理
linux·运维·centos
厨 神2 小时前
vmware中的ubuntu系统扩容分区
linux·运维·ubuntu
Karoku0663 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
geek_Chen013 小时前
虚拟机共享文件夹开启后mnt/hgfs/下无sharefiles? --已解决
linux·运维·服务器
架构文摘JGWZ3 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
(⊙o⊙)~哦3 小时前
linux 解压缩
linux·运维·服务器
小齿轮lsl3 小时前
PFC理论基础与Matlab仿真模型学习笔记(1)--PFC电路概述
笔记·学习·matlab