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

news2024/11/17 0:20:11

目录

  • 一、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=0
    spi_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=0
    spi_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=0
    spi_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=0
    spi_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/1000000
        case 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=0
    spi_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.coloradmin.cn/o/366450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring boot的包扫描范围

目录标题一、误解二、正确的理解三、不同包也能扫描到Bean的方法一、误解 一开始我一直以为spring boot默认的包扫描范围是启动类的同级目录和子目录下的Bean。其实正真是与启动类在同个包以及子包下的Bean。 我一直误解了包的概念&#xff0c;包并不是只文件夹&#xff08;文…

vscode远程调试python

目的 注意&#xff1a;这里我们想要实现的是&#xff1a;用vscode 使用remote ssh打开project&#xff0c;然后直接在project里面进行debug&#xff0c;而不需要 在本地vscode目录打开一样的project。 假设大家已经会使用remote ssh打开远程服务器的代码了&#xff0c;那么只…

Qt——QLineEdit

QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数&#xff0c;输入和编辑单行文本&#xff0c;比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变QLineEdit的 echoMode() &#xff0c;可以设置其属性&#xff0c;比如以密码的形式输入。 文本的长度可以由 maxLength(…

ctf pwn基础-2

今天学了一个保护的绕过&#xff0c;这里讲一讲&#xff0c;这个好像是使用的是格式化字符串漏洞。 目录 基础 实例讲解 基础 首先我们要知道什么是canary保护&#xff0c;就是在入栈EBP以后加一个Canary 我可能讲的不是很好&#xff0c;大家可以看看这些 文章 用通俗一点将就…

C++问答汇总_2023自用

C是一种通用编程语言&#xff0c;具有高级抽象、强类型和编译性能等特点。C语言具有许多特性&#xff0c;包括面向对象编程、模板、多态、运算符重载等等。它广泛应用于各种领域&#xff0c;如系统软件、嵌入式系统、游戏开发、科学计算等等。 1、C11相对于C98的新特性&#xf…

Redis的安装部署和配置文件的修改

1、准备安装环境 由于 Redis 是基于 C 语言编写的&#xff0c;因此首先需要安装 Redis 所需要的依赖&#xff1a; yum install -y gcc tcl gcc-c make 2、上传安装文件 将下载好的 redis-6.2.7.tar.gz 安装包上传到虚拟机的任意目录&#xff08;一般推荐上传到 /usr/local/s…

贝叶斯网络实践

目录 一。朴素贝叶斯的假设 二。朴素贝叶斯的推导 三。高斯朴素贝叶斯Gaussian Naive Bayes 四。多项分布朴素贝叶斯Multinomial Naive Bayes 五。以文本分类为例 1.分析 2.分解 3.拉普拉斯平滑 4.对朴素贝叶斯的思考 六。总结 七。word2vec 八。GaussianNB,…

【数据结构】Map 和 Set

目录二叉搜索树二叉搜索树---查找二叉搜索树---插入二叉搜索树---删除Map和SetMap的使用Set的使用哈希表哈希冲突冲突避免冲突解决冲突解决---闭散列冲突解决---开散列题目练习只出现一次的数复制带随机指针的链表宝石与石头旧键盘二叉搜索树 二叉搜索树也叫二叉排序树&#x…

(二十六)大白话如何从底层原理解决生产的Too many connections故障?

今天我们继续讲解昨天的那个案例背景&#xff0c;其实就是经典的Too many connections故障&#xff0c;他的核心就是linux的文件句柄限制&#xff0c;导致了MySQL的最大连接数被限制&#xff0c;那么今天来讲讲怎么解决这个问题。 其实核心就是一行命令&#xff1a; ulimit -H…

分布式面试题

目录 分布式id的生成方案有哪些 雪花算法生成的ID由哪些部分组成 分布式锁在项目中有哪些应用场景? 分布式锁有哪些解决方案 Redis做分布式锁用什么命令 Redis做分布式锁&#xff0c;死锁有哪些情况&#xff1f;如何解决 Redis如何做分布式锁 MySQL如何做分布式锁 什么…

代码签名即将迎来一波新关注

在数字化高度发展的当下&#xff0c;个人隐私及信息安全保护已经成了大家关注的重点&#xff0c;包括日常使用的电脑软件&#xff0c;手机APP等&#xff0c;由于包含了大量的用户信息&#xff0c;已经成了重点关注对象&#xff0c;任何一个疏忽就可能泄露大量用户信息。所以权威…

了解线程安全

线程安全是多线程的重点和难点。 线程安全概念 线程安全&#xff1a;在多线程的各种随机调度顺序下&#xff0c;代码没有bug&#xff0c;都能够符合预期的方式来执行&#xff0c;此时认为线程安全 线程不安全&#xff1a;如果在多线程随机调度下代码出现bug&#xff0c;此时…

Java Web:开篇综述与第一章

前言 翻开这本书&#xff0c;又是一段新的学习路线&#xff0c;在学习的道路上是枯燥的&#xff0c;是乏味的&#xff0c;难免有放弃的想法。但回看曾经的学习笔记&#xff0c;自己也一步一步走过来了&#xff0c;即使会自我怀疑自我否定&#xff0c;但不坚持不努力是永远没有…

#G. 求约数个数之六

我们先求到区间[1..b]之间的所有约数之和于是结果就等于 [1..b]之间的所有约数之和减去[1..a-1]之间的约数之和很明显这两个问题是同性质的问题&#xff0c;只是右端点不同罢了.明显对于1到N之间的数字&#xff0c;其约数范围也为1到N这个范围内。于是我们可以枚举约数L,当然这…

【ROS学习笔记1】ROS快速体验输出Hello World

【ROS学习笔记1】ROS快速体验输出Hello World 文章目录【ROS学习笔记1】ROS快速体验输出Hello World1.1 ROS快速体验1.1.1 Hello World快速实现简介1.1.2 Hello World的C实现1.1.3 Hello World的Python实现写在前面&#xff0c;本系列笔记参考的是AutoLabor的教程&#xff0c;具…

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

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

【Django】内建用户、文件上传、发送邮件、项目部署

一、内建用户系统 Django带有一个用户认证系统用来处理账号、cookie等 from django.contrib.auth.models import User1、创建用户 from django.contrib.auth.models import User # 普通用户 user User.objects.create_uer(username用户名,password密码,email邮箱) # 超级用…

这几个免费、商用图片素材网,你一定要知道

很多朋友不知道去哪里找图片素材&#xff0c;找到了又担心会不会侵权。 今天给大家分享7个免费可商用图片素材网站&#xff0c;这下再也不用担心找不到素材或侵权啦&#xff01; 1、菜鸟图库 传送门&#xff1a;美女图片|手机壁纸|风景图片大全|高清图片素材下载网 - 菜鸟图库…

hive只复制表结构不复制表数据

目录 一、背景 二、准备测试数据 1.建表 2.造测试数据 三、操作 1.CTAS &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 2.LIKE &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 一、背景 有一张ori_…

《狂飙》壁纸大嫂如此惊艳,做成日历壁纸天天看

兄弟们&#xff0c;今年的反腐大剧狂飙都有看吗 &#xff1f; 话说&#xff0c;名字虽然叫狂飙&#xff0c;但是全剧只有有田一个人在狂飙&#xff01; 当然&#xff0c;有田虽然亮眼&#xff0c;但是毕竟是个糟老头子&#xff0c;正经人谁看有田啊&#xff0c;当然是看大嫂了…