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

1.IIO子系统简介

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

1.1 iio_dev

iio_dev 结构体


第 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_dev 申请与释放

iio_dev 注册与注销

1.2 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 是应用程序向陀螺仪芯片写数据,一般用于配置芯片,比如量程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下:mask:掩码,用于指定我们读取的是什么数据,比如 ICM20608 这样的传感器,他既有原始的测量数据,比如 X,Y,Z 轴的陀螺仪、加速度计等,也有测量范围值,或者分辨率。比如加速度计测量范围设置为±16g,那么分辨率就是 32/65536≈0.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 参数为 0,val2参数为 244

1.3 iio_chan_spec

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那么这个 ADC 就有 8 个通道。我们本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,也就是一共有 7 路数据,因此就有 7 个通道。注意,三轴陀螺仪或加速度计的 X、Y、Z 这三个轴,每个轴都算一个通道。
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_ACCEL,X、Y、Z 这三个轴就用 channel2的通道修饰符来区分。IIO_MOD_X、IIO_MOD_Y、IIO_MOD_Z 就分别对应 X、Y、Z 这三个轴。通道修饰符主要是影响 sysfs 下的通道文件名字,后面我们会讲解 sysfs 下通道文件名字组成形式。
继续回到示例代码 75.1.3.1,第 227 行的 address 成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道,它的数据首地址就是 0X3B。address 也可以用作其他功能,自行选择,也可以不使用 address,一切以实际情况为准。
第 228 行,当使用触发缓冲区的时候,scan_index 是扫描索引。
第 229~236,scan_type 是一个结构体,描述了扫描数据在缓冲区中的存储格式。我们依次来看一下 scan_type 各个成员变量的涵义:
比如 ICM20608 加速度计的 X、Y、Z 这三个轴,在 sysfs 下这三个轴肯定是对应三个不同的文件,我们通过读取这三个文件就能得到每个轴的原始数据。IIO_CHAN_INFO_RAW 这个属性表示原始数据,当我们在配置 X、Y、Z 这三个通道的时候,在 info_mask_separate 中使能IIO_CHAN_INFO_RAW 这个属性,那么就表示在 sysfs 下生成三个不同的文件分别对应 X、Y、Z 轴,这三个轴的 IIO_CHAN_INFO_RAW 属性是相互独立的。
第 238 行,info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。也就是iio_chan_spec.type 成员变量相同的通道。比如 ICM20608 加速度计的 X、Y、Z 轴他们的 type 都是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通道的时候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。
第 239 行,info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。
第 240 行,info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。
第 246 行,modified 为 1 的时候,channel2 为通道修饰符。
第 247 行,indexed 为 1 的时候,channel 为通道索引。
第 248 行,output 表示为输出通道。
第 249 行,differential 表示为差分通道。

2.IIO驱动框架创建

2.1 基础驱动框架建立

同spi和IIC驱动框架

2.2 IIO设备申请与初始化


第 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。

3. 驱动代码

3.1 使能内核设置

3.2 代码

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>                        /* 异步通知 */
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include "icm20608reg.h"
#include <linux/regmap.h>
/* IIO框架 */
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/unaligned/be_byteshift.h>#define ICM20608_NAME            "icm20608_"#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE 326800000/* icm20608 陀螺仪分辨率,对应 250、500、1000、2000,计算方法:
* 以正负 250 度量程为例,500/2^16=0.007629,扩大 1000000 倍,就是 7629
*/static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};/* icm20608 加速度计分辨率,对应 2、4、8、16 计算方法:
* 以正负 2g 量程为例,4/2^16=0.000061035,扩大 1000000000 倍,就是 61035
*/
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};/* 通道修饰符,用来指定 X、Y、Z 轴 *//* 行设置相同类型的通道IIO_CHAN_INFO_SCALE 属性相同,“scale”是比例的意思,在这里就是量程(分辨率),因为ICM20608 的陀螺仪和加速度计的量程是可以调整的,量程不同分辨率也就不同。陀螺仪或加速度计的三个轴也是一起设置的,因此对于陀螺仪或加速度计而言,X、Y、Z 这三个轴的量程是共享的 *//* 设置每个通道的 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS这两个属性都是独立的,IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,这个肯定是每个通道独立的。IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是ICM20608 的特性,不是所有的传感器都有校准值,一切都要以实际所使用的传感器为准 *//* 设置扫描数据类型,也就是 ICM20608 原始数据类型 *//* 为有符号类型、实际位数 16bit,存储位数 16bit,大端模式 */
#define ICM20608_CHAN(_type, _channel2, _index) \
{                                               \.type = _type,                                \.modified = 1,                                \.channel2 = _channel2,                    \.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),     \.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_CALIBBIAS), \.scan_index = _index, \.scan_type = { \.sign = 's', \.realbits = 16, \.storagebits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \
}/*
* ICM20608 的扫描元素,3 轴加速度计、
* 3 轴陀螺仪、1 路温度传感器,1 路时间戳
*/
/* 自定义的扫描索引枚举类型 inv_icm20608_scan,包括陀螺仪、加速度计的 6
个通道,温度计的 1 个通道、以及 1 个 ICM20608 时间戳通道 */
enum inv_icm20608_scan {INV_ICM20608_SCAN_ACCL_X,INV_ICM20608_SCAN_ACCL_Y,INV_ICM20608_SCAN_ACCL_Z,INV_ICM20608_SCAN_TEMP,INV_ICM20608_SCAN_GYRO_X,INV_ICM20608_SCAN_GYRO_Y,INV_ICM20608_SCAN_GYRO_Z,INV_ICM20608_SCAN_TIMESTAMP,
};struct icm20608_dev {struct spi_device *spi;          /* spi 设备 */struct regmap *regmap;              /* regmap */struct regmap_config regmap_config;struct mutex lock;
};/*
* icm20608 通道,1 路温度通道,3 路陀螺仪,3 路加速度计
*/
/* 这里定义了 7 个通道,分别是:1 个温度通道,3 个陀螺仪
通道(X、Y、Z),3 个加速度计通道(X、Y、Z)。 */
static const struct iio_chan_spec icm20608_channels[] = {
/* 温度通道 */{.type = IIO_TEMP,/* IIO_CHAN_INFO_RAW为温度通道的原始值,IIO_CHAN_INFO_OFFSET 是 ICM20608 温度 offset 值 *//* IIO_CHAN_INFO_SCALE 是 ICM20608 的比例,也就是一个单位的原始值为多少℃ */.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)| BIT(IIO_CHAN_INFO_OFFSET)| BIT(IIO_CHAN_INFO_SCALE),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},/* IIO_MOD_X、IIO_MOD_Y 和 IIO_MOD_Z 分别是 X、Y、Z 这三个轴
的修饰符 */ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X,INV_ICM20608_SCAN_GYRO_X),ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z), ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),
};/*
* @description : 读取 icm20608 指定寄存器值,读取一个寄存器
* @param – dev : icm20608 设备
* @param – reg : 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 ret;unsigned int data;ret = regmap_read(dev->regmap, reg, &data);return (u8)data;
}/*
* @description : 向 icm20608 指定寄存器写入指定的值,写一个寄存器
* @param – dev : icm20608 设备
* @param – reg : 要写的寄存器
* @param – data : 要写入的值
* @return : 无
*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{regmap_write(dev->regmap, reg, value);
}/*
* @description : ICM20608 内部寄存器初始化函数
* @param – spi : 要操作的设备
* @return : 无
*/
void icm20608_reginit(struct icm20608_dev *dev)
{u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value);icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18);icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04);icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); icm20608_write_onereg(dev, ICM20_INT_ENABLE, 0x01);
}/*
* @description : 读取 ICM20608 传感器数据,可以用于陀螺仪、加速度计、温度
* @param – dev : icm20608 设备
* @param - reg : 要读取的通道寄存器首地址。
* @param – anix : 需要读取的通道,比如 X,Y,Z。
* @param - *val : 保存读取到的值。
* @return : 0,成功;其他值,错误
*/
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg, int axis, int *val)
{int ind, result;__be16 d;ind = (axis - IIO_MOD_X) * 2;result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;*val = (short)be16_to_cpup(&d);return IIO_VAL_INT;
}
/*
* @description : 读取 ICM20608 陀螺仪、加速度计、温度通道值
* @param - indio_dev : iio 设备
* @param - chan : 通道。
* @param - val : 保存读取到的通道值。
* @return : 0,成功;其他值,错误
*/
static int icm20608_read_channel_data(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val)
{struct icm20608_dev *dev = iio_priv(indio_dev);int ret = 0;switch (chan->type) {case IIO_ANGL_VEL: /* 读取陀螺仪数据 */ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val); /* channel2 为 X、Y、Z 轴 */break;case IIO_ACCEL: /* 读取加速度计数据 */ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2 为 X、Y、Z 轴 */break;case IIO_TEMP: /* 读取温度 */ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val); break;default:ret = -EINVAL;break;}return ret;
}/*
* @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执
* :行,此函数里面会从传感器里面读取各种数据,然后上传给应用。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - val : 读取的值,如果是小数值的话,val 是整数部分。
* @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
* @param - mask : 掩码。
* @return : 0,成功;其他值,错误
*/
static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2, long mask)
{struct icm20608_dev *dev = iio_priv(indio_dev);int ret = 0;unsigned char regdata = 0;switch (mask) {case IIO_CHAN_INFO_RAW: /* 读取 ICM20608 加速度计、陀螺仪、温度传感器原始值 */iio_device_claim_direct_mode(indio_dev);/* 保持 direct 模式 */mutex_lock(&dev->lock); /* 上锁 */ret = icm20608_read_channel_data(indio_dev, chan, val); mutex_unlock(&dev->lock); /* 释放锁 */iio_device_release_direct_mode(indio_dev);return ret;case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_ANGL_VEL:mutex_lock(&dev->lock);regdata = (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG) & 0X18) >> 3;*val = 0;*val2 = gyro_scale_icm20608[regdata];mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_MICRO; /* 值为 val+val2/1000000 */case IIO_ACCEL:mutex_lock(&dev->lock);regdata = (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG) & 0X18) >> 3;*val = 0;*val2 = accel_scale_icm20608[regdata];;mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_NANO;/* 值为 val+val2/1000000000 */case IIO_TEMP: *val = ICM20608_TEMP_SCALE/ 1000000;*val2 = ICM20608_TEMP_SCALE % 1000000;return IIO_VAL_INT_PLUS_MICRO; /* 值为 val+val2/1000000 */default:return -EINVAL;}return ret;case IIO_CHAN_INFO_OFFSET: /* ICM20608 温度传感器 offset 值 */switch (chan->type) {case IIO_TEMP:*val = ICM20608_TEMP_OFFSET;return IIO_VAL_INT;default:return -EINVAL;}return ret;case IIO_CHAN_INFO_CALIBBIAS: /* ICM20608 加速度计和陀螺仪校准值 */switch (chan->type) {case IIO_ANGL_VEL: /* 陀螺仪的校准值*/mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->lock);return ret;case IIO_ACCEL: /* 加速度计的校准值*/mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->lock);return ret;default:return -EINVAL;}default:return ret -EINVAL;}
}/* @description : 设置 ICM20608 传感器,可以用于陀螺仪、加速度计设置
* @param - dev : icm20608 设备
* @param - reg : 要设置的通道寄存器首地址。
* @param – anix : 要设置的通道,比如 X,Y,Z。
* @param - val : 要设置的值。
* @return : 0,成功;其他值,错误
*/
static int icm20608_sensor_set(struct icm20608_dev *dev, int reg, int axis, int val)
{int ind, result;__be16 d = cpu_to_be16(val);ind = (axis - IIO_MOD_X) * 2;result = regmap_bulk_write(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;return 0;
}/* @description : 设置 ICM20608 的陀螺仪计量程(分辨率)
* @param - dev : icm20608 设备
* @param - val : 量程(分辨率值)。
* @return : 0,成功;其他值,错误
*/
static int icm20608_write_gyro_scale(struct icm20608_dev *dev, int val)
{int result, i;u8 d;for (i = 0; i < ARRAY_SIZE(gyro_scale_icm20608); ++i) {if (gyro_scale_icm20608[i] == val) {d = (i << 3);result = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);if (result)return result;return 0;}}return -EINVAL;
}/*
* @description : 设置 ICM20608 的加速度计量程(分辨率)
* @param - dev : icm20608 设备
* @param - val : 量程(分辨率值)。
* @return : 0,成功;其他值,错误
*/
static int icm20608_write_accel_scale(struct icm20608_dev *dev, int val)
{int result, i;u8 d;for (i = 0; i < ARRAY_SIZE(accel_scale_icm20608); ++i) {if (accel_scale_icm20608[i] == val) {d = (i << 3);result = regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, d);if (result)return result;return 0;}}return -EINVAL;
}/*
* @descriptio : 写函数,当向 sysfs 中的文件写数据的时候最终此函数会
* :执行,一般在此函数里面设置传感器,比如量程等。
* @param - indio_dev : iio_dev
* @param – chan : 通道
* @param – val : 应用程序写入的值,如果是小数值的话,val 是整数部分。
* @param - val2 : 应用程序写入的值,如果是小数值的话,val2 是小数部分。
* @return : 0,成功;其他值,错误
*/
static int icm20608_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val, int val2, long mask)
{struct icm20608_dev *dev = iio_priv(indio_dev);int ret = 0;iio_device_claim_direct_mode(indio_dev); switch (mask) {case IIO_CHAN_INFO_SCALE: /* 设置陀螺仪和加速度计的分辨率 */switch (chan->type) {case IIO_ANGL_VEL: /* 设置陀螺仪 */mutex_lock(&dev->lock);ret = icm20608_write_gyro_scale(dev, val2);mutex_unlock(&dev->lock);break;case IIO_ACCEL: /* 设置加速度计 */mutex_lock(&dev->lock);ret = icm20608_write_accel_scale(dev, val2);mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;case IIO_CHAN_INFO_CALIBBIAS: /* 设置陀螺仪和加速度计的校准值 */switch (chan->type) {case IIO_ANGL_VEL: /* 设置陀螺仪校准值 */mutex_lock(&dev->lock);ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,chan->channel2, val);mutex_unlock(&dev->lock);break;case IIO_ACCEL: /* 加速度计校准值 */mutex_lock(&dev->lock);ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,chan->channel2, val);mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}iio_device_release_direct_mode(indio_dev); return ret;}/*
* @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传
* :感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间
* : 应该扩大多少倍,此函数就是用来设置这个的。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - mask : 掩码
* @return : 0,成功;其他值,错误
*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, long mask)
{switch (mask) {case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_ANGL_VEL: return IIO_VAL_INT_PLUS_MICRO;default:return IIO_VAL_INT_PLUS_NANO;}default:return IIO_VAL_INT_PLUS_MICRO;}return -EINVAL;}/*
* iio_info 结构体变量
*/
static const struct iio_info icm20608_info = {.read_raw = icm20608_read_raw,.write_raw = icm20608_write_raw,.write_raw_get_fmt = &icm20608_write_raw_get_fmt,
};/*
* @description : spi 驱动的 probe 函数,当驱动与
* 设备匹配以后此函数就会执行
* @param – spi : spi 设备
* @return : 0,成功;其他值,失败
*/
static int icm20608_probe(struct spi_device *spi)
{int ret;struct icm20608_dev *dev;struct iio_dev *indio_dev;/* 1、申请 iio_dev 内存 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev)return -ENOMEM;/* 2、获取 icm20608_dev 结构体地址 */dev = iio_priv(indio_dev);dev->spi = spi;spi_set_drvdata(spi, indio_dev);mutex_init(&dev->lock);/* 3、iio_dev 的其他成员变量 */indio_dev->dev.parent = &spi->dev;indio_dev->info = &icm20608_info;indio_dev->name = ICM20608_NAME; indio_dev->modes = INDIO_DIRECT_MODE;/* 直接模式,提供 sysfs 接口 */indio_dev->channels = icm20608_channels;indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);/* 4、注册 iio_dev */ret = iio_device_register(indio_dev);if (ret < 0) {dev_err(&spi->dev, "iio_device_register failed\n");goto err_iio_register;}/* 5、初始化 regmap_config 设置 */dev->regmap_config.reg_bits = 8;             /* 寄存器长度 8bit */dev->regmap_config.val_bits = 8;            /* 值长度 8bit */dev->regmap_config.read_flag_mask = 0x80;     /* 读掩码设置为 0X80 *//* 6、初始化 SPI 接口的 regmap */dev->regmap = regmap_init_spi(spi, &dev->regmap_config);if (IS_ERR(dev->regmap)) {ret = PTR_ERR(dev->regmap);goto err_regmap_init;}/* 7、初始化 spi_device */spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);/* 初始化 ICM20608 内部寄存器 */icm20608_reginit(dev); return 0;err_regmap_init:iio_device_unregister(indio_dev);
err_iio_register:return ret;
}/*
* @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
* @param - spi : spi 设备
* @return : 0,成功;其他负值,失败
*/
static int icm20608_remove(struct spi_device *spi)
{struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);/* 1、删除 regmap */regmap_exit(dev->regmap);/* 2、注销 IIO */iio_device_unregister(indio_dev);return 0;
}/* 传统匹配方式 ID 列表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI 驱动结构体 */
static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = "icm20608",.of_match_table = icm20608_of_match,},.id_table = icm20608_id,
};/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");MODULE_INFO(intree, "Y");

4.应用代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "signal.h"#include <linux/input.h>/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\ret = file_data_read(file_path[index], str);\dev->member = atof(str);\/* 字符串转数字,将整数字符串转换为整数数值 */
#define SENSOR_INT_DATA_GET(ret, index, str, member)\ret = file_data_read(file_path[index], str);\dev->member = atoi(str);\/* icm20608 iio 框架对应的文件路径 */
static char *file_path[] = {"/sys/bus/iio/devices/iio:device1/in_accel_scale","/sys/bus/iio/devices/iio:device1/in_accel_x_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_x_raw","/sys/bus/iio/devices/iio:device1/in_accel_y_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_y_raw","/sys/bus/iio/devices/iio:device1/in_accel_z_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_z_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_scale","/sys/bus/iio/devices/iio:device1/in_anglvel_x_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_x_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_y_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_y_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_z_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_z_raw","/sys/bus/iio/devices/iio:device1/in_temp_offset","/sys/bus/iio/devices/iio:device1/in_temp_raw","/sys/bus/iio/devices/iio:device1/in_temp_scale",
};/* 文件路径索引,要和 file_path 里面的文件顺序对应 */
enum path_index {IN_ACCEL_SCALE = 0,IN_ACCEL_X_CALIBBIAS,IN_ACCEL_X_RAW,IN_ACCEL_Y_CALIBBIAS,IN_ACCEL_Y_RAW,IN_ACCEL_Z_CALIBBIAS,IN_ACCEL_Z_RAW,IN_ANGLVEL_SCALE,IN_ANGLVEL_X_CALIBBIAS,IN_ANGLVEL_X_RAW,IN_ANGLVEL_Y_CALIBBIAS,IN_ANGLVEL_Y_RAW,IN_ANGLVEL_Z_CALIBBIAS,IN_ANGLVEL_Z_RAW,IN_TEMP_OFFSET,IN_TEMP_RAW,IN_TEMP_SCALE,
};/*
* icm20608 数据设备结构体
*/
struct icm20608_dev{int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;int accel_x_raw, accel_y_raw, accel_z_raw;int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;int gyro_x_raw, gyro_y_raw, gyro_z_raw;int temp_offset, temp_raw;float accel_scale, gyro_scale, temp_scale;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;
};struct icm20608_dev icm20608;/*
* @description : 读取指定文件内容
* @param – filename : 要读取的文件路径
* @param - str : 读取到的文件字符串
* @return : 0 成功;其他 失败
*/
static int file_data_read(char *filename, char *str)
{int ret = 0;FILE *data_stream;data_stream = fopen(filename, "r"); /* 只读打开 */if(data_stream == NULL) {printf("can't open file %s\r\n", filename);return -1;}ret = fscanf(data_stream, "%s", str);if(!ret) {} else if(ret == EOF) {/* 读到文件末尾的话将文件指针重新调整到文件头 */fseek(data_stream, 0, SEEK_SET); }fclose(data_stream); /* 关闭文件 */ return 0;
}/*
* @description : 获取 ICM20608 数据
* @param - dev : 设备结构体
* @return : 0 成功;其他 失败
*/
static int sensor_read(struct icm20608_dev *dev)
{int ret = 0;char str[50];/* 1、获取陀螺仪原始数据 */SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);/* 2、获取加速度计原始数据 */SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);/* 3、获取温度值 */SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);/* 3、转换为实际数值 */dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;dev->temp_act = ((dev->temp_raw - dev->temp_offset) /dev->temp_scale) + 25;return ret;
}/*
* @description : main 主程序
* @param – argc : argv 数组元素个数
* @param – argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{int ret = 0;if (argc != 1) {printf("Error Usage!\r\n");return -1;}while (1) {ret = sensor_read(&icm20608);if(ret == 0) { /* 数据读取成功 */printf("\r\n 原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n",icm20608.gyro_x_raw, icm20608.gyro_y_raw, icm20608.gyro_z_raw);printf("ax = %d, ay = %d, az = %d\r\n",icm20608.accel_x_raw,icm20608.accel_y_raw,icm20608.accel_z_raw);printf("temp = %d\r\n", icm20608.temp_raw);printf("实际值:");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);printf("act ax = %.2fg, act ay = %.2fg,  act az = %.2fg\r\n", icm20608.accel_x_act, icm20608.accel_y_act, icm20608.accel_z_act);printf("act temp = %.2f°C\r\n", icm20608.temp_act);}usleep(100000); /*100ms */}return 0;
}

Linux IIO驱动相关推荐

  1. STM32MP157驱动开发——Linux IIO驱动(上)

    STM32MP157驱动开发--Linux IIO驱动(上 ) 0.前言 一.IIO 子系统简介 1.iio_dev 结构体 2.iio_dev 申请与释放 3.iio_dev 注册与注销 4.iio ...

  2. STM32MP157驱动开发——Linux IIO驱动(下)

    STM32MP157驱动开发--Linux IIO驱动(下) 0.前言 一.IIO 触发缓冲区 1.IIO 触发器 2.申请触发器 3.释放触发器 4.注册触发器 5.注销触发器 6. IIO 缓冲区 ...

  3. 嵌入式linux IIO驱动

    IIO子系统简介 我们一般搜索IIO子系统,就会发现大多数讲的都是ADC,这是因为IIO就是为ADC类传感器准备的,当然了DAC也是可以的,我们常用的陀螺仪,加速度计,电压/电流测量芯片等内部都是有个 ...

  4. Linux iio驱动学习

    最近在做新项目的充电bringup,kernel 内核版本为5.4版本,使用到的充电电荷泵(charger pump)IC具备采样vbus vbat ibat ibus等功能,以往项目都是通过powe ...

  5. Linux IIO 驱动

    工业场合里面也有大量的模拟量和数字量之间的转换,也就是 ADC 和 DAC. 手机或者手环里面的加速度计.光传感器.陀螺仪.气压计.磁力计等,这些传感器本质上都是ADC,大家注意查看这些传感器的手册, ...

  6. linux iio 设备驱动,FS4412开发板使用Linux IIO驱动框架实现ADC驱动

    1.概述 FS4412开发板有一个4通道(0/1/2).10/12比特精度的 ADC ,其中: 1)ADCIN0: 在核心板中引出 2)ADCIN1: 在核心板中引出 3)ADCIN2: 在核心板中引 ...

  7. Linux设备驱动之IIO子系统——IIO框架及IIO数据结构

    由于需要对ADC进行驱动设计,因此学习了一下Linux驱动的IIO子系统.本文翻译自<Linux Device Drivers Development >–John Madieu,本人水平 ...

  8. linux iio 设备驱动,Linux设备驱动之IIO子系统——IIO框架数据读取,linuxiio

    Linux设备驱动之IIO子系统--IIO框架数据读取,linuxiio IIO DATA ACCESS IIO数据获取 只有两种方法可以使用IIO框架访问数据; 通过sysf通道进行一次性捕获,或通 ...

  9. 【转】 linux iio子系统

    原文网址:http://blog.csdn.net/tsy20100200/article/details/47101661 最近由于工作的需要,接触了Linux iio子系统,对于这个目录其实以前是 ...

最新文章

  1. 万字干货:如何从零开始构建企业级推荐系统?
  2. 【移动端DL框架】当前主流的移动端深度学习框架一览
  3. GitOps:Kubernetes多集群环境下的高效CICD实践
  4. 在Kubernetes集群上部署和管理JFrog Artifactory
  5. 文件系统ext3的文件大小限制
  6. c#调用javascript的方法,有Updatepanel的情况
  7. sql2008 表名为全数字时查询报错
  8. openCV 下载地址
  9. win2012服务器 注册表,第十一章 Windows Server 2012 R2 注册表域注册表编辑器 ---学习笔记...
  10. linux挂载光盘的命令,linux下挂载(mount)光盘镜像文件、移动硬盘
  11. Multipass中文文档-教程
  12. 用ajax表单全部提交
  13. 双 JK 触发器 74LS112 逻辑功能。真值表_数字电路学习笔记(十一):时序逻辑...
  14. 测试硬盘软件hd不能结束进程,终于解决了HD TUNE以及所有其他硬盘检测工具都不能使用的情况。。...
  15. 罗永浩疑回应再被强制执行
  16. 理解实时频谱分析仪的频域电平触发
  17. Android 调用系统打开相机,打开相册获取图片路径
  18. Hadoop集群中添加Snappy解压缩库
  19. 智能车的“耳朵”电磁检测传感器
  20. maps google android版,google maps中文安卓版

热门文章

  1. 简述控制反转ioc_什么是控制反转(Inversion of Control,IoC)
  2. 关于计算机的猜谜游戏,元宵灯谜猜谜语
  3. 怎么注册自媒体账号?技巧分享
  4. 2011年3月18至2011年3月19日 记
  5. 网易雷火 2020暑期实习 面经
  6. Android IOS WebRTC 音视频开发总结(七一)-- H265/H264有何不同
  7. 调试摄像头驱动GC2053和wifi驱动RTL8822cs问题小结
  8. Linux内核发包软件,请教一Linux內核自帶的網絡发包工具: pktgen 的問題
  9. E:Package 'Vim' has no installation candidate问题解决
  10. BUUCTF-RE XOR