赞
踩
1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
工业场合里面也有大量的模拟量和数字量之间的转换,也就是我们常说的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来观察传感器数据有没有变化。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。