【正点原子Linux连载】 第三十七章 Linux IIO驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第三十七章 Linux IIO驱动实验

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

37.1 IIO子系统简介
IIO全称是Industrial I/O,翻译过来就是工业I/O,大家不要看到“工业”两个字就觉得IIO是只用于工业领域的。大家一般在搜索IIO子系统的时候,会发现大多数讲的都是ADC,这是因为IIO就是为ADC类传感器准备的,当然了DAC也是可以的。大家常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个ADC,内部ADC将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如IIC、SPI等传输给SOC。因此,当你使用的传感器本质是ADC或DAC器件的时候,可以优先考虑使用IIO驱动框架。
37.1.1 iio_dev
1、iio_dev结构体
IIO子系统使用结构体iio_dev来描述一个具体IIO设备,此设备结构体定义在include/linux/iio/iio.h文件中,结构体内容如下(有省略):
示例代码37.1.1.1 iio_dev结构体

525 struct iio_dev {
526     int             				id;
527     struct module   				*driver_module;
528 
529     int            	 				modes;
530     int             				currentmode;
531     struct device   				dev;
532 
533     struct iio_event_interface  *event_interface;
534 
535     struct iio_buffer 			*buffer;
536     struct list_head    			buffer_list;
537     int             				scan_bytes;
538     struct mutex         			mlock;
539 
540     const unsigned long 			*available_scan_masks;
541     unsigned            			masklength;
542     const unsigned long 			*active_scan_mask;
543     bool                			scan_timestamp;
544     unsigned            			scan_index_timestamp;
545     struct iio_trigger   			*trig;
546     bool               	 			trig_readonly;
547     struct iio_poll_func			*pollfunc;
548     struct iio_poll_func			*pollfunc_event;
549 
550     struct iio_chan_spec const  	*channels;
551     int             				num_channels;
552 
553     struct list_head  			channel_attr_list;
554     struct attribute_group      	chan_attr_group;
555     const char          			*name;
556     const struct iio_info       	*info;
557     clockid_t           			clock_id;
558     struct mutex         			info_exist_lock;
559     const struct iio_buffer_setup_ops   *setup_ops;
560     struct cdev         			chrdev;
561 };
我们来看一下iio_dev结构体中几个比较重要的成员变量:

第529行,modes为设备支持的模式,可选择的模如表37.1.1.1所示:
在这里插入图片描述

表37.1.1.1 模式
第530行,currentmode为当前模式。
第535行,buffer为缓冲区。
第536行,buffer_list为当前匹配的缓冲区列表。
第537行,scan_bytes为捕获到,并且提供给缓冲区的字节数。
第540行,available_scan_masks为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到IIO缓冲区。
第542行,active_scan_mask为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。
第543行,scan_timestamp为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。
第545行,trig为IIO设备当前触发器,当使用缓冲模式的时候。
第547行,pollfunc为一个函数,在接收到的触发器上运行。
第550行,channels为IIO设备通道,为iio_chan_spec结构体类型,稍后会详细讲解IIO通道。
第551行,num_channels为IIO设备的通道数。
第555行,name为IIO设备名字。
第556行,info为iio_info结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!我们从用户空间读取IIO设备内部数据,最终调用的就是iio_info里面的函数。稍后会详细讲解iio_info结构体。
第559行,setup_ops为iio_buffer_setup_ops结构体类型,内容如下:
示例代码37.1.1.2 iio_buffer_setup_ops结构体

474 struct iio_buffer_setup_ops {
475     int (*preenable)(struct iio_dev *); 	/* 缓冲区使能之前调用 */
476     int (*postenable)(struct iio_dev *);	/* 缓冲区使能之后调用 */
477     int (*predisable)(struct iio_dev *);	/* 缓冲区禁用之前调用 */
478     int (*postdisable)(struct iio_dev *);	/* 缓冲区禁用之后调用 */
479     bool (*validate_scan_mask)(struct iio_dev *indio_dev,
480           const unsigned long *scan_mask);	/* 检查扫描掩码是否有效 */
481 };

可以看出iio_buffer_setup_ops里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定的话就默认使用iio_triggered_buffer_setup_ops。
继续回到示例代码37.1.1.1中第560行,chrdev为字符设备,由IIO内核创建。
2、iio_dev申请与释放
在使用之前要先申请iio_dev,申请函数为iio_device_alloc,函数原型如下:
struct iio_dev *iio_device_alloc(int sizeof_priv)
函数参数和返回值含义如下:
sizeof_priv:私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为iio_dev的私有数据,这样可以直接通过iio_device_alloc函数同时完成iio_dev和设备结构体变量的内存申请。申请成功以后使用iio_priv函数来得到自定义的设备结构体变量首地址。
返回值:如果申请成功就返回iio_dev首地址,如果失败就返回NULL。
一般iio_device_alloc和iio_priv之间的配合使用如下所示:
示例代码37.1.1.3 iio_device_alloc和iio_priv函数的使用

1  struct ap3216c_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行,ap3216c _dev是自定义的设备结构体。	
第2行,indio_dev是iio_dev结构体变量指针。
第5行,使用iio_device_alloc函数来申请iio_dev,并且一起申请了ap3216c_dev的内存。
第10行,使用iio_priv函数从iio_dev中提取出私有数据,也就是ap3216c_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的释放工作。
3、iio_dev注册与注销
前面分配好iio_dev以后就要初始化各种成员变量,初始化完成以后就需要将iio_dev注册到内核中,需要用到iio_device_register函数,函数原型如下:
int iio_device_register(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev:需要注册的iio_dev。
返回值:0,成功;其他值,失败。
如果要注销iio_dev使用iio_device_unregister函数,函数原型如下:
void iio_device_unregister(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev:需要注销的iio_dev。
返回值:0,成功;其他值,失败。
37.1.2 iio_info
iio_dev有个成员变量:info,为iio_info结构体指针变量,这个是我们在编写IIO驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到iio_info里面。iio_info结构体定义在include/linux/iio/iio.h中,结构体定义如下(有省略):
示例代码37.1.2.1 iio_info结构体

393 struct iio_info {
394     const struct attribute_group    *event_attrs;
395     const struct attribute_group    *attrs;
396 
397     int (*read_raw)(struct iio_dev *indio_dev,
398             struct iio_chan_spec const *chan,
399             int *val,
400             int *val2,
401             long mask);
......
416 
417     int (*write_raw)(struct iio_dev *indio_dev,
418              struct iio_chan_spec const *chan,
419              int val,
420              int val2,
421              long mask);
422 
423     int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
424              struct iio_chan_spec const *chan,
425              long mask);
......
462 };
第395行,attrs是通用的设备属性。	
第397和417行,分别为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内核里面定义的数据扩大倍数,或者说数据组合形式如表37.1.2.1所示:

组合宏 描述
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_DB dB数据,和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.
表37.1.2.1 数据组合表
mask:掩码,用于指定我们读取的是什么数据。
第423行的write_raw_get_fmt用于设置用户空间向内核空间写入的数据格式,write_raw_get_fmt函数决定了wtite_raw函数中val和val2的意义,也就是表37.1.2.1中的组合形式。
37.1.3 iio_chan_spec
IIO的核心就是通道,一个传感器可能有多路数据,比如一个ADC芯片支持8路采集,那么这个ADC就有8个通道
Linux内核使用iio_chan_spec结构体来描述通道,定义在include/linux/iio/iio.h文件中,内容如下:
示例代码37.1.3.1 iio_chan_spec结构体

236 struct iio_chan_spec {
237     enum iio_chan_type 	type;
238     int         			channel;
239     int         			channel2;
240     unsigned long       	address;
241     int         			scan_index;
242     struct {
243         char	sign;
244         u8  	realbits;
245         u8  	storagebits;
246         u8  	shift;
247         u8  	repeat;
248         enum 	iio_endian endianness;
249     } scan_type;
250     long            info_mask_separate;
251     long            info_mask_separate_available;
252     long            info_mask_shared_by_type;
253     long            info_mask_shared_by_type_available;
254     long            info_mask_shared_by_dir;
255     long            info_mask_shared_by_dir_available;
256     long            info_mask_shared_by_all;
257     long            info_mask_shared_by_all_available;
258     const struct 	iio_event_spec *event_spec;
259     unsigned int  	num_event_specs;
260     const struct	iio_chan_spec_ext_info *ext_info;
261     const char   	*extend_name;
262     const char   	*datasheet_name;
263     unsigned      	modified:1;
264     unsigned      	indexed:1;
265     unsigned      	output:1;
266     unsigned     	differential:1;
267 };
来看一下iio_chan_spec结构体中一些比较重要的成员变量:
第237行,type为通道类型, iio_chan_type是一个枚举类型,列举出了可以选择的通道类型,定义在include/uapi/linux/iio/types.h文件里面,内容如下:

示例代码37.1.3.2 iio_chan_type

14 enum iio_chan_type {
15  	IIO_VOLTAGE,        			/* 电压类型 						*/
16 		IIO_CURRENT,        			/* 电流类型 						*/
17  	IIO_POWER,          			/* 功率类型 						*/
18  	IIO_ACCEL,          			/* 加速度类型 					*/
19  	IIO_ANGL_VEL,       			/* 角度类型(陀螺仪) 				*/
20  	IIO_MAGN,           			/* 电磁类型(磁力计) 				*/
21  	IIO_LIGHT,          			/* 灯光类型 						*/
22  	IIO_INTENSITY,      			/* 强度类型(光强传感器) 			*/
23  	IIO_PROXIMITY,      			/* 接近类型(接近传感器) 			*/
24  	IIO_TEMP,           			/* 温度类型 						*/
25  	IIO_INCLI,          			/* 倾角类型(倾角测量传感器) 		*/
26  	IIO_ROT,            			/* 旋转角度类型 					*/
27  	IIO_ANGL,           			/* 转动角度类型(电机旋转角度测量传感器) */
28  	IIO_TIMESTAMP,      			/* 时间戳类型 					*/
29  	IIO_CAPACITANCE,    			/* 电容类型 						*/
30  	IIO_ALTVOLTAGE,     			/* 频率类型 						*/
31  	IIO_CCT,            			/* 笔者暂时未知的类型 			*/
32  	IIO_PRESSURE,       			/* 压力类型 						*/  
33  	IIO_HUMIDITYRELATIVE,		/* 湿度类型 						*/
34  	IIO_ACTIVITY,       			/* 活动类型(计步传感器) 			*/
35  	IIO_STEPS,          			/* 步数类型 						*/
36  	IIO_ENERGY,         			/* 能量类型(卡路里) 				*/
37  	IIO_DISTANCE,       			/* 距离类型 						*/
38  	IIO_VELOCITY,       			/* 速度类型 						*/
39  	IIO_CONCENTRATION,  			/* 浓度类型 						*/
40  	IIO_RESISTANCE,     			/* 电阻类型 						*/
41  	IIO_PH,             			/* PH类型 						*/
42  	IIO_UVINDEX,        			/* 紫外线类型 					*/
43  	IIO_ELECTRICALCONDUCTIVITY, 	/* 电导率类型  					*/
44  	IIO_COUNT,          			/* 计数类型 						*/
45  	IIO_INDEX,          			/* 笔者暂时未知的类型 			*/
46  	IIO_GRAVITY,        			/* 重力类型 						*/
47  	IIO_POSITIONRELATIVE,		/* 相对位置类型 					*/
48  	IIO_PHASE,          			/* 相位类型 						*/
49 };

从示例代码37.1.3.2可以看出,目前Linux内核支持的传感器类型非常丰富,而且支持类型也会不断的增加。如果是ADC,那就是IIO_VOLTAGE类型。如果是ICM20608这样的多轴传感器,那么就是复合类型了,陀螺仪部分是IIO_ANGL_VEL类型,加速度计部分是IIO_ACCEL类型,温度部分就是IIO_TEMP。
继续来看示例代码37.1.3.1中的iio_chan_spec结构体,第238行,当成员变量indexed为1时候,channel为通道索引。
第239行,当成员变量modified为1的时候,channel2为通道修饰符。Linux内核给出了可用的通道修饰符,定义在include/uapi/linux/iio/types.h文件里面,内容如下(有省略)
示例代码37.1.3.3 iio_modifier

52 enum iio_modifier {
53  	IIO_NO_MOD,
54  	IIO_MOD_X,          	/* X轴 		*/
55 	 	IIO_MOD_Y,          	/* Y轴 		*/
56  	IIO_MOD_Z,          	/* Z轴 		*/
...... 
94  	IIO_MOD_PM10,       	/* PM10 	*/
95  	IIO_MOD_ETHANOL,    	/* 乙醇 	*/
96  	IIO_MOD_H2,         	/* H2 		*/
97 };

通道修饰符主要是影响sysfs下的通道文件名字,后面我们会讲解sysfs下通道文件名字组成形式。
继续回到示例代码37.1.3.1,第240行的address用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址address也可以用作其他功能,自行选择,也可以不使用address,一切以实际情况为准。
第241行,当使用触发缓冲区的时候,scan_index是扫描索引。
第242~249,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(小端)。
第250行,info_mask_separate标记某些属性专属于此通道,include/linux/iio/types.h文件中的iio_chan_info_enum枚举类型描述了可选的属性值,如下所示:
示例代码37.1.3.4 iio_chan_info_enum

34 enum iio_chan_info_enum {
35  	IIO_CHAN_INFO_RAW = 0,
36  	IIO_CHAN_INFO_PROCESSED,
37  	IIO_CHAN_INFO_SCALE,
38  	IIO_CHAN_INFO_OFFSET,
......
57  	IIO_CHAN_INFO_DEBOUNCE_TIME,
58  	IIO_CHAN_INFO_CALIBEMISSIVITY,
59  	IIO_CHAN_INFO_OVERSAMPLING_RATIO,
60 };

第251行,info_mask_shared_by_type标记导出的信息由相同类型的通道共享。也就是iio_chan_spec.type成员变量相同的通道。
第254行,info_mask_shared_by_dir标记某些导出的信息由相同方向的通道共享。
第256行,info_mask_shared_by_all表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。
第263行,modified为1的时候,channel2为通道修饰符。
第264行,indexed为1的时候,channel为通道索引。
第265行,output表示为输出通道。
第266行,differential表示为差分通道。
37.2 实验程序编写
本实验对应的例程路径为:开发板光盘1、程序源码3、Linux驱动例程24_iio_ap3216c。
接下来我们就直接使用IIO驱动框架编写AP3216C驱动,AP3216C驱动核心就是IIC,上一章节我们也讲解了如何在IIC总线上使用regmap。因此本章AP3216驱动底层没啥好讲解的,重点是如何套上IIO的驱动框架,因此关于AP3216C芯片内部寄存器、IIC驱动、regmap等不会做讲解,有什么不懂得可以看前面相应得实验章节。
37.2.1 IIO_AP3216C驱动编写
新建名为“23_iio_ap3216c”的文件夹,然后在23_iio_ap3216c文件夹里面创建vscode工程,工作区命名位“iio_ap3216c”。工程创建好以后新建iio_ap3216c.c和iio_ap3216c.h这两个文件,iio_ap3216c.c为AP3216C的驱动代码,iio_ap3216c.h是AP3216C寄存器头文件。先在iio_ap3216c.h中定义好AP3216C的寄存器,输入如下内容:
示例代码37.2.1 iio_ap3216c.h 文件代码段

1 #ifndef AP3216C_H
2 #define AP3216C_H
3 /***************************************************************
4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
5 文件名 : iio_ap3216c.h
6 作者 : 正点原子 Linux 团队
7 版本 : V1.0
8 描述 : AP3216C 寄存器地址描述头文件
9 其他 : 无
10 论坛 : www.openedv.com
11 日志 : 初版 V1.0 2021/03/19 正点原子 Linux 团队创建
12 ***************************************************************/
13
14 #define AP3216C_ADDR 0X1E /* AP3216C 器件地址 */
15
16 /* AP3316C 寄存器 */
17 #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
18 #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
19 #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
20 #define AP3216C_IRDATALOW 0x0A /* IR 数据低字节 */
21 #define AP3216C_IRDATAHIGH 0x0B /* IR 数据高字节 */
22 #define AP3216C_ALSDATALOW 0x0C /* ALS 数据低字节 */
23 #define AP3216C_ALSDATAHIGH 0X0D /* ALS 数据高字节 */
24 #define AP3216C_PSDATALOW 0X0E /* PS 数据低字节 */
25 #define AP3216C_PSDATAHIGH 0X0F /* PS 数据高字节 */
26 #define AP3216C_ALSCONFIG	0X10	/* ALS配置寄存器 */
27 #define AP3216C_PSCONFIG	0X20	/* PS配置寄存器 */
28 #define AP3216C_PSLEDCONFIG 0X21	/* LED配置寄存器 */
29 #endif
iio_ap3216c.h就是AP3216C器件寄存器宏定义。然后在iio_ap3216c.c输入如下内容:

示例代码37.2.2 iio_ap3216c.c 文件代码段

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名	: iio_ap3216c.c
作者   	: 正点原子Linux团队
版本   	: V1.0
描述   	: AP3216C IIO驱动程序
其他   	: 无
论坛   	: www.openedv.com
日志    	: 初版 V1.0 2023/08/15 正点原子Linux团队创建
***************************************************************/
1   #include <linux/types.h>
2   #include <linux/kernel.h>
3   #include <linux/delay.h>
4   #include <linux/ide.h>
5   #include <linux/init.h>
6   #include <linux/module.h>
7   #include <linux/errno.h>
8   #include <linux/gpio.h>
9   #include <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of_gpio.h>
12  #include <linux/semaphore.h>
13  #include <linux/timer.h>
14  #include <linux/i2c.h>
15  #include <asm/uaccess.h>
16  #include <asm/io.h>
17  #include <linux/regmap.h>
18  #include <linux/iio/iio.h>
19  #include <linux/iio/sysfs.h>
20  #include <linux/iio/trigger_consumer.h>
21  #include <linux/iio/buffer.h>
22  #include <linux/iio/triggered_buffer.h>
23  #include <linux/unaligned/be_byteshift.h>
24  #include <linux/iio/trigger.h>
25  #include "iio_ap3216c.h"
26 
27  #define AP321C_NAME   "ap3216c"
28 
29  /* 
30   * AP3216C的扫描元素,1路ALS(环境关),1路PS(距离传感器),1路IR
31   */
32  enum inv_ap3216c_scan{
33      AP3216C_ALS,
34      AP3216C_PS,
35      AP3216C_IR,
36  };
37 
38  /* 
39   * ap3216c环境光传感器分辨率,扩大1000000倍,
40   * 量程依次为0~20661,0~5162,0~1291,0~323。单位:lux
41   */
42  static const int als_scale_ap3216c[] = {315000, 78800, 19700, 4900};
43 
44 
45  struct ap3216c_dev{
46      struct i2c_client *client;      /*i2c设备*/
47      struct regmap *regmap;          /*regmap*/
48      struct regmap_config regmap_config;
49      struct mutex lock;
50  };
51 
52  /*
53   * ap3216c通道,1路ALS(环境关),1路PS(距离传感器),1路IR
54   */
55  static const struct iio_chan_spec ap3216c_channels[] = {
56      {
57          /* ALS通道 */
58          .type = IIO_INTENSITY,
59          .modified = 1,
60          .channel2 = IIO_MOD_LIGHT_BOTH,
61          .address = AP3216C_ALSDATALOW,
62          .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 
63              BIT(IIO_CHAN_INFO_SCALE),
64          .scan_index = AP3216C_ALS,
65          .scan_type = {
66                  .sign = 'u',
67                  .realbits = 16,
68                  .storagebits = 16,
69                  .endianness = IIO_LE,
70                   },
71      },
72          /* PS通道 */
73      {
74          .type = IIO_PROXIMITY,
75          .address = AP3216C_PSDATALOW,
76          .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
77          .scan_index = AP3216C_PS,
78          .scan_type = {
79                  .sign = 'u',
80                  .realbits = 10,
81                  .storagebits = 16,
82                  .endianness = IIO_LE,
83                   },
84      },
85          /* IR通道 */
86          {
87          .type = IIO_INTENSITY,
88          .modified = 1,
89          .channel2 = IIO_MOD_LIGHT_IR,
90          .address = AP3216C_IRDATALOW,
91          .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
92          .scan_index = AP3216C_IR,
93          .scan_type = {
94                  .sign = 'u',
95                  .realbits = 16,
96                  .storagebits = 16,
97                  .endianness = IIO_LE,
98                   },
99      },
100
101 };
102
103 /*
104  * 扫描掩码,两种情况,全启动0X111,或者都不启动0X0
105  */
106 static const unsigned long ap3216c_scan_masks[] = {
107     BIT(AP3216C_ALS)|BIT(AP3216C_IR)|BIT(AP3216C_PS),
108     0,
109 };
110
111 /*
112  * @description : 读取ap3216c指定寄存器值,读取一个寄存器
113  * @param - dev:  ap3216c设备
114  * @param - reg:  要读取的寄存器
115  * @return    :   读取到的寄存器值
116  */
117 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, 
u8 reg)
118 {
119     u8 ret;
120     unsigned int data;
121
122     ret = regmap_read(dev->regmap, reg, &data);
123     return (u8)data;
124 }
125
126 /*
127  * @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器
128  * @param - dev:  ap3216c设备
129  * @param - reg:  要写的寄存器
130  * @param - data: 要写入的值
131  * @return   :    无
132  */
133 static void ap3216c_write_reg(struct ap3216c_dev *dev,u8 reg,u8 data)
134 {
135     regmap_write(dev->regmap,reg,data);
136 }
137
138 /*
139  * @description     : 初始化AP3216C
140  * @param - dev     : 要初始化的ap3216c设备
141  * @return          : 0 成功;其他 失败
142  */
143 static int ap3216c_reginit(struct ap3216c_dev *dev)
144 {
145     /* 初始化AP3216C */
146     ap3216c_write_reg(dev, AP3216C_SYSTEMCONG, 0x04);   /* 复位AP3216C            */
147     mdelay(50);                                         /* AP3216C复位最少10ms  */
148     ap3216c_write_reg(dev,AP3216C_SYSTEMCONG,0x03);     /* 开启ALS、PS+IR        */
149     ap3216c_write_reg(dev,AP3216C_ALSCONFIG,0x00);      /* ALS单次转换触发,量程为0~20661 lux */
150     ap3216c_write_reg(dev,AP3216C_PSLEDCONFIG,0x13);    /* IR LED 1脉冲,驱动电流100%*/
151
152     return 0;
153 }
154
155 /*
156   * @description    : 读取AP3216C传感器数
157   * @param - dev    : ap3216c设备 
158   * @param - reg    : 要读取的通道寄存器首地址。
159   * @param - chann2 : 需要读取的通道,比如ALS,IR。
160   * @param - val    : 保存读取到的值。
161   * @return         : 0,成功;其他值,错误
162   */
163 static int ap3216c_read_alsir_data(struct ap3216c_dev *dev, int reg, int channel2, int *val)
164 {
165     int ret = 0;
166     unsigned char data[2];
167
168     switch(channel2){
169         case IIO_MOD_LIGHT_BOTH:    /* 读取ALS数据 */
170             ret = regmap_bulk_read(dev->regmap, reg, data, 2);
171             *val = ((int)data[1]<<8) | data[0];
172             break;
173         case IIO_MOD_LIGHT_IR:      /* 读取IR数据 */
174             ret = regmap_bulk_read(dev->regmap, reg, data, 2);
175             *val = ((int)data[1]<<2) | (data[0] & 0x03);
176             break;
177         default:
178             ret = -EINVAL;
179             break;
180     }
181
182     if(ret){
183         return -EINVAL;
184     }
185
186     return IIO_VAL_INT;
187 }
188
189 /*
190   * @description    : 设置AP3216C的ALS量程(分辨率)
191   * @param - dev    : ap3216c设备
192   * @param - val    : 量程(分辨率值)。
193   * @param - chann2 : 需要设置的通道。
194   * @return         : 0,成功;其他值,错误
195   */
196 static int ap3216c_write_als_scale(struct ap3216c_dev *dev, 
int channel2, int val)
197 {
198     int ret = 0,i;
199     u8 d;
200
201     switch(channel2){
202         case IIO_MOD_LIGHT_BOTH:    /* 设置ALS分辨率 */
203             for(i = 0; i < ARRAY_SIZE(als_scale_ap3216c); ++i){
204                 if(als_scale_ap3216c[i] == val){
205                     d = (i<<4);
206                   ret = regmap_write(dev->regmap, AP3216C_ALSCONFIG,d);
207                 }
208             }
209             break;
210         default:
211             ret = -EINVAL;
212             break;
213     }
214     return ret;
215 }
216
217 /*
218   * @description    : 读函数,当读取sysfs中的文件的时候最终此函数会执行,
219   *                     :此函数里面会从传感器里面读取各种数据,然后上传给应用。
220   * @param - indio_dev      : iio_dev
221   * @param - chan       : 通道
222   * @param - val            : 读取的值,如果是小数值的话,val是整数部分。
223   * @param - val2           : 读取的值,如果是小数值的话,val2是小数部分。
224   * @return                 : 0,成功;其他值,错误
225   */
226 static int ap3216c_read_raw(struct iio_dev *indio_dev,
227                struct iio_chan_spec const *chan,
228                int *val, int *val2, long mask)
229 {
230     int ret = 0;
231     unsigned char data[2];
232     unsigned char regdata = 0;
233     struct ap3216c_dev *dev = iio_priv(indio_dev);
234
235     switch(mask){
236     case IIO_CHAN_INFO_RAW:             
237         mutex_lock(&dev->lock);         /* 上锁*/
238         switch(chan->type){
239             case IIO_INTENSITY:
240                 ret = ap3216c_read_alsir_data(dev,chan->address,
chan->channel2,val);
241                 break;                  /* 值为val */
242             case IIO_PROXIMITY:
243                 ret = regmap_bulk_read(dev->regmap, chan->address,
 data,2);
244                 *val = ((int)(data[1] & 0x03F) << 4 | (data[0] & 0x0F));
245                 ret = IIO_VAL_INT;
246                 break;                  /* 值为val */
247             default :
248             ret = -EINVAL;
249             break;
250         }
251         mutex_unlock(&dev->lock);       /* 释放锁*/
252         return ret;
253     case IIO_CHAN_INFO_SCALE:
254         
255         switch(chan->type){
256             case IIO_INTENSITY:
257                 mutex_init(&dev->lock);         /* 上锁*/
258     regdata = (ap3216c_read_reg(dev,AP3216C_ALSCONFIG) & 0x30) >> 4;
259                 *val =0;
260                 *val2 = als_scale_ap3216c[regdata];
261                 mutex_unlock(&dev->lock);       /* 释放锁*/
262              return IIO_VAL_INT_PLUS_MICRO;  /* 值为val+val2/1000000 */
263             default :
264                 return -EINVAL;
265         }
266         return ret;
267     
268     default :
269         return -EINVAL;
270     }
271     
272     return ret;
273 }   
274
275 /*
276   * @description    : 写函数,当向sysfs中的文件写数据的时候最终此函数会
277   *                         :执行,一般在此函数里面设置传感器,比如量程等。
278   * @param - indio_dev  : iio_dev
279   * @param - chan       : 通道
280   * @param - val        : 应用程序写入的值,如果是小数值的话,val是整数部分。
281   * @param - val2       : 应用程序写入的值,如果是小数值的话,val2是小数部分。
282   * @return             : 0,成功;其他值,错误
283   */
284 static int ap3216c_write_raw(struct iio_dev *indio_dev,
285                 struct iio_chan_spec const *chan,
286                 int val, int val2, long mask)
287 {
288     int ret = 0;
289     struct ap3216c_dev *dev = iio_priv(indio_dev);
290
291     switch(mask){
292        case IIO_CHAN_INFO_SCALE:
293         switch(chan->type){
294             case IIO_INTENSITY:
295                 mutex_init(&dev->lock);
296                ret = ap3216c_write_als_scale(dev,chan->channel2,val2);
297                 mutex_unlock(&dev->lock);
298                 break;
299             default:
300                 ret = -EINVAL;
301                 break;
302         }
303         default:
304             ret = -EINVAL;
305             break;
306     }
307     return ret;
308 }
309
310 /*
311   * @description    : 用户空间写数据格式,比如我们在用户空间操作sysfs来
312   *                 :设置传感器的分辨率,如果分辨率带小数,那么这个小数传递
313   *                 : 到内核空间应该扩大多少倍,此函数就是用来设置这个的。
314   * @param - indio_dev  : iio_dev
315   * @param - chan           : 通道
316   * @param - mask           : 掩码
317   * @return                 : 0,成功;其他值,错误
318   */
319 static int ap3216c_write_raw_get_fmt(struct iio_dev *indio_dev,
320                  struct iio_chan_spec const *chan, long mask)
321 {
322     switch (mask) {
323     case IIO_CHAN_INFO_SCALE:
324         switch (chan->type) {
325         case IIO_INTENSITY:/* 用户空间写的陀螺仪分辨率数据要乘以1000000 */
326             return IIO_VAL_INT_PLUS_MICRO;
327         default:                
328             return IIO_VAL_INT_PLUS_MICRO;
329         }
330     default:
331         return IIO_VAL_INT_PLUS_MICRO;
332     }
333
334     return -EINVAL;
335 }
336
337 /*
338  * iio_info结构体变量
339  */
340 static const struct iio_info ap3216c_info = {
341     .read_raw       = ap3216c_read_raw,
342     .write_raw      = ap3216c_write_raw,
343     .write_raw_get_fmt = &ap3216c_write_raw_get_fmt,  
344 };
345
346 /*
347   * @description     : i2c驱动的probe函数,当驱动与
348   *                    设备匹配以后此函数就会执行
349   * @param - client  : i2c设备
350   * @param - id      : i2c设备ID
351   * @return          : 0,成功;其他负值,失败
352   */   
353 static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
354 {
355     int ret;
356     struct ap3216c_dev *dev;
357     struct iio_dev *indio_dev;
358
359     /*  1、申请iio_dev内存 */
360     indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev));
361     if (!indio_dev)
362         return -ENOMEM;
363
364     /* 2、获取ap3216c_dev结构体地址 */
365     dev = iio_priv(indio_dev); 
366     dev->client = client;
367
368     i2c_set_clientdata(client,indio_dev);   /* 保存ap3216cdev结构体 */
369
370     /* 初始化regmap_config设置 */
371     dev->regmap_config.reg_bits = 8;        /* 寄存器长度8bit */
372     dev->regmap_config.val_bits = 8;        /* 值长度8bit */
373
374     /* 初始化IIC接口的regmap */
375     dev->regmap = regmap_init_i2c(client, &dev->regmap_config);
376     if(IS_ERR(dev->regmap)){
377         ret = PTR_ERR(dev->regmap);
378         goto err_regmap_init;
379     }
380
381     mutex_init(&dev->lock);
382
383     /* 4、iio_dev的其他成员变量 */
384     indio_dev->dev.parent = &client->dev;
385     indio_dev->info = &ap3216c_info;
386     indio_dev->name = AP321C_NAME;
387     indio_dev->modes = INDIO_DIRECT_MODE; /* 直接模式,提供sysfs接口 */
388     indio_dev->channels = ap3216c_channels;
389     indio_dev->num_channels = ARRAY_SIZE(ap3216c_channels);
390     indio_dev->available_scan_masks = ap3216c_scan_masks;
391
392     /* 5、注册iio_dev */
393     ret = iio_device_register(indio_dev);
394     if(ret < 0){
395         dev_err(&client->dev, "iio_device_register failed\n");
396         goto err_iio_register;
397     }
398
399     ap3216c_reginit(dev); /* 初始化ap3216c */
400     return 0;
401
402 err_iio_register:
403 err_regmap_init:
404     iio_device_unregister(indio_dev);
405     return ret;
406 }
407
408 /*
409  * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
410  * @param - client  : i2c设备
411  * @return          : 0,成功;其他负值,失败
412  */
413 static int ap3216c_remove(struct i2c_client *client)
414 {
415     struct iio_dev *indio_dev = i2c_get_clientdata(client);
416     struct ap3216c_dev *dev;
417
418     dev =iio_priv(indio_dev);
419
420     /* 1、释放regmap */
421     regmap_exit(dev->regmap);
422     /* 2、注销IIO */   
423     iio_device_unregister(indio_dev);
424     return 0;
425 }
426
427 /* 传统匹配方式ID列表 */
428
429 static const struct i2c_device_id ap3216c_id[] = {
430     {"alientek,ap3216c", 0},
431     {}
432 };
433
434 /* 设备树匹配列表 */
435 static const struct of_device_id ap3216c_of_match[] = {
436     { .compatible = "alientek,ap3216c" },
437     { /* Sentinel */ }
438 };
439
440 /* I2C
441 驱动结构体 */
442 static struct i2c_driver ap3216c_driver = {
443     .probe = ap3216c_probe,
444     .remove = ap3216c_remove,
445     .driver = {
446             .owner = THIS_MODULE,
447             .name = "ap3216c",
448             .of_match_table = ap3216c_of_match,
449            },
450     .id_table = ap3216c_id,
451 };
452
453 /*
454  * @description     : 驱动入口函数
455  * @param           : 无
456  * @return          : 无
457  */
458 static int __init ap3216c_init(void)
459 {
460     int ret = 0;
461     ret = i2c_add_driver(&ap3216c_driver);
462     return ret;
463 }
464
465 /*
466  * @description     : 驱动出口函数
467  * @param           : 无
468  * @return          : 无
469  */
470 static void __exit ap3216c_exit(void)
471 {
472     i2c_del_driver(&ap3216c_driver);
473 }
474
475 module_init(ap3216c_init);
476 module_exit(ap3216c_exit);
477 MODULE_LICENSE("GPL");
478 MODULE_AUTHOR("ALIENTEK");
479 MODULE_INFO(intree, "Y");

第32~36行,自定义了扫描索引枚举类型inv_ap3216c_scan,包括1路环境光强度,1路接触距离,1路红外光强度。
第45~50行,设备结构体,由于采用了regmap和IIO框架,因此AP3216C的设备结构体非常简单。
第55~101行,AP3216C通道,这里定义了3个通道,分别是:ALS(环境光强度)通道,PS(接触距离)通道,IR(红外光强度)通道。ALS通道有两个属性,IIO_CHAN_INFO_RAW为ALS通道的原始值,IIO_CHAN_INFO_SCANLE是AP3216C的比例,也就是一个单位的原始值为多少光强度,这个需要查阅AP3216C的数据手册。从这里可以看出,想要得到AP3216C的具体光强度,需要两个数据:原始值和比例值,也就是应用程序需要能够从IIO驱动框架中的到着三个值,一般是应用程序读取相应的文件,所以这里就要有三个独立的文件分别表示原始值和比例值,这就是两个属性的来源。剩下的PS通道和IR通道也是同样的道理。
第117~136行,就是上一章的regmap的内容,使用regmap_read来完成对一个寄存器读取操作,使用regmap_write函数来完成对一个寄存器写操作。
第226~273行,这部分就是iio_info,ap3216c_read_raw为读取函数,应用程序读取相应文件的时候此函数执行,ap3216c_write_raw为写函数,应用程序向相应的文件写数据的时候此函数执行。ap3216c_write_raw_get_fmt函数用来设置应用程序向驱动写入的数据格式,ap3216c_info就是具体的iio_info变量,初始化iio_dev的时候需要用到。
第362~415行,ap3216c_probe函数,一般在此函数里面申请iio_dev、初始化并注册,初始化regmap等。
这里就简单的分析了IIO的框架,接下来就是编写APP。
37.3 测试应用程序编写
37.3.1 Linux文件流读取
前面我们都是直接使用cat命令读取对应文件的内容,如果要连续不断的读取传感器数据就不能用cat命令了,我们需要编写对应的APP软件,在编写APP之前我们先了解一下所要用到的API函数。
首先我们要知道,前面使用cat命令读取到的文件内容字符串,虽然看起来像是数字。比如我们使用cat命令读取到的in_intensity_both_scale如图37.3.1.1所示:
在这里插入图片描述

图37.3.1.1 in_intensity_both_scale 文件内容
图37.3.1中的文件内容为0.315000,但是这里的0.315000是字符串,并不是具体的数字,所以我们需要将其转换为对应的数字。另外in_intensity_both_scale是流文件,也叫做标准的I/O流,因此打开、读写操作要使用文件流操作函数。
1、打开文件流
打开文件流使用fopen函数,函数原型如下:
FILE *fopen(const char *pathname, const char *mode)
函数参数和返回值含义如下:
pathname:需要打开的文件流路径。
mode:打开方式,可选的打开方式如表37.3.1.2所示:
mode 描述
r 打开只读文件。
r+ 打开读写文件。
w 打开只写文件,如文件存在则文件长度清零,如文件不存在就自动创建,文件流指针调整到文件头部。
w+ 打开可读写文件,如文件存在则文件长度清零,如文件不存在就自动创建,文件流指针调整到文件头部。
a 以追加的方式打开只写文件,如果文件不存在就新建文件,如果存在就将数据追加到文件末尾。
a+ 以追加的方式打开读写文件,如果文件不存在就新建文件,如果存在就将数据追加到文件末尾。
表37.3.1.2 打开方式
返回值:NULL,打开错误;其他值,打开成功的文件流指针,为FILE类型。
2、关闭文件流
关闭文件流使用函数fclose,函数原型如下:
int fclose(FILE *stream)
函数参数和返回值含义如下:
stream:要关闭的文件流指针。
返回值:0,关闭成功;EOF,关闭错误。
3、读取文件流
要读取文件流使用fread函数,函数原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
fread函数用于从给定的输入流中读取最多nmemb个对象到数组ptr中,函数参数和返回值含义如下:
ptr:要读取的数组中首个对象的指针。
size:每个对象的大小。
nmemb:要读取的对象个数。
stream:要读取的文件流。
返回值:返回读取成功的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者0)。
4、写文件流
要向文件流写入数据,使用fwrite函数,函数原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite函数用于向给定的文件流中写入最多nmemb个对象,函数参数和返回值含义如下:
ptr:要写入的数组中首个对象的指针。
size:每个对象的大小。
nmemb:要写入的对象个数。
stream:要写入的文件流。
返回值:返回成功写入的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者0)。
5、格式化输入文件流
fscanf函数用于从一个文件流中格式化读取数据,fscanf函数在遇到空格和换行符的时候就会结束。前面我们说了IIO框架下的sysfs文件内容都是字符串,比如in_accel_scale文件内容为“0.000488281”,这是一串字符串,并不是具体的数字,因此我们在读取的时候就需要使用字符串读取格式。在这里就可以使用fscanf函数来格式化读取文件内容,函数原型如下:
int fscanf(FILE *stream, const char *format, ,[argument…])
fscanf用法和scanf类似,函数参数和返回值含义如下:
stream:要操作的文件流。
format:格式。
argument:保存读取到的数据。
返回值:成功读取到的数据个数,如果读到文件末尾或者读取错误就返回EOF。
37.3.2 编写测试APP
新建名为iio_ap3216App.c的文件,输入如下内容:
示例代码37.3.2.1 iio_ap3216cAPP.c 文件代码段

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名	: iio_ap3216cAPP.c
作者   	: 正点原子Linux团队
版本   	: V1.0
描述   	: AP3216C设备IIO框架测试程序
其他   	: 无
论坛   	: www.openedv.com
日志    	: 初版 V1.0 2023/08/15 正点原子Linux团队创建
***************************************************************/
1   #include "stdio.h"
2   #include "unistd.h"
3   #include "sys/types.h"
4   #include "sys/stat.h"
5   #include "sys/ioctl.h"
6   #include "fcntl.h"
7   #include "stdlib.h"
8   #include "string.h"
9   #include <poll.h>
10  #include <sys/select.h>
11  #include <sys/time.h>
12  #include <signal.h>
13  #include <fcntl.h>
14  #include <errno.h>
15 
16  /* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
17  #define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
18      ret = file_data_read(file_path[index], str);\
19      dev->member = atof(str);\
20      
21  /* 字符串转数字,将整数字符串转换为整数数值 */
22  #define SENSOR_INT_DATA_GET(ret, index, str, member)\
23      ret = file_data_read(file_path[index], str);\
24      dev->member = atoi(str);\
25 
26 
27  /* ap3216c iio框架对应的文件路径 */
28  static char *file_path[] = {
29      "/sys/bus/iio/devices/iio:device1/in_intensity_both_scale",
30      "/sys/bus/iio/devices/iio:device1/in_intensity_both_raw",
31      "/sys/bus/iio/devices/iio:device1/in_intensity_ir_raw",
32      "/sys/bus/iio/devices/iio:device1/in_proximity_raw",
33  };
34 
35  /* 文件路径索引,要和file_path里面的文件顺序对应 */
36  enum path_index {
37      IN_INTENSITY_BOTH_SCALE = 0,
38      IN_INTENSITY_BOTH_RAW,
39      IN_INTENSITY_IR_RAW,
40      IN_PROXIMITY_RAW,
41  };
42 
43  /*
44   * ap3216c数据设备结构体
45   */
46  struct ap3216c_dev{
47      int als_raw, ir_raw, ps_raw;
48      float als_scale;
49      float als_act;
50  };
51 
52  struct ap3216c_dev ap3216c;
53 
54   /*
55   * @description         : 读取指定文件内容
56   * @param - filename    : 要读取的文件路径
57   * @param - str         : 读取到的文件字符串
58   * @return              : 0 成功;其他 失败
59   */
60  static int file_data_read(char *filename, char *str)
61  {
62      int ret = 0;
63      FILE *data_stream;
64 
65      data_stream = fopen(filename, "r"); /* 只读打开 */
66      if(data_stream == NULL) {
67          printf("can't open file %s\r\n", filename);
68          return -1;
69      }
70 
71      ret = fscanf(data_stream, "%s", str);
72      if(!ret) {
73          printf("file read error!\r\n");
74      } else if(ret == EOF) {
75          /* 读到文件末尾的话将文件指针重新调整到文件头 */
76          fseek(data_stream, 0, SEEK_SET);  
77      }
78      fclose(data_stream);    /* 关闭文件 */  
79      return 0;
80  }
81 
82   /*
83   * @description : 获取AP3216C数据
84   * @param - dev : 设备结构体
85   * @return      : 0 成功;其他 失败
86   */
87  static int sensor_read(struct ap3216c_dev *dev)
88  {
89      int ret = 0;
90      char str[50];
91 
92      SENSOR_FLOAT_DATA_GET(ret, IN_INTENSITY_BOTH_SCALE, str,
 als_scale);
93      SENSOR_INT_DATA_GET(ret, IN_INTENSITY_BOTH_RAW, str, als_raw);
94      SENSOR_INT_DATA_GET(ret, IN_INTENSITY_IR_RAW, str, ir_raw);
95      SENSOR_INT_DATA_GET(ret, IN_PROXIMITY_RAW, str, ps_raw);
96 
97      /* 将ALS转换为实际lux */
98      dev->als_act = dev->als_scale * dev->als_raw;
99      return ret;
100 }
101
102 /*
103  * @description     : main主程序
104  * @param - argc    : argv数组元素个数
105  * @param - argv    : 具体参数
106  * @return          : 0 成功;其他 失败
107  */
108 int main(int argc, char *argv[])
109 {
110     int ret = 0;
111
112     if (argc != 1) {
113         printf("Error Usage!\r\n");
114         return -1;
115     }
116
117     while (1) {
118         ret = sensor_read(&ap3216c);
119         if(ret == 0) {          /* 数据读取成功 */
120             printf("\r\n原始值:\r\n");
121             printf("als = %d, ps = %d, ir = %d\r\n", ap3216c.als_raw, 
ap3216c.ps_raw, ap3216c.ir_raw);
122             printf("实际值:");
123             printf("act als = %.2f lx\r\n", ap3216c.als_act);
124         }
125         usleep(100000); /*100ms */
126     }
127     return 0;
128 }

第17~19行,SENSOR_FLOAT_DATA_GET宏用来读取指定路径的文件内容,然后将读到的浮点型字符串数据转换为具体的浮点数据。
第21~24行,SENSOR_INT_DATA_GET宏用来读取指定路径的文件内容,然后将读到的整数型字符串数据转换为具体的整数数据。
第28~33行,需要操作的文件路径。
第36~41行,文件路径索引,要和file_path里面的文件顺序对应。
第60~80行,file_data_read函数用于读取指定文件,第一个参数filename是要读取的文件路径,第二个参数str为读取到的文件内容,为字符串类型。第64行fopen函数打开指定的文件流,第71行调用fscanf函数进行格式化读取,也就是按照字符串方式读取文件,文件内容保存到str参数里面。
第87~100行,sensor_read函数用于读取AP3216C传感器数据,包括光强度、接触距离、红外线强度的原始值,最后将获取到的原始值转换为具体的数值。
第108~126行,main函数,在while循环中调用sensor_read函数读取AP3216C数据,并将读到的数据打印出来。
37.4运行测试
37.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件前面的实验基本一样,只是将obj-m变量的值改为“iio_ap3216c.o”,Makefile内容如下所示:
示例代码28.7.1.1 Makefile文件

1  KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
...... 
4  obj-m := iio_ap3216c.o
......
11 clean:
12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为“iio_ap3216c.o”。

输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“iio_ap3216c.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译iio_ap3216cApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc iio_ap3216cApp.c -o iio_App
编译成功以后就会生成iio_App这个应用程序。
37.4.2 运行测试
在Ubuntu中将上一小节编译出来的iio_ap3216c.ko和iio_App通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push iio_ap3216c.ko iio_App /lib/modules/4.19.232
加载驱动。
depmod //第一次加载驱动的时候需要运行此命令
modprobe iio_app3216c //加载驱动模块
当驱动模块加载成功以后使用iio_App来测试,输入如下命令:
./ iio_App
测试APP会不断的从AP3216C中读取数据,然后输出到终端上,如图37.4.2.1所示:
在这里插入图片描述

图37.4.2.1 获取到的AP3216C数据
大家可以用手电筒照一下AP3216C,或者手指靠近AP3216C来观察传感器数据有没有变化。

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值