自学内容网 自学内容网

Linux-IIO驱动实验

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


IIO 子系统简介

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,大家不要看到“工业”两个字就觉得 IIO是只用于工业领域的。大家一般在搜索 IIO 子系统的时候,会发现大多数讲的都是 ADC,这是因为 IIO 就是为 ADC 类传感器准备的,当然了 DAC 也是可以的。大家常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如 IICSPI 等传输给 SOC

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

iio_dev

1iio_dev 结构体

IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备,此设备结构体定义在 include/linux/iio/iio.h 文件中,结构体内容如下(有省略)

我们来看一下 iio_dev 结构体中几个比较重要的成员变量:

477 行,modes 为设备支持的模式,可选择的模如表 75.1.1.1 所示:

478 行,currentmode 为当前模式。

483 行,buffer 为缓冲区。

484 行,buffer_list 为当前匹配的缓冲区列表。

485 行,scan_bytes 为捕获到,并且提供给缓冲区的字节数。

488 行,available_scan_masks 为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。

490 行,active_scan_mask 为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。

491 行,scan_timestamp 为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。

493 行,trig IIO 设备当前触发器,当使用缓冲模式的时候。

494 行,pollfunc 为一个函数,在接收到的触发器上运行。

496 行,channels IIO 设备通道,为 iio_chan_spec 结构体类型,稍后会详细讲解 IIO通道。

497 行,num_channels IIO 设备的通道数。

501 行,name IIO 设备名字。

502 行,info iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!我们从用户空间读取 IIO 设备内部数据,最终调用的就是 iio_info 里面的函数。稍后会详细讲解 iio_info 结构体。

504 行,setup_ops iio_buffer_setup_ops 结构体类型,内容如下:

可以看出 iio_buffer_setup_ops 里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定的话就默认使用 iio_triggered_buffer_setup_ops

继续回到示例代码 75.1.1.1 中第 505 行,chrdev 为字符设备,由 IIO 内核创建。

2iio_dev 申请与释放

在使用之前要先申请 iio_dev,申请函数为 iio_device_alloc,函数原型如下:

struct iio_dev *iio_device_alloc(int sizeof_priv)

函数参数和返回值含义如下:

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

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

一般 iio_device_alloc iio_priv 之间的配合使用如下所示:

1 行,icm20608_dev 是自定义的设备结构体。

2 行,indio_dev iio_dev 结构体变量指针。

5 行,使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 icm2060_dev 的内存。

10 行,使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址。

如果要释放 iio_dev,需要使用 iio_device_free 函数,函数原型如下:

void iio_device_free(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev需要释放的 iio_dev

返回值:无。

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

3iio_dev 注册与注销

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

int iio_device_register(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev需要注册的 iio_dev

返回值0,成功;其他值,失败。

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

void iio_device_unregister(struct iio_dev *indio_dev)

函数参数和返回值含义如下:

indio_dev需要注销的 iio_dev

返回值0,成功;其他值,失败。

iio_info

iio_dev 有个成员变量:info,为 iio_info 结构体指针变量,这个是我们在编写 IIO 驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info 里面。iio_info结构体定义在 include/linux/iio/iio.h 中,结构体定义如下(有省略)

第 355 行,attrs 是通用的设备属性。

357 370 行,分别为 read_raw write_raw 函数,这两个函数就是最终读写设备内部数据的操作函数,需要程序编写人员去实现的。比如应用读取一个陀螺仪传感器的原始数据,那么最终完成工作的就是 read_raw 函数,我们需要在 read_raw 函数里面实现对陀螺仪芯片的读取操作。同理,write_raw 是应用程序向陀螺仪芯片写数据,一般用于配置芯片,比如量程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下:

indio_dev需要读写的 IIO 设备。

chan:需要读取的通道。

valval2:对于 read_raw 函数来说 val val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。val val2 共同组成具体值,val 是整数部分,val2 是小数部分。但是val2 也是对具体的小数部分扩大 N 倍后的整数值,因为不能直接从内核向应用程序返回一个小数值。比如现在有个值为 1.00236,那么 val 就是 1vla2 理论上来讲是 0.00236,但是我们需要对 0.00236 扩大 N 倍,使其变为整数,这里我们扩大 1000000 倍,那么 val2 就是 2360。因此val=1,val2=2360。扩大的倍数我们不能随便设置,而是要使用 Linux 定义的倍数,Linux 内核里面定义的数据扩大倍数,或者说数据组合形式如表 75.1.2.1 所示:

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

376 行的 write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式, write_raw_get_fmt 函数决定了 wtite_raw 函数中 val val2 的意义,也就是表 75.1.2.1 中的组合 形式。比如我们需要在应用程序中设置 ICM20608 加速度计的量程为±8g,那么分辨率就是 16/65536≈0.000244,我们在 write_raw_get_fmt 函数里面设置加速度计的数据格式为 IIO_VAL_INT_PLUS_MICRO。那么我们在应用程序里面向指定的文件写入 0.000244 以后,最终传递给内核驱动的就是 0.000244*1000000=244。也就是 write_raw 函数的 val 参数为 0val2参数为 244

iio_chan_spec

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那么这个 ADC 就有 8 个通道。我们本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(XYZ)、三轴加速度计(XYZ)和一路温度,也就是一共有 7 路数据,因此就有 7 个通道。注意,三轴陀螺仪或加速度计的 XYZ 这三个轴,每个轴都算一个通道。

Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中,内容如下:

来看一下 iio_chan_spec 结构体中一些比较重要的成员变量:

224 行,type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类型,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下:

从示例代码 75.1.3.2 可以看出,目前 Linux 内核支持的传感器类型非常丰富,而且支持类型也会不断的增加。如果是 ADC,那就是 IIO_VOLTAGE 类型。如果是 ICM20608 这样的多轴传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是IIO_ACCEL 类型,温度部分就是 IIO_TEMP

继续来看示例代码 75.1.3.1 中的 iio_chan_spec 结构体,第 225 行,当成员变量 indexed 1 时候,channel 为通道索引。

226 行,当成员变量 modified 1 的时候,channel2 为通道修饰符。Linux 内核给出了可用的通道修饰符,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下(有省略)

比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCELXYZ 这三个轴就用 channel2的通道修饰符来区分。IIO_MOD_XIIO_MOD_YIIO_MOD_Z 就分别对应 XYZ 这三个轴。通道修饰符主要是影响 sysfs 下的通道文件名字,后面我们会讲解 sysfs 下通道文件名字组成形式。

继续回到示例代码 75.1.3.1,第 227 行的 address 成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道,它的数据首地址就是 0X3Baddress 也可以用作其他功能,自行选择,也可以不使用 address,一切以实际情况为准。

228 行,当使用触发缓冲区的时候,scan_index 是扫描索引。

229~236scan_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_CPUIIO_BE(大端)IIO_LE(小 端)

237 行,info_mask_separate 标记某些属性专属于此通道,include/linux/iio/types.h 文件中的 iio_chan_info_enum 枚举类型描述了可选的属性值,如下所示:

比如 ICM20608 加速度计的 XYZ 这三个轴,在 sysfs 下这三个轴肯定是对应三个不同的文件,我们通过读取这三个文件就能得到每个轴的原始数据。IIO_CHAN_INFO_RAW 这个属性表示原始数据,当我们在配置 XYZ 这三个通道的时候,在 info_mask_separate 中使能 IIO_CHAN_INFO_RAW 这个属性,那么就表示在 sysfs 下生成三个不同的文件分别对应 XY、 Z 轴,这三个轴的 IIO_CHAN_INFO_RAW 属性是相互独立的。

238 行,info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。也就是 iio_chan_spec.type 成员变量相同的通道。比如 ICM20608 加速度计的 XYZ 轴他们的 type 都 是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通道的时候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。

239 行,info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。

240 行,info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。

246 行,modified 1 的时候,channel2 为通道修饰符。

247 行,indexed 1 的时候,channel 为通道索引。

248 行,output 表示为输出通道。

249 行,differential 表示为差分通道。


IIO 驱动框架创建

前面我们已经对 IIO 设备、IIO 通道进行了详细的讲解,本节我们就来学习如何搭建 IIO 驱动框架。在上一小节分析 IIO 子系统的时候大家应该看出了,IIO 框架主要用于 ADC 类的传感器,比如陀螺仪、加速度计、磁力计、光强度计等,这些传感器基本都是 IIC 或者 SPI 接口的。

因此 IIO 驱动的基础框架就是 IIC 或者 SPI,我们可以在 IIC SPI 驱动里面在加上上一章讲解的 regmap。当然了,有些 SOC 内部的 ADC 也会使用 IIO 框架,那么这个时候驱动的基础框架就是 platfrom

基础驱动框架建立

我们以 SPI 接口为例,首先是 SPI 驱动框架,如下所示:

示例代码 75.2.1.1 就是标准的 SPI 驱动框架,如果所使用的传感器是 IIC 接口的,那么就是 IIC 驱动框架,没啥好说的。

IIO 设备申请与初始化

IIO 设备的申请、初始化以及注册在 probe 函数中完成,在注销驱动的时候还需要在 remove 函数中注销掉 IIO 设备、释放掉申请的一些内存。添加完 IIO 框架以后的 probe remove 函数 如下所示:

2~7 行,用户自定义的设备结构体。

12 行,IIO 通道数组。

16~71 行, 这部分为 iio_info,当应用程序读取相应的驱动文件的时候,xxx_read_raw函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱动写数据的时候,xxx_write_raw 函数就会执行。因此 xxx_read_raw xxx_write_raw 这两个函数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。

79~114 行,xxx_probe 函数,此函数的核心就是分配并初始化 iio_dev,最后向内核注册 iio_dev。第 86 行调用 devm_iio_device_alloc 函数分配 iio_dev 内存,这里连用户自定义的设备 结构体变量内存一起申请了。第 91 行调用 iio_priv 函数从 iio_dev 中提取出私有数据,这个私 有数据就是设备结构体变量。第 97~102 行初始化 iio_dev,重点是第 98 行设置 iio_dev info成员变量。第 101 行设置 iio_dev 的通道。初始化完成以后就要调用 iio_device_register 函数向内核注册 iio_dev。整个过程就是:申请 iio_dev、初始化、注册,和我们前面讲解的其他驱动框架步骤一样。

121~134 行,xxx_remove 函数里面需要做的就是释放 xxx_probe 函数申请到的 IIO 相关资源,比如第 131 行,使用 iio_device_unregister 注销掉前面注册的 iio_dev。由于前面我们使用 devm_iio_device_alloc 函数申请的 iio_dev,因此不需要在 remove 函数中手动释放 iio_dev

IIO 框架示例就讲解到这里,剩下的就是根据所使用的具体传感器,在 IIO 驱动框架里面添加相关的处理,接下来我们就以正点原子 I.MX6ULL 开发板上的 ICM20608 为例,进行 IIO 驱 动实战!


实验程序编写

接下来我们就直接使用 IIO 驱动框架编写 ICM20608 驱动,ICM20608 驱动核心就是 SPI,上一章我们也讲解了如何在 SPI 总线上使用 regmap。因此本章 ICM20608 驱动底层没啥好讲解的,重点是如何套上 IIO 驱动框架,因此关于 ICM20608 芯片内部寄存器、SPI 驱动、regmap 等不会做讲解,有什么不懂的可以看前面相应的实验章节。

本实验对应的例程路径为:开发板光盘01、程序源码02Linux 驱动例程27_iiospi

使能内核 IIO 相关配置

Linux 内核默认使能了 IIO 子系统,但是有一些 IIO 模块没有选择上,这样会导致我们编译驱动的时候会提示某些 API 函数不存在,需要使能的项目如下:

ICM20608 IIO 驱动框架搭建

1、驱动框架搭建

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

首先我们基于上一章实验,搭建出 ICM20608 IIO 驱动框架,IIO 框架部分我们已经在示例代码 75.2.2.1 中进行了详细的讲解。新建名为 icm20608.c 的驱动文件,搭建好的 ICM20608IIO 驱动框架内容如下所示:

具体参考正点原子驱动开发手册。

关于 ICM20608 IIO 驱动框架就讲解到这里,接下来就是测试此驱动框架。

2、驱动框架测试

我们已经搭建好了 ICM20608 IIO 驱动框架,通道也已经设置好了,虽然还不能直接读取到 ICM20608 的原始数据,但是我们可以通过驱动框架来窥探 IIO 在用户空间的存在方式。

我们在 icm20608_probe 函数里面设置了打印 ICM20608 ID 值,因此如果驱动加载成功,SPI 工作正常的话就会读取 ICM20608 ID 值并打印出来,如图 75.3.2.1 所示:

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

从图 75.3.2.2 可以看出,此时有两个 IIO 设备“iio:device0”,iio:device0 I.MX6ULL 内部 ADCiio:device1 才是 ICM20608。大家进入到对应的设备目录就可以看出对应的 IIO 设备,我们进入图 75.3.2.2 中的“iio:device1”目录,此目录下的内容如图 75.3.2.3 所示:

从图 75.3.2.3 可以看出,iio:device1 对应 spi2.0 上的设备,也就是 ICM20608,此目录下有很多文件,比如 in_accel_scalein_accel_x_calibiasin_accel_x_raw 等,这些就是我们设置的通 道。in_accel_scale 就是加速度计的比例,也就是分辨率(量程)in_accel_x_calibias 就是加速度 计 X 轴的校准值,in_accel_x_raw 就是加速度计的 X 轴原始值。我们在配置通道的时候,设置了类型相同的所有通道共用 SCALE,所以这里只有一个 in_accel_scale,而 XYZ 轴的原始值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

更多后续内容参考正点原子驱动开发手册。


原文地址:https://blog.csdn.net/qq_28576837/article/details/144309498

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!