驱动程序开发:基于ICM20608六轴传感器 --- 使用Regmap API 的 SPI 读取数据 之 IIO驱动

news/2024/4/20 4:10:12/文章来源:https://blog.csdn.net/morecrazylove/article/details/129159098

目录

  • 一、IIO 子系统简介
  • 二、IIO子系统使用的一些相关的结构体、函数等
    • 1、iio_dev 结构体
      •   ①modes:是选择iio驱动设备支持的工作模式,模式分别有如下:
      •   ②dev:其是一个设备结构体。
      •   ②channels:为 IIO 设备通道规格结构表。
  • 三、IIO驱动框架搭建
    • 1、SPI基础框架搭建
    • 2、基于SPI基础驱动框架上搭建IIO基础驱动框架
      •   ①测试IIO基础驱动框架程序
    • 3、配置IIO设备通道规格结构表
      •   ①了解通道文件命名方式
      •   ①编写IIO驱动程序的配置IIO设备通道规格结构表
    • 4、添加Regmap API
    • 5、完善xxx_read_raw函数,真正实现通道文件可读取传感器数据
  • 四、完整的驱动程序
    • 1、驱动程序
    • 2、测试现象
    • 3、icm20608APP程序及测试

一、IIO 子系统简介

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

二、IIO子系统使用的一些相关的结构体、函数等

1、iio_dev 结构体

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

struct iio_dev {int				modes;struct device			dev;struct iio_chan_spec const	*channels;int				num_channels;const char			*name;const struct iio_info		*info;/* 注意:这个结构体只列出了该实验驱动使用到的结构体属性,其他省略了,有兴趣的大家可以查看内核源码,有注释的 */
};

  ①modes:是选择iio驱动设备支持的工作模式,模式分别有如下:

在这里插入图片描述
  至于sysfs接口可以简单理解为生成了带有用于用户访问设备文件,也就是说实现sysfs接口,可通过脚本命令“echo”、“cat”访问驱动。详细介绍可以点击该链接,这位博主写的挺详细的📌

  ②dev:其是一个设备结构体。

  回顾一下以前我们使用的普通的IO字符设备驱动、SPI、IIC等是不是都会使用到device结构体呀,什么分配设备号、添加字符设备、创建设备节点等呀。有了这个之后,我们只需要把要操作的一系列字符设备的那个device给实例到该iio_dev->dev下,那么通过调用 iio_device_register 该接口就可以内部生成在内核生成字符设备文件了。
  简单看一下 iio_device_register 函数接口内部是怎么样的,如下:
在这里插入图片描述

  ②channels:为 IIO 设备通道规格结构表。

  通俗一点来说,就是我们获取的传感器信息,如加速度传感器、陀螺仪传感器、温度传感器,获取这三个传感器是通过通道去获取的,那我们怎么知道我们获取的通道数据是哪个传感器的数据呢,因此我们需要给每个通道打上对应传感器的标签,这像一个树状查询表一样,可以继续细分,如加速度传感器有X、Y、Z轴三组数据,我们需要对这三组数据进行细分,对应的管道打上对应的标签。
  先看看 iio_chan_spec 结构体,如下图:
在这里插入图片描述
这里我讲一下比较重要的成员变量:
  224行,type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类型,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下:

enum iio_chan_type {IIO_VOLTAGE, /* 电压类型 */IIO_CURRENT, /* 电流类型 */IIO_POWER, /* 功率类型 */IIO_ACCEL, /* 加速度类型 */IIO_ANGL_VEL, /* 角度类型(陀螺仪) */IIO_MAGN, /* 电磁类型(磁力计) */IIO_LIGHT, /* 灯光类型 */IIO_INTENSITY, /* 强度类型(光强传感器) */IIO_PROXIMITY, /* 接近类型(接近传感器) */IIO_TEMP, /* 温度类型 */IIO_INCLI, /* 倾角类型(倾角测量传感器) */IIO_ROT, /* 旋转角度类型 */IIO_ANGL, /* 转动角度类型(电机旋转角度测量传感器) */IIO_TIMESTAMP, /* 时间戳类型 */IIO_CAPACITANCE, /* 电容类型 */IIO_ALTVOLTAGE, /* 频率类型 */IIO_CCT, /* 笔者暂时未知的类型 */IIO_PRESSURE, /* 压力类型 */IIO_HUMIDITYRELATIVE, /* 湿度类型 */IIO_ACTIVITY, /* 活动类型(计步传感器) */IIO_STEPS, /* 步数类型 */IIO_ENERGY, /* 能量类型(卡路里) */IIO_DISTANCE, /* 距离类型 */IIO_VELOCITY, /* 速度类型 */
};

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

enum iio_modifier {IIO_NO_MOD,IIO_MOD_X, /* X 轴 */IIO_MOD_Y, /* Y 轴 */IIO_MOD_Z, /* Z 轴 */
......
};

  比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL, X、 Y、 Z 这三个轴就用 channel2的通道修饰符来区分。
  第 228 行,当使用触发缓冲区的时候, scan_index 是扫描索引。
  第 229~236, 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(小端)。
  第 237 行, info_mask_separate 标记某些属性专属于此通道, include/linux/iio/types.h 文件中
的 iio_chan_info_enum 枚举类型描述了可选的属性值,如下所示:

enum iio_chan_info_enum {IIO_CHAN_INFO_RAW = 0,IIO_CHAN_INFO_PROCESSED,IIO_CHAN_INFO_SCALE,IIO_CHAN_INFO_OFFSET,
......IIO_CHAN_INFO_DEBOUNCE_TIME,
};

  比如 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 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。

三、IIO驱动框架搭建

1、SPI基础框架搭建

/* *  根据linux内核的程序查找所使用函数的对应头文件。 */  
#include <linux/types.h>
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_number  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/input.h>        //input
#include <linux/platform_device.h>  //platform
#include <linux/delay.h>        //mdelay
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/regmap.h>
#include "icm20608.h"/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi) {int ret = 0;return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi) {int ret = 0;return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}
};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{}
};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.1 驱动模块入口函数 */  
static int __init icm20608_init(void) {  return spi_register_driver(&icm20608_driver);   //注册spi驱动设备
} /* 1.2 驱动模块出口函数 */  
static void __exit icm20608_exit(void) {  spi_unregister_driver(&icm20608_driver);    //注销spi驱动设备
}  /* 1.3 驱动许可和个人信息 */ 
module_init(icm20608_init);
module_exit(icm20608_exit); 
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

2、基于SPI基础驱动框架上搭建IIO基础驱动框架

/**  根据linux内核的程序查找所使用函数的对应头文件。*/
/* 省略... *//* 定义设备名称 */
#define DEVICE_NAME "ICM20608"/* 2.0 设备结构体 */
struct icm20608_dev
{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex mutex_lock; // 互斥锁,保证一次只有一个应用读取该数据
};/* 2.3.1 icm20608 通道, 1 路温度通道, 3 路陀螺仪, 3 路加速度计 */
static const struct iio_chan_spec icm20608_channels[] = {/* 只有配置好每个通道的项,那么才会像一个树状分支那样,每个数字对应每个通道或通道细分的分支 */
};/* 2.3.2.1 */
/* 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。* indio_dev : iio_dev, chan : 通道, val : 读取的值的整数部分,val2 : 读取的值的小数部分, mask : 掩码。*/
static int icm20608_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{int ret = 0;printk("icm20608_read_raw\r\n");return ret;
}/* 2.3.2.2 */
static int icm20608_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{int ret = 0;printk("icm20608_write_raw\r\n");return ret;
}/*  2.3.2.3 *  用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是用来设置这个的。* indio_dev : iio_dev* chan : 通道* mask : 掩码* return : 0,成功;其他值,错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask)
{int ret = 0;printk("icm20608_write_raw_get_fmt\r\n");return ret;
}/* 2.3.2 */
/*  iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw*  函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱*  动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函*  数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。* */
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,.driver_module = THIS_MODULE,
};/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;struct icm20608_dev *dev;struct iio_dev *indio_dev;printk("icm20608_probe successful!\r\n");/* 2.1 申请icm20608_dev结构体大小的内存, 为私人结构体“icm20608_dev”分配内存空间 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev){ret = -ENOMEM;goto fail_iio_dev;}/* 2.2 获取定义的设备结构体首地址和获取spi_device结构体等数据私有化操作 */dev = iio_priv(indio_dev);       // 使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址dev->spi = spi;                  // 设备树匹配成功后,系统会分配struct spi_device *spi,因此将其spi赋值给我们定义的设备结构体上spi_set_drvdata(spi, indio_dev); // 将indio_dev设置为spi->driver_data私有数据mutex_init(&dev->mutex_lock);    // 初始化互斥锁/* 2.3 初始化iio_dev */indio_dev->dev.parent = &spi->dev;                       // 获取spi->dev结构体indio_dev->channels = icm20608_channels;                 // 通道indio_dev->num_channels = ARRAY_SIZE(icm20608_channels); // 通道大小indio_dev->name = DEVICE_NAME;                           // 名称indio_dev->modes = INDIO_DIRECT_MODE;                    // 直接模式,提供sysfs接口indio_dev->info = &icm20608_info;/* 2.4 将iio_dev注册到内核 */ret = iio_device_register(indio_dev);if (ret < 0){dev_err(&spi->dev, "unable to register iio device\r\n");goto fail_iio_register;}/* 2.5 设置SPI的模式 */spi->mode = SPI_MODE_0; // MODE0,CPOL=0, CPHA=0spi_setup(spi); //设置好 spi_device 以后需要使用 spi_setup 配置一下return 0;fail_iio_register:
fail_iio_dev:return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi)
{int ret = 0;/* 获取私有数据 */struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);printk("icm20608_remove finish\r\n");/* 注销iio_dev */iio_device_unregister(indio_dev);return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608"},{}};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.2 驱动模块入口函数 */
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver); // 注册spi驱动设备
}/* 1.3 驱动模块出口函数 */
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver); // 注销spi驱动设备
}/* 1.1 驱动许可和个人信息 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

  ①测试IIO基础驱动框架程序

  Ⅰ、使能Linux内核IIO子系统功能,操作如下:
在这里插入图片描述
在这里插入图片描述
  Ⅱ、加载驱动程序,查看现象,如下:
在这里插入图片描述
在这里插入图片描述
  上面现象,可以看出加载驱动后,已经生成了我们所期望的设备文件“iio:device0”。

3、配置IIO设备通道规格结构表

  ①了解通道文件命名方式

  这里我以已经配置好IIO设备通道规格结构表参数后加载驱动后查看的现象中的“in_accel_x_raw”这个生成的通道文件为例:

在这里插入图片描述
配置加速度传感器x轴原始值程序段,如下图所示:
在这里插入图片描述
通 道 属 性 的 命 名 模 式 为 :[direction][type][index][modifier][info_mask],我们依次来看一下这些命名组织模块:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  ①编写IIO驱动程序的配置IIO设备通道规格结构表

驱动程序:

/**  根据linux内核的程序查找所使用函数的对应头文件。*/
/* 省略... *//* 定义设备名称 */
#define DEVICE_NAME "ICM20608"/* 2.0 设备结构体 */
struct icm20608_dev
{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex mutex_lock; // 互斥锁,保证一次只有一个应用读取该数据
};/* 2.3.1.1 ICM20608的扫描元素,3轴加速计、3轴陀螺仪、1路温度传感器、一路时间戳 */
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,
};/* 2.3.1.2 */
/* _type:通道类型(加速度和陀螺仪), _channel2:当modified为1时,channel2为通道修饰符 */
/* info_mask_shared_by_type:通道共享; IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。*/
/* info_mask_separate:通道独立; IIO_CHAN_INFO_RAW为原始值,IIO_CHAN_INFO_CALIBBIAS校准值  */
#define ICM20608_CHANNEL(_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,                             \},                                                    \}/* 2.3.1 icm20608 通道, 1 路温度通道, 3 路陀螺仪, 3 路加速度计 */
static const struct iio_chan_spec icm20608_channels[] = {/* 使用最元素的配置温度 */{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},/* 2.3.1.3 加速度X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), // X轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), // Y轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), // Z轴/* 2.3.1.4 陀螺仪X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X), // X轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y), // Y轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z), // Z轴
};/* 2.3.2.1 */
/* 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。* indio_dev : iio_dev, chan : 通道, val : 读取的值的整数部分,val2 : 读取的值的小数部分, mask : 掩码。*/
static int icm20608_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{int ret = 0;printk("icm20608_read_raw\r\n");return ret;
}/* 2.3.2.2 */
static int icm20608_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{int ret = 0;printk("icm20608_write_raw\r\n");return ret;
}/*  2.3.2.3 *  用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是用来设置这个的。* indio_dev : iio_dev* chan : 通道* mask : 掩码* return : 0,成功;其他值,错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask)
{int ret = 0;printk("icm20608_write_raw_get_fmt\r\n");return ret;
}/* 2.3.2 */
/*  iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw*  函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱*  动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函*  数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。* */
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,.driver_module = THIS_MODULE,
};/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;struct icm20608_dev *dev;struct iio_dev *indio_dev;printk("icm20608_probe successful!\r\n");/* 2.1 申请icm20608_dev结构体大小的内存, 为私人结构体“icm20608_dev”分配内存空间 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev){ret = -ENOMEM;goto fail_iio_dev;}/* 2.2 获取定义的设备结构体首地址和获取spi_device结构体等数据私有化操作 */dev = iio_priv(indio_dev);       // 使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址dev->spi = spi;                  // 设备树匹配成功后,系统会分配struct spi_device *spi,因此将其spi赋值给我们定义的设备结构体上spi_set_drvdata(spi, indio_dev); // 将indio_dev设置为spi->driver_data私有数据mutex_init(&dev->mutex_lock);    // 初始化互斥锁/* 2.3 初始化iio_dev */indio_dev->dev.parent = &spi->dev;                       // 获取spi->dev结构体indio_dev->channels = icm20608_channels;                 // 通道indio_dev->num_channels = ARRAY_SIZE(icm20608_channels); // 通道大小indio_dev->name = DEVICE_NAME;                           // 名称indio_dev->modes = INDIO_DIRECT_MODE;                    // 直接模式,提供sysfs接口indio_dev->info = &icm20608_info;/* 2.4 将iio_dev注册到内核 */ret = iio_device_register(indio_dev);if (ret < 0){dev_err(&spi->dev, "unable to register iio device\r\n");goto fail_iio_register;}/* 2.5 设置SPI的模式 */spi->mode = SPI_MODE_0; // MODE0,CPOL=0, CPHA=0spi_setup(spi); //设置好 spi_device 以后需要使用 spi_setup 配置一下return 0;fail_iio_register:
fail_iio_dev:return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi)
{int ret = 0;/* 获取私有数据 */struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);printk("icm20608_remove finish\r\n");/* 注销iio_dev */iio_device_unregister(indio_dev);return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608"},{}};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.2 驱动模块入口函数 */
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver); // 注册spi驱动设备
}/* 1.3 驱动模块出口函数 */
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver); // 注销spi驱动设备
}/* 1.1 驱动许可和个人信息 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

现象:
在这里插入图片描述

4、添加Regmap API

程序如下:

/**  根据linux内核的程序查找所使用函数的对应头文件。*/
/* 省略... *//* 定义设备名称 */
#define DEVICE_NAME "ICM20608"/* 2.0 设备结构体 */
struct icm20608_dev
{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex mutex_lock; // 互斥锁,保证一次只有一个应用读取该数据
};/* 2.3.1.1 ICM20608的扫描元素,3轴加速计、3轴陀螺仪、1路温度传感器、一路时间戳 */
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,
};/* 2.3.1.2 */
/* _type:通道类型(加速度和陀螺仪), _channel2:当modified为1时,channel2为通道修饰符 */
/* info_mask_shared_by_type:通道共享; IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。*/
/* info_mask_separate:通道独立; IIO_CHAN_INFO_RAW为原始值,IIO_CHAN_INFO_CALIBBIAS校准值  */
#define ICM20608_CHANNEL(_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,                             \},                                                    \}/* 2.3.1 icm20608 通道, 1 路温度通道, 3 路陀螺仪, 3 路加速度计 */
static const struct iio_chan_spec icm20608_channels[] = {/* 使用最元素的配置温度 */{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},/* 2.3.1.3 加速度X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), // X轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), // Y轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), // Z轴/* 2.3.1.4 陀螺仪X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X), // X轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y), // Y轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z), // Z轴
};/* 3.2 icm20608读取单个寄存器 */
static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {u32 data = 0;u8 ret = 0;ret = regmap_read(dev->regmap, reg, &data);return (u8)data;
}/* 3.3 icm20608写一个寄存器 */
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {u8 ret = 0;ret = regmap_write(dev->regmap, reg, value);
}/* 3.4 icm20608里面的寄存器初始化 */
void icm20608_reg_init(struct icm20608_dev *dev) {u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0X80); //复位,复位后为0X40,睡眠模式 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 = 0X%X\r\n",value);value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1 = 0X%X\r\n",value);/* 以下是关于6轴传感器的初始化 */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_FIFO_EN, 0x00);
}/* 2.3.2.1 */
/* 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。* indio_dev : iio_dev, chan : 通道, val : 读取的值的整数部分,val2 : 读取的值的小数部分, mask : 掩码。*/
static int icm20608_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{int ret = 0;printk("icm20608_read_raw\r\n");return ret;
}/* 2.3.2.2 */
static int icm20608_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{int ret = 0;printk("icm20608_write_raw\r\n");return ret;
}/*  2.3.2.3 *  用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是用来设置这个的。* indio_dev : iio_dev* chan : 通道* mask : 掩码* return : 0,成功;其他值,错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask)
{int ret = 0;printk("icm20608_write_raw_get_fmt\r\n");return ret;
}/* 2.3.2 */
/*  iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw*  函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱*  动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函*  数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。* */
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,.driver_module = THIS_MODULE,
};/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;struct icm20608_dev *dev;struct iio_dev *indio_dev;printk("icm20608_probe successful!\r\n");/* 2.1 申请icm20608_dev结构体大小的内存, 为私人结构体“icm20608_dev”分配内存空间 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev){ret = -ENOMEM;goto fail_iio_dev;}/* 2.2 获取定义的设备结构体首地址和获取spi_device结构体等数据私有化操作 */dev = iio_priv(indio_dev);       // 使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址dev->spi = spi;                  // 设备树匹配成功后,系统会分配struct spi_device *spi,因此将其spi赋值给我们定义的设备结构体上spi_set_drvdata(spi, indio_dev); // 将indio_dev设置为spi->driver_data私有数据mutex_init(&dev->mutex_lock);    // 初始化互斥锁/* 2.3 初始化iio_dev */indio_dev->dev.parent = &spi->dev;                       // 获取spi->dev结构体indio_dev->channels = icm20608_channels;                 // 通道indio_dev->num_channels = ARRAY_SIZE(icm20608_channels); // 通道大小indio_dev->name = DEVICE_NAME;                           // 名称indio_dev->modes = INDIO_DIRECT_MODE;                    // 直接模式,提供sysfs接口indio_dev->info = &icm20608_info;/* 2.4 将iio_dev注册到内核 */ret = iio_device_register(indio_dev);if (ret < 0){dev_err(&spi->dev, "unable to register iio device\r\n");goto fail_iio_register;}/* 2.5 设置SPI的模式 */spi->mode = SPI_MODE_0; // MODE0,CPOL=0, CPHA=0spi_setup(spi); //设置好 spi_device 以后需要使用 spi_setup 配置一下/* 3.1 初始化 regmap_config 设置 和 regmap *//* 补充:当设置SPI读ICM20608操作时,使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config* 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候* 就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要* 我们来操作,全部由 regmap 框架来完成的 */dev->regmap_config.reg_bits = 8; /* 寄存器长度8bit */dev->regmap_config.val_bits = 8; /* 值长度8bit */dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */dev->regmap = regmap_init_spi(spi,&dev->regmap_config);if(IS_ERR(dev->regmap)) {ret = PTR_ERR(dev->regmap);goto fail_regmap_init;}/* 3.5 调用icm20608初始化函数 */icm20608_reg_init(dev);return 0;fail_regmap_init:iio_device_unregister(indio_dev);
fail_iio_register:
fail_iio_dev:return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi)
{int ret = 0;/* 获取私有数据 */struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);printk("icm20608_remove finish\r\n");/* 2.6 注销iio_dev */iio_device_unregister(indio_dev);/* 3.6 删除regmap */regmap_exit(dev->regmap);return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608"},{}};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.2 驱动模块入口函数 */
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver); // 注册spi驱动设备
}/* 1.3 驱动模块出口函数 */
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver); // 注销spi驱动设备
}/* 1.1 驱动许可和个人信息 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

现象:
在这里插入图片描述
  从以上程序可以看出,Regmap它就是Linux内核给我们封装好的寄存器读写接口,我们只需要配置初始化Regmap后就可以直接使用了。
  如果要读取连续的多个寄存器值,可以使用:

regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count)

  如果要写值进连续的多个寄存器内,可以使用:

regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count)

5、完善xxx_read_raw函数,真正实现通道文件可读取传感器数据

  因为应 用 程 序 所 有 的 读 取 操 作 ,最 终 都 会 汇 总 到 iio_info 的 read 函 数 , 这 里 就是
icm20608_read_raw 函数。
  对此我以读取加速度传感器X轴的原始值为例,也就是通道规则结构树下的:

IIO_CHAN_INFO_RAW->
        IIO_ACCEL->

驱动程序如下:

/**  根据linux内核的程序查找所使用函数的对应头文件。*/
/* 省略... *//* 定义设备名称 */
#define DEVICE_NAME "ICM20608"/* 2.0 设备结构体 */
struct icm20608_dev
{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex mutex_lock; // 互斥锁,保证一次只有一个应用读取该数据
};/* 2.3.1.1 ICM20608的扫描元素,3轴加速计、3轴陀螺仪、1路温度传感器、一路时间戳 */
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,
};/* 2.3.1.2 */
/* _type:通道类型(加速度和陀螺仪), _channel2:当modified为1时,channel2为通道修饰符 */
/* info_mask_shared_by_type:通道共享; IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。*/
/* info_mask_separate:通道独立; IIO_CHAN_INFO_RAW为原始值,IIO_CHAN_INFO_CALIBBIAS校准值  */
#define ICM20608_CHANNEL(_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,                             \},                                                    \}/* 2.3.1 icm20608 通道, 1 路温度通道, 3 路陀螺仪, 3 路加速度计 */
static const struct iio_chan_spec icm20608_channels[] = {/* 使用最元素的配置温度 */{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},/* 2.3.1.3 加速度X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), // X轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), // Y轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), // Z轴/* 2.3.1.4 陀螺仪X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X), // X轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y), // Y轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z), // Z轴
};/* 3.2 icm20608读取单个寄存器 */
static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {u32 data = 0;u8 ret = 0;ret = regmap_read(dev->regmap, reg, &data);return (u8)data;
}/* 3.3 icm20608写一个寄存器 */
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {u8 ret = 0;ret = regmap_write(dev->regmap, reg, value);
}/* 3.4 icm20608里面的寄存器初始化 */
void icm20608_reg_init(struct icm20608_dev *dev) {u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0X80); //复位,复位后为0X40,睡眠模式 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 = 0X%X\r\n",value);value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1 = 0X%X\r\n",value);/* 以下是关于6轴传感器的初始化 */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_FIFO_EN, 0x00);
}/*  4.1.1.1*  读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取,读取2个寄存器值* dev: icm20608设备,  reg: 要读取的通道寄存器首地址,  anix: 需要读取的通道,比如X,Y,Z,  val: 保存读取到的值。*/
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg, int axis, int *val)
{int ind, result;__be16 d;   //定义大端规则的16位整形变量ind = (axis - IIO_MOD_X) * 2;   //计算读取的陀螺仪和加速度传感器的X、Y、Z轴的偏移寄存器地址result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;*val = (short)be16_to_cpup(&d); //将16位整形数据以大端规则转换存储return IIO_VAL_INT; //表示读取的数据类型是整数值,没有小数
}/*  4.1.1*  读取 ICM20608 陀螺仪、加速度计、温度通道值-----读取原始数据使用的*  indio_dev : iio 设备, chan : 通道, val : 保存读取到的通道值。*/
static int icm20608_read_channel_data(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val)
{int ret = 0;struct icm20608_dev *dev = iio_priv(indio_dev);/* 区分通道类型,是温度传感器、陀螺仪传感器、加速度传感器 */switch (chan->type) {case IIO_ACCEL: //加速度printk("read channel type is IIO_ACCEL\r\n");ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */return ret;case IIO_ANGL_VEL: //陀螺仪printk("read channel type is IIO_ANGL_VEL\r\n");ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);  /* channel2为X、Y、Z轴 */return ret;case IIO_TEMP: //温度printk("read channel type is IIO_TEMP\r\n");ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);return ret;  default:return -EINVAL; //没有一项符合,那么返回参数不符合}return ret;
}/* 2.3.2.1 */
/* 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。* indio_dev : iio_dev, chan : 通道, val : 读取的值的整数部分,val2 : 读取的值的小数部分, mask : 掩码。*/
static int icm20608_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{int ret = 0;/********** 4.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/struct icm20608_dev *dev = iio_priv(indio_dev);// printk("icm20608_read_raw\r\n");/* 区分读取的数据类型,如raw、scale、offset等掩码 */switch (mask){case IIO_CHAN_INFO_RAW: //原始数据类型printk("read type is IIO_CHAN_INFO_RAW. \r\n");mutex_lock(&dev->mutex_lock); //上锁,保证读取的数据正确性ret = icm20608_read_channel_data(indio_dev, chan, val);   //再进行细分筛选,判断读取的是原始数据类型下的哪个通道的数据(温度、加速度、陀螺仪),而这里val参数不用做处理,因为元素数据是ADC数据,是整形的mutex_unlock(&dev->mutex_lock); //释放锁return ret;case IIO_CHAN_INFO_SCALE: //分辨率数据类型printk("read type is IIO_CHAN_INFO_SCALE. \r\n");return ret;case IIO_CHAN_INFO_OFFSET: //补偿、偏置数据类型printk("read type is IIO_CHAN_INFO_OFFSET. \r\n");return ret;case IIO_CHAN_INFO_CALIBBIAS: //校准数据类型printk("read type is IIO_CHAN_INFO_CALIBBIAS. \r\n");return ret;default:return -EINVAL; //没有一项符合,那么返回参数不符合}/********** 4.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/return ret;
}/* 2.3.2.2 */
static int icm20608_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{int ret = 0;printk("icm20608_write_raw\r\n");return ret;
}/*  2.3.2.3 *  用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是用来设置这个的。* indio_dev : iio_dev* chan : 通道* mask : 掩码* return : 0,成功;其他值,错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask)
{int ret = 0;printk("icm20608_write_raw_get_fmt\r\n");return ret;
}/* 2.3.2 */
/*  iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw*  函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱*  动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函*  数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。* */
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,.driver_module = THIS_MODULE,
};/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;struct icm20608_dev *dev;struct iio_dev *indio_dev;printk("icm20608_probe successful!\r\n");/* 2.1 申请icm20608_dev结构体大小的内存, 为私人结构体“icm20608_dev”分配内存空间 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev){ret = -ENOMEM;goto fail_iio_dev;}/* 2.2 获取定义的设备结构体首地址和获取spi_device结构体等数据私有化操作 */dev = iio_priv(indio_dev);       // 使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址dev->spi = spi;                  // 设备树匹配成功后,系统会分配struct spi_device *spi,因此将其spi赋值给我们定义的设备结构体上spi_set_drvdata(spi, indio_dev); // 将indio_dev设置为spi->driver_data私有数据mutex_init(&dev->mutex_lock);    // 初始化互斥锁/* 2.3 初始化iio_dev */indio_dev->dev.parent = &spi->dev;                       // 获取spi->dev结构体indio_dev->channels = icm20608_channels;                 // 通道indio_dev->num_channels = ARRAY_SIZE(icm20608_channels); // 通道大小indio_dev->name = DEVICE_NAME;                           // 名称indio_dev->modes = INDIO_DIRECT_MODE;                    // 直接模式,提供sysfs接口indio_dev->info = &icm20608_info;/* 2.4 将iio_dev注册到内核 */ret = iio_device_register(indio_dev);if (ret < 0){dev_err(&spi->dev, "unable to register iio device\r\n");goto fail_iio_register;}/* 2.5 设置SPI的模式 */spi->mode = SPI_MODE_0; // MODE0,CPOL=0, CPHA=0spi_setup(spi); //设置好 spi_device 以后需要使用 spi_setup 配置一下/* 3.1 初始化 regmap_config 设置 和 regmap *//* 补充:当设置SPI读ICM20608操作时,使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config* 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候* 就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要* 我们来操作,全部由 regmap 框架来完成的 */dev->regmap_config.reg_bits = 8; /* 寄存器长度8bit */dev->regmap_config.val_bits = 8; /* 值长度8bit */dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */dev->regmap = regmap_init_spi(spi,&dev->regmap_config);if(IS_ERR(dev->regmap)) {ret = PTR_ERR(dev->regmap);goto fail_regmap_init;}/* 3.5 调用icm20608初始化函数 */icm20608_reg_init(dev);return 0;fail_regmap_init:iio_device_unregister(indio_dev);
fail_iio_register:
fail_iio_dev:return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi)
{int ret = 0;/* 获取私有数据 */struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);printk("icm20608_remove finish\r\n");/* 2.6 注销iio_dev */iio_device_unregister(indio_dev);/* 3.6 删除regmap */regmap_exit(dev->regmap);return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608"},{}};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.2 驱动模块入口函数 */
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver); // 注册spi驱动设备
}/* 1.3 驱动模块出口函数 */
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver); // 注销spi驱动设备
}/* 1.1 驱动许可和个人信息 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

测试现象:
在这里插入图片描述

四、完整的驱动程序

1、驱动程序

/**  根据linux内核的程序查找所使用函数的对应头文件。*/
#include <linux/types.h>
#include <linux/module.h>          //MODULE_LICENSE,MODULE_AUTHOR
#include <linux/init.h>            //module_init,module_exit
#include <linux/kernel.h>          //printk
#include <linux/fs.h>              //struct file_operations
#include <linux/slab.h>            //kmalloc, kfree
#include <linux/uaccess.h>         //copy_to_user,copy_from_user
#include <linux/io.h>              //ioremap,iounmap
#include <linux/cdev.h>            //struct cdev,cdev_init,cdev_add,cdev_del
#include <linux/device.h>          //class
#include <linux/of.h>              //of_find_node_by_path
#include <linux/of_gpio.h>         //of_get_named_gpio
#include <linux/gpio.h>            //gpio_request,gpio_direction_output,gpio_set_number
#include <linux/atomic.h>          //atomic_t
#include <linux/of_irq.h>          //irq_of_parse_and_map
#include <linux/interrupt.h>       //request_irq
#include <linux/timer.h>           //timer_list
#include <linux/jiffies.h>         //jiffies
#include <linux/atomic.h>          //atomic_set
#include <linux/input.h>           //input
#include <linux/platform_device.h> //platform
#include <linux/delay.h>           //mdelay
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/regmap.h>
#include "icm20608.h"/* 定义设备名称 */
#define DEVICE_NAME "ICM20608"
#define ICM20608_TEMP_OFFSET	     0
#define ICM20608_TEMP_SCALE		     326800000  //每度是326.8。扩大1000000所得每度对应打数值/* 2.0 设备结构体 */
struct icm20608_dev
{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex mutex_lock; // 互斥锁,保证一次只有一个应用读取该数据
};/** 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};/* 2.3.1.1 ICM20608的扫描元素,3轴加速计、3轴陀螺仪、1路温度传感器、一路时间戳 */
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,
};/* 2.3.1.2 */
/* _type:通道类型(加速度和陀螺仪), _channel2:当modified为1时,channel2为通道修饰符 */
/* info_mask_shared_by_type:通道共享; IIO_CHAN_INFO_SCALE 这个属性,表示这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。*/
/* info_mask_separate:通道独立; IIO_CHAN_INFO_RAW为原始值,IIO_CHAN_INFO_CALIBBIAS校准值  */
#define ICM20608_CHANNEL(_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,                             \},                                                    \}/* 2.3.1 icm20608 通道, 1 路温度通道, 3 路陀螺仪, 3 路加速度计 */
static const struct iio_chan_spec icm20608_channels[] = {/* 使用最元素的配置温度 */{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},/* 2.3.1.3 加速度X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X), // X轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y), // Y轴ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z), // Z轴/* 2.3.1.4 陀螺仪X、Y、Z三个通道 */ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X), // X轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y), // Y轴ICM20608_CHANNEL(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z), // Z轴
};/* 3.2 icm20608读取单个寄存器 */
static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {u32 data = 0;u8 ret = 0;ret = regmap_read(dev->regmap, reg, &data);return (u8)data;
}/* 3.3 icm20608写一个寄存器 */
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {u8 ret = 0;ret = regmap_write(dev->regmap, reg, value);
}/* 3.4 icm20608里面的寄存器初始化 */
void icm20608_reg_init(struct icm20608_dev *dev) {u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0X80); //复位,复位后为0X40,睡眠模式 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 = 0X%X\r\n",value);value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1 = 0X%X\r\n",value);/* 以下是关于6轴传感器的初始化 */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_FIFO_EN, 0x00);
}/*  4.1.1.1*  读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取,读取2个寄存器值* dev: icm20608设备,  reg: 要读取的通道寄存器首地址,  anix: 需要读取的通道,比如X,Y,Z,  val: 保存读取到的值。*/
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg, int axis, int *val)
{int ind, result;__be16 d;   //定义大端规则的16位整形变量ind = (axis - IIO_MOD_X) * 2;   //计算读取的陀螺仪和加速度传感器的X、Y、Z轴的偏移寄存器地址result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;*val = (short)be16_to_cpup(&d); //将16位整形数据以大端规则转换存储return IIO_VAL_INT; //表示读取的数据类型是整数值,没有小数
}/*  4.1.1*  读取 ICM20608 陀螺仪、加速度计、温度通道值-----读取原始数据使用的*  indio_dev : iio 设备, chan : 通道, val : 保存读取到的通道值。*/
static int icm20608_read_channel_data(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val)
{int ret = 0;struct icm20608_dev *dev = iio_priv(indio_dev);/* 区分通道类型,是温度传感器、陀螺仪传感器、加速度传感器 */switch (chan->type) {case IIO_ACCEL: //加速度printk("read channel type is IIO_ACCEL\r\n");ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */return ret;case IIO_ANGL_VEL: //陀螺仪printk("read channel type is IIO_ANGL_VEL\r\n");ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);  /* channel2为X、Y、Z轴 */return ret;case IIO_TEMP: //温度printk("read channel type is IIO_TEMP\r\n");ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);return ret;  default:return -EINVAL; //没有一项符合,那么返回参数不符合}return ret;
}/* 5.1.1* 设置ICM20608的陀螺仪计量程(分辨率)* dev:icm20608设备,  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;
}/* 5.1.2* 设置ICM20608的加速度计量程(分辨率)* dev:icm20608设备,  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;
}/*  5.1.3*  设置ICM20608传感器,可以用于陀螺仪、加速度计设置*  dev:icm20608设备,  reg:要设置的通道寄存器首地址,  anix:要设置的通道,比如X,Y,Z,  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;
}/* 2.3.2.1 */
/* 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。* indio_dev : iio_dev, chan : 通道, val : 读取的值的整数部分,val2 : 读取的值的小数部分, mask : 掩码。*/
static int icm20608_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{int ret = 0;/********** 4.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/struct icm20608_dev *dev = iio_priv(indio_dev);u8 regdata = 0; //保存读取寄存器的值// printk("icm20608_read_raw\r\n");/* 区分读取的数据类型,如raw、scale、offset等掩码 */switch (mask){case IIO_CHAN_INFO_RAW: //原始数据类型printk("read type is IIO_CHAN_INFO_RAW. \r\n");mutex_lock(&dev->mutex_lock); //上锁,保证读取的数据正确性ret = icm20608_read_channel_data(indio_dev, chan, val);   //再进行细分筛选,判断读取的是原始数据类型下的哪个通道的数据(温度、加速度、陀螺仪),而这里val参数不用做处理,因为元素数据是ADC数据,是整形的mutex_unlock(&dev->mutex_lock); //释放锁return ret;case IIO_CHAN_INFO_SCALE: //分辨率数据类型printk("read type is IIO_CHAN_INFO_SCALE. \r\n");switch (chan->type) {case IIO_ACCEL: //加速度printk("read channel type is IIO_ACCEL. \r\n");mutex_lock(&dev->mutex_lock); //上锁,保证读取的数据正确性regdata = (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG) & 0x18) >> 3;  //获取配置寄存器的3、4bit位的分辨率配置值*val = 0;   //陀加速度的分辨率是小数,因此整数部分为0*val2 = accel_scale_icm20608[regdata];mutex_unlock(&dev->mutex_lock); //释放锁return IIO_VAL_INT_PLUS_NANO;   //小数部分放大1000000000倍 值为val+val2/1000000000 case IIO_ANGL_VEL: //陀螺仪printk("read channel type is IIO_ANGL_VEL\r\n");mutex_lock(&dev->mutex_lock); //上锁,保证读取的数据正确性regdata = (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG) & 0x18) >> 3;  //获取配置寄存器的3、4bit位的分辨率配置值*val = 0;   //陀螺仪的分辨率是小数,因此整数部分为0*val2 = gyro_scale_icm20608[regdata];   //获取已经计算出来的陀螺仪分辨率表的分辨率值mutex_unlock(&dev->mutex_lock); //释放锁return IIO_VAL_INT_PLUS_MICRO;  //小数部分放大1000000倍, 值为val+val2/1000000case IIO_TEMP: //温度printk("read channel type is IIO_TEMP\r\n");*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: //补偿、偏置数据类型printk("read type is IIO_CHAN_INFO_OFFSET. \r\n");switch (chan->type) {case IIO_TEMP:*val = ICM20608_TEMP_OFFSET;return IIO_VAL_INT;default:return -EINVAL;}return ret;case IIO_CHAN_INFO_CALIBBIAS: //校准数据类型printk("read type is IIO_CHAN_INFO_CALIBBIAS. \r\n");switch (chan->type) {case IIO_ANGL_VEL:		/* 陀螺仪的校准值 */mutex_lock(&dev->mutex_lock);ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->mutex_lock);return ret;case IIO_ACCEL:			/* 加速度计的校准值 */mutex_lock(&dev->mutex_lock);	ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->mutex_lock);return ret;default:return -EINVAL;}default:return -EINVAL; //没有一项符合,那么返回参数不符合}/********** 4.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/return ret;
}/* 2.3.2.2 */
static int icm20608_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{int ret = 0;/********** 5.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/struct icm20608_dev *dev = iio_priv(indio_dev);// printk("icm20608_write_raw\r\n");switch (mask) {case IIO_CHAN_INFO_SCALE:	/* 设置陀螺仪和加速度计的分辨率 */switch (chan->type) {case IIO_ANGL_VEL:		/* 设置陀螺仪 */mutex_lock(&dev->mutex_lock);ret = icm20608_write_gyro_scale(dev, val2);mutex_unlock(&dev->mutex_lock);break;case IIO_ACCEL:			/* 设置加速度计 */mutex_lock(&dev->mutex_lock);ret = icm20608_write_accel_scale(dev, val2);mutex_unlock(&dev->mutex_lock);break;default:ret = -EINVAL;break;}break;case IIO_CHAN_INFO_CALIBBIAS:	/* 设置陀螺仪和加速度计的校准值*/switch (chan->type) {case IIO_ANGL_VEL:		/* 设置陀螺仪校准值 */mutex_lock(&dev->mutex_lock);ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,chan->channel2, val);mutex_unlock(&dev->mutex_lock);break;case IIO_ACCEL:			/* 加速度计校准值 */mutex_lock(&dev->mutex_lock);ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,chan->channel2, val);mutex_unlock(&dev->mutex_lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}    /********** 5.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/return ret;
}/*  2.3.2.3 *  用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是用来设置这个的。* indio_dev : iio_dev* chan : 通道* mask : 掩码* return : 0,成功;其他值,错误*/
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask)
{/********** 6.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/    switch (mask) {case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_ANGL_VEL:		/* 用户空间写的陀螺仪分辨率数据要乘以1000000 */return IIO_VAL_INT_PLUS_MICRO;default:				/* 用户空间写的加速度计分辨率数据要乘以1000000000 */return IIO_VAL_INT_PLUS_NANO;}default:return IIO_VAL_INT_PLUS_MICRO;}return -EINVAL;/********** 6.1 根据设置的通道文件参数作为条件依据读取数据类型 **********/    }/* 2.3.2 */
/*  iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw*  函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱*  动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函*  数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。* */
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,.driver_module = THIS_MODULE,
};/* 1.7 probe函数 */
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;struct icm20608_dev *dev;struct iio_dev *indio_dev;printk("icm20608_probe successful!\r\n");/* 2.1 申请icm20608_dev结构体大小的内存, 为私人结构体“icm20608_dev”分配内存空间 */indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));if (!indio_dev){ret = -ENOMEM;goto fail_iio_dev;}/* 2.2 获取定义的设备结构体首地址和获取spi_device结构体等数据私有化操作 */dev = iio_priv(indio_dev);       // 使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址dev->spi = spi;                  // 设备树匹配成功后,系统会分配struct spi_device *spi,因此将其spi赋值给我们定义的设备结构体上spi_set_drvdata(spi, indio_dev); // 将indio_dev设置为spi->driver_data私有数据mutex_init(&dev->mutex_lock);    // 初始化互斥锁/* 2.3 初始化iio_dev */indio_dev->dev.parent = &spi->dev;                       // 获取spi->dev结构体indio_dev->channels = icm20608_channels;                 // 通道indio_dev->num_channels = ARRAY_SIZE(icm20608_channels); // 通道大小indio_dev->name = DEVICE_NAME;                           // 名称indio_dev->modes = INDIO_DIRECT_MODE;                    // 直接模式,提供sysfs接口indio_dev->info = &icm20608_info;/* 2.4 将iio_dev注册到内核 */ret = iio_device_register(indio_dev);if (ret < 0){dev_err(&spi->dev, "unable to register iio device\r\n");goto fail_iio_register;}/* 2.5 设置SPI的模式 */spi->mode = SPI_MODE_0; // MODE0,CPOL=0, CPHA=0spi_setup(spi); //设置好 spi_device 以后需要使用 spi_setup 配置一下/* 3.1 初始化 regmap_config 设置 和 regmap *//* 补充:当设置SPI读ICM20608操作时,使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config* 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候* 就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要* 我们来操作,全部由 regmap 框架来完成的 */dev->regmap_config.reg_bits = 8; /* 寄存器长度8bit */dev->regmap_config.val_bits = 8; /* 值长度8bit */dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */dev->regmap = regmap_init_spi(spi,&dev->regmap_config);if(IS_ERR(dev->regmap)) {ret = PTR_ERR(dev->regmap);goto fail_regmap_init;}/* 3.5 调用icm20608初始化函数 */icm20608_reg_init(dev);return 0;fail_regmap_init:iio_device_unregister(indio_dev);
fail_iio_register:
fail_iio_dev:return ret;
}/* 1.8 remove函数 */
static int icm20608_remove(struct spi_device *spi)
{int ret = 0;/* 获取私有数据 */struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;dev = iio_priv(indio_dev);printk("icm20608_remove finish\r\n");/* 2.6 注销iio_dev */iio_device_unregister(indio_dev);/* 3.6 删除regmap */regmap_exit(dev->regmap);return ret;
}/* 1.5 传统的匹配表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 1.6 设备树匹配表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608"},{}};/* 1.4 spi_driver结构体 */
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,
};// module_spi_driver(icm20608_driver);
/* 1.2 驱动模块入口函数 */
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver); // 注册spi驱动设备
}/* 1.3 驱动模块出口函数 */
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver); // 注销spi驱动设备
}/* 1.1 驱动许可和个人信息 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

2、测试现象

  我们修改了加速度传感器分辨率后,查看的重力加速度的原始数据值也发生改变,大家可以将分辨率与重力加速度原始数据值相乘,约等于1个g的加速度。大家自行测试其他的通道文件了。
在这里插入图片描述

3、icm20608APP程序及测试

/***************************************************************
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.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:device0/in_accel_scale","/sys/bus/iio/devices/iio:device0/in_accel_x_calibbias","/sys/bus/iio/devices/iio:device0/in_accel_x_raw","/sys/bus/iio/devices/iio:device0/in_accel_y_calibbias","/sys/bus/iio/devices/iio:device0/in_accel_y_raw","/sys/bus/iio/devices/iio:device0/in_accel_z_calibbias","/sys/bus/iio/devices/iio:device0/in_accel_z_raw","/sys/bus/iio/devices/iio:device0/in_anglvel_scale","/sys/bus/iio/devices/iio:device0/in_anglvel_x_calibbias","/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw","/sys/bus/iio/devices/iio:device0/in_anglvel_y_calibbias","/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw","/sys/bus/iio/devices/iio:device0/in_anglvel_z_calibbias","/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw","/sys/bus/iio/devices/iio:device0/in_temp_offset","/sys/bus/iio/devices/iio:device0/in_temp_raw","/sys/bus/iio/devices/iio:device0/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) {printf("file read error!\r\n");} 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;
}

实验现象:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73220.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[机器学习]XGBoost---增量学习多阶段任务学习

一 说明当我们的训练数据非常多&#xff0c;并且还在不断增加时&#xff0c;每次都用全量训练&#xff0c;数据过多&#xff0c;时间过长&#xff0c;此时就可以使用增量训练&#xff1a;用新增的数据微调校正模型。二 全量与增量的差异在使用增量训练时&#xff0c;最关心的问…

Head First设计模式---5.单例模式

2.2单例模式 单例模式运用的可能比其他几种简单&#xff0c;通俗点理解就是&#xff0c;我这个对象只能存在一个。 问题 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量&#xff1f; 最常见的原因是控制某些共享资源 &#xff08;例如数据库或文件&am…

【Java】Spring更简单的读取和存储

文章目录Spring更简单的读取和存储对象1. 存储Bean对象1.1 前置工作&#xff1a;配置扫描路径1.2 添加注解存储Bean对象1.2.1 Controller(控制器存储)1.2.2 Service(服务存储)1.2.3 Repository(仓库存储)1.2.4 Component(组件存储)1.2.5 Configuration1.3 为什么要这么多类注解…

结构建模设计——Solidworks软件之装配体操作基本总结三(高级配合、机械配合、快捷菜单功能)

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

如何设计一个通用的权限管理系统

一个系统&#xff0c;如果没有安全控制&#xff0c;是十分危险的&#xff0c;一般安全控制包括身份认证和权限管理。用户访问时&#xff0c;首先需要查看此用户是否是合法用户&#xff0c;然后检查此用户可以对那些资源进行何种操作&#xff0c;最终做到安全访问。身份认证的方…

K8s调度器Scheduler

当创建k8s pod的时候调度器会决定pod在哪个node上被创建且运行&#xff0c;调度器给apiserver发出了一个创建pod的api请求&#xff0c;apiserver首先将pod的基本信息保存在etcd&#xff0c;apiserver又会把这些信息给到每个node上的kubelet进程&#xff0c;kubelet一直在监听这…

【python】anaconda 管理 python 环境

anaconda 管理虚拟环境anaconda 简介python 虚拟环境的安装查看当前 anaconda中所有的虚拟环境创建新的虚拟环境激活所创建的虚拟环境删除指定的虚拟环境退出当前虚拟环境查看当前虚拟环境中所有安装的库安装常用包pycharmpycharm 下环境配置pycharm 使用anaconda 简介 anacon…

springBoot使用ShardingJDBC实现分表

ShardingSphere的介绍 ShardingSphere是一款起源于当当网内部的应用框架。2015年在当当网内部诞 生&#xff0c;最初就叫ShardingJDBC。2016年的时候&#xff0c;由其中一个主要的开发人员张亮&#xff0c; 带入到京东数科&#xff0c;组件团队继续开发。在国内历经了当当网、…

LeetCode 622.设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里&a…

注意啦!如何通过广告吸引客户直接下单?

2023年跨境电商越来越突出&#xff0c;据业内相关人士称&#xff0c;在未来几年与跨境电商相关的政策仍会继续倾斜甚至加大力度&#xff0c;因此各行各业都响应政策&#xff0c;在新政策落实之前致力于平台的转型升级&#xff0c;做新时代创新型的高质量发展&#xff0c;其实细…

Linux下的命令执行绕过技巧合集(渗透测试专用)

一、通配符* 代表『0个到无穷多个』任意字符&#xff0c;包括空字符? 代表『一定有一个』任意字符[ ] 同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符&#xff0c; 可能是 a, b, c, d 这四个任何一个』[ - ]若有减号在中括号内时&#…

(考研湖科大教书匠计算机网络)第六章应用层-第五节:文件传送协议FTP

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;概述二&#xff1a;工作原理三&#xff1a;控制连接与数据连接本节对应视频如下 【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】…

求职3个月,简历大多都石沉大海,一听是手工测试都纷纷摇头....太难了

距离被上家公司裁员已经过去了3个月了&#xff0c;3个月的求职经历真的让我痛不欲生&#xff0c;我也从中理解感叹到了很多&#xff0c;想写出来&#xff0c;告诫跟我一样的经历的人。 我今年26岁&#xff0c;大学是一所普通的大专&#xff0c;学的是机电专业&#xff0c;如何…

Python自动化测试框架封装和调用

封装与调用函数与参数化前言 面实现了参数的关联&#xff0c;那种只是记流水账的完成功能&#xff0c;不便于维护&#xff0c;也没什么可读性&#xff0c;接下来这篇可以把每一个动作写成一个函数&#xff0c;这样更方便了。参数化的思维只需记住一点&#xff1a;不要写死 登录…

类与对象(this 关键字、构造器)

目录一、面向对象二、类与对象三、对象内存图四、成员变量和局部变量区别五、this关键字六、构造器/构造方法一、面向对象 一种编程思想:也就是说我们要以何种思路&#xff0c;解决问题&#xff0c;以何种形式组织代码 当解决一个问题的时候&#xff0c;面向对象会把事物抽象成…

分享app的测试技巧

前言 今天笔者想和大家来唠唠app测试&#xff0c;现在的app有非常的多&#xff0c;这些app都是需要经过测试之后才能发布到应用市场中&#xff0c;app已经成为了我们日常生活中不可或缺的一部分了&#xff0c;但它的功能必须强大&#xff0c;才能受到消费者的重视&#xff0c;…

已解决from cryptography.hazmat.backends import default_backend导包错误

已解决Python连接FTPS抛出异常&#xff1a;CryptographyDeprecationWarning: Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography. The next release of cryptography (40.0) will be the last to support …

pyaudio声卡信息中hostApi是什么意思?

hostApi是声卡驱动协议&#xff0c;声卡驱动模式&#xff0c;有如下很多类。下面的类型是网上找的PortAudio的类&#xff0c;不不确定是不是python的。typedef enum PaHostApiTypeId{paInDevelopment0, /* use while developing support for a new host API */paDirectSound1,p…

深度学习之“制作自定义数据”--torch.utils.data.DataLoader重写构造方法。

深度学习之“制作自定义数据”–torch.utils.data.DataLoader重写构造方法。 前言&#xff1a; ​ 本文讲述重写torch.utils.data.DataLoader类的构造方法&#xff0c;对自定义图片制作类似MNIST数据集格式&#xff08;image, label&#xff09;&#xff0c;用于自己的Pytorc…

推荐系统从入门到入门(3)——基于MapReuduce与Spark的分布式推荐系统构建

本系列博客总结了不同框架、不同算法、不同界面的推荐系统&#xff0c;完整阅读需要大量时间&#xff08;又臭又长&#xff09;&#xff0c;建议根据目录选择需要的内容查看&#xff0c;欢迎讨论与指出问题。 目录 系列文章梗概 系列文章目录 三、MapReduce 1.MapReduce详…