Linux SPI 驱动实验

news2024/11/19 23:18:11

目录

一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

2、SPI 设备驱动

SPI 设备数据收发处理流程

3、SPI 设备和驱动匹配过程

二、添加SPI 设备信息

1、添加 ICM20608 所使用的 IO

 2、 在 ecspi3 节点追加 icm20608 子节点

三、编写 ICM20608 驱动

1、修改makefile​编辑

2、icm20608reg.h

3、icm20608 设备结构体创建

4、probe 函数

5、icm20608 寄存器读写与初始化

①icm20608_read_regs函数

②icm20608_write_regs函数

③icm20608_read_onereg函数

④ icm20608_write_onereg函数

⑤ icm20608_readdata函数

⑥icm20608_reginit 函数

6、icm20608_read函数

代码如下

四、APP编写

编译验证


一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

        SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核
使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,部分如下

struct spi_master {

    struct device dev;

    struct list_head list;
.....

    int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
.....

    int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
}

transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去

SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。和 I2C 适配器驱动一样, SPI 主机驱动一般都是 SOC 厂商去编写

SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master

2、SPI 设备驱动

spi 设备驱动也和 i2c 设备驱动也很类似, Linux 内核使用 spi_driver 结构体来表示 spi 设备
驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。 spi_driver 结构体定义在
include/linux/spi/spi.h 文件中,结构体内容如下:

 struct spi_driver {
          const struct spi_device_id *id_table;
          int (*probe)(struct spi_device *spi);
          int (*remove)(struct spi_device *spi);
          void (*shutdown)(struct spi_device *spi);
          struct device_driver driver;
 };

 spi_driver 和 i2c_driver、 platform_driver 基本一样,当 SPI 设备和驱动匹配成功以后 probe 函数就会执行。

SPI 设备数据收发处理流程

SPI 设备驱动的核心是 spi_driver,当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容部分如下:

struct spi_transfer {

1    const void *tx_buf;
2   void *rx_buf;
3    unsigned len;
........
4    struct list_head transfer_list;
};

第1行, tx_buf 保存着要发送的数据。
第2行, rx_buf 用于保存接收到的数据。
第3行, len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。

spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,在使用spi_message之前需要对其进行初始化,spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,异步传输不会阻塞的等到 SPI 数据传输完成,下面采用同步传输方式来完成 SPI 数据的传输工作

3、SPI 设备和驱动匹配过程

SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、 I2C 等驱动一样, SPI
总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:

 struct bus_type spi_bus_type = {
          .name = "spi",
          .dev_groups = spi_dev_groups,
        .match = spi_match_device,
         .uevent = spi_uevent,
 };

 SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:

 static int spi_match_device(struct device *dev,
                                struct device_driver *drv)
{
1 const struct spi_device *spi = to_spi_device(dev);
2 const struct spi_driver *sdrv = to_spi_driver(drv);
3
4 /* Attempt an OF style match */
5 if (of_driver_match_device(dev, drv))
6 return 1;
7
8 /* Then try ACPI */
9 if (acpi_driver_match_device(dev, drv))
10 return 1;
11
12 if (sdrv->id_table)
13 return !!spi_match_id(sdrv->id_table, spi);
14
15 return strcmp(spi->modalias, drv->name) == 0;
}

 第5 行, of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配

第9 行, acpi_driver_match_device 函数用于 ACPI 形式的匹配

第13 行, spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配

第15 行,比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。

二、添加SPI 设备信息

1、添加 ICM20608 所使用的 IO

在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点
中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚

pinctrl_ecspi3: icm20608 {
            fsl,pins = < 
                MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x10b0    /* CS */
                MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK    0x10b1    /* SCLK */
                MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x10b1    /* MISO */
                MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1    /* MOSI */
            >;
        };

注意的就是检查相应的 IO 有没有被其他的设备所使用,分别搜UART2_TX、UART2_RX、UART2_RTS和UART2_CTS,有复用的全部屏蔽,如下

 

 2、 在 ecspi3 节点追加 icm20608 子节点

 打开 imx6qdl-sabresd.dtsi 这个设备树头文件

&ecspi3 {
1    fsl,spi-num-chipselects = <1>;
2	cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; 
3	pinctrl-names = "default";
4	pinctrl-0 = <&pinctrl_ecspi3>;
5	status = "okay";

6	spidev: icm20608@0 {
7		compatible = "my,icm20608";
8		spi-max-frequency = <8000000>;
9		reg = <0>;
	};	
};

第1行,设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备

第2行,设置“cs-gpios”属性,也就是片选信号,SPI 主机驱动就会控制片选引脚

第3行,设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字

第4行,设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点

第5行,“status”属性为“okay”

第6行,每一个 SPI 设备都采用一个子节点来描述其设备信息,icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0

第7行,SPI 设备的 compatible 属性值,用于匹配设备驱动

第8行,“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据使用的SPI 设备来设置

第9行,icm20608 连接在通道 0 上,因此 reg 为 0。

三、编写 ICM20608 驱动

新建 icm20608.c 和 icm20608reg.h 这两个文件, icm20608.c 为 ICM20608的驱动代码, icm20608reg.h 是 ICM20608 寄存器头文件

1、修改makefile

2、icm20608reg.h

#ifndef __IMC20608REG_H
#define __IMC20608REG_H

/* ID值 */
#define ICM20608G_ID    (0XAF)
#define ICM20608D_ID    (0XAE)

/*  定义寄存器 */
/* ICM20608寄存器 
 *复位后所有寄存器地址都为0,除了
 *Register 107(0X6B) Power Management 1 	= 0x40
 *Register 117(0X75) WHO_AM_I 				= 0xAF或0xAE
 */
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F

/* 陀螺仪静态偏移 */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18

#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A

/* 加速度输出 */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40

/* 温度输出 */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42

/* 陀螺仪输出 */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48

#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75

/* 加速度静态偏移 */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_ZA_OFFSET_L 			0x7E


#endif

3、icm20608 设备结构体创建

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>

#define ICM20608_CNT 1
#define ICM20608_NAME "icm20680"

/*设备结构体*/
struct icm20608_dev{
    dev_t devid;/* 设备号 	 */
    int major;/* 主设备号 */
    int minor;/* 次设备号 */
    struct cdev cdev;/* cdev 	*/
    struct class *class;/* 类 		*/
    struct device *device;/* 设备 	 */
    
    void *private_data; /*私有数据 */
};
struct icm20608_dev icm20608dev;

static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev;
    return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* icm20608操作函数 */
struct file_operations icm20608_fops = {
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .release = icm20608_release,
    .read = icm20608_read,
};

static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;
    /* 1、构建设备号 */
    icm20608dev.major=0;
    if(icm20608dev.major)
    {
        icm20608dev.devid = MKDEV(icm20608dev.major,0);
        ret = register_chrdev_region(icm20608dev.devid,ICM20608_CNT,ICM20608_NAME);
    }else
    {
        ret = alloc_chrdev_region(&icm20608dev.devid,0,ICM20608_CNT,ICM20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
        icm20608dev.minor = MINOR(icm20608dev.devid);
    }
    if(ret < 0)
    {
        printk("icm20608 chrdev_region err!\r\n");
        goto fail_devid;
    }
    printk("icm20608dev major = %d, minor = %d\r\n",icm20608dev.major,icm20608dev.minor);
    /* 2、注册设备 */
    icm20608dev.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608dev.cdev,&icm20608_fops);
    ret = cdev_add(&icm20608dev.cdev,icm20608dev.devid,ICM20608_CNT);
    if(ret <0)
    {
        printk("icm20608 cdev_add err!\r\n");
        goto fail_cdev;
    }
    /* 3、创建类 */
    icm20608dev.class =  class_create(THIS_MODULE,ICM20608_NAME);
    if(IS_ERR(icm20608dev.class))
    {
        ret = PTR_ERR(icm20608dev.class);
        printk("icm20608 chrdev_class err!\r\n");
        goto fail_class;
    }
    /* 4、创建设备 */
    icm20608dev.device = device_create(icm20608dev.class,NULL,
                                        icm20608dev.devid,NULL,ICM20608_NAME);
    if(IS_ERR(icm20608dev.device))
    {
        ret = PTR_ERR(icm20608dev.device);
        printk("icm20608 chrdev_device err!\r\n");
        goto fail_device;
    }
    /*设置icm20608私有数据*/
    icm20608dev.private_data = spi;

    printk("icm20608_probe\r\n");
    return 0;
fail_device:
    class_destroy(icm20608dev.class);
fail_class: 
    cdev_del(&icm20608dev.cdev);
fail_cdev:
    unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);
fail_devid:
    return ret;
}

static int icm20608_remove(struct spi_device *spi)
{
    /* 删除字符设备 */
    cdev_del(&icm20608dev.cdev);
    /*注销设备号*/
    unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);
    /*摧毁设备*/
    device_destroy(icm20608dev.class,icm20608dev.devid);
    /*摧毁类*/
    class_destroy(icm20608dev.class);
    printk("icm20608_remove\r\n");
    return 0;
}

/*传统匹配*/
struct spi_device_id icm20608_id[] ={
    {"my,icm20608",0},
    {}
};
/*设备树匹配*/
static const struct of_device_id icm20608_of_match[] = {
    {.compatible = "my,icm20608"},
    {}
};
/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {
	.probe = icm20608_probe,
	.remove = icm20608_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "icm20608",
		   	.of_match_table = icm20608_of_match, 
		   },
	.id_table = icm20608_id,
};

/*驱动入口*/
static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}
/*驱动出口*/
static void __exit icm20608_exit(void)
{
   spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

当 icm20608 设备和此驱动匹配成功以后 icm20608_probe 函数就会执行。同样的,当注销此驱动的时候 icm20608_remove 函数会执行。

4、probe 函数

在probe函数里面,添加初始化spi_device和icm20608

 315行,设置 SPI 为模式 0,也就是 CPOL=0, CPHA=0

316行,设置好 spi_device 以后需要使用 spi_setup 配置一下

318行,设置 icm20608dev 的 private_data 成员变量为 spi_device

321行,调用 icm20608_reginit 函数初始化 ICM20608,主要是初始化 ICM20608 指定寄存器

5、icm20608 寄存器读写与初始化

SPI 驱动最终是通过读写 icm20608 的寄存器来实现的,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对 icm20608 的初始化

①icm20608_read_regs函数

 从 icm20608 中读取连续多个寄存器数据;注意:在这实验中, SPI 为全双工通讯没有所谓的发送和接收长度之分。要读取或者发送 N 个字节就要封装 N+1 个字节,第 1 个字节是告诉设备我们要进行读还是写,后面的 N 个字节才是我们要读或者发送的数据。 因为是读操作,因此在第 77行设置第一个数据 bit7 位 1,表示读操作

67和71行,用kzalloc申请内存的时候, 效果等同于先是用 kmalloc() 申请空间 , 然用 memset() 来初始化 ,所有申请的元素都被初始化为 0,GFP_KERNEL,内核内存的正常分配. 可能睡眠.

81行,在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init, 函数原型如下:

void spi_message_init(struct spi_message *m)

m:要初始化的 spi_message。 返回值:无。

 82行,spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用 到 spi_message_add_tail 函数,此函数原型如下:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

t:要添加到队列中的 spi_transfer。 m:spi_transfer 要加入的 spi_message。 返回值:无

83行,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步 传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

int spi_sync(struct spi_device *spi, struct spi_message *message)

spi:要进行数据传输的 spi_device。 message:要传输的 spi_message。

返回值:错误返回负值。

89-92行,用kfree函数释放内存

②icm20608_write_regs函数

向 icm20608 连续写入多个寄存器数据。此函数和icm20608_read_regs 函数区别不大。

 123行,清零表示写操作

③icm20608_read_onereg函数

读取 icm20608 指定寄存器数据

④ icm20608_write_onereg函数

向 icm20608 指定寄存器写入数据

⑤ icm20608_readdata函数

读取 icm20608 六轴传感器和温度传感器原始数据值,应用程序读取 icm20608 的时候这些传感器原始数据就会上报给应用程序,先在设备结构体中添加对应数据类型

⑥icm20608_reginit 函数

 初始化 icm20608

6、icm20608_read函数

 当应用程序调用 read 函数读取 icm20608 设备文件的时候此函数就会执行。此函数调用上面编写好的icm20608_readdata 函数读取 icm20608 的原始数据并将其上报给应用程序。

代码如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include "icm20608reg.h"

#define ICM20608_CNT 1
#define ICM20608_NAME "icm20680"

/*设备结构体*/
struct icm20608_dev
{
    dev_t devid;           /* 设备号 	 */
    int major;             /* 主设备号 */
    int minor;             /* 次设备号 */
    struct cdev cdev;      /* cdev 	*/
    struct class *class;   /* 类 		*/
    struct device *device; /* 设备 	 */

    void *private_data;     /*私有数据 */
    signed int gyro_x_adc;  /* 陀螺仪X轴原始值 	 */
    signed int gyro_y_adc;  /* 陀螺仪Y轴原始值		*/
    signed int gyro_z_adc;  /* 陀螺仪Z轴原始值 		*/
    signed int accel_x_adc; /* 加速度计X轴原始值 	*/
    signed int accel_y_adc; /* 加速度计Y轴原始值	*/
    signed int accel_z_adc; /* 加速度计Z轴原始值 	*/
    signed int temp_adc;    /* 温度原始值 			*/
};
struct icm20608_dev icm20608dev;

/*
 * @description : 从 icm20608 读取多个寄存器数据
 * @param – dev : icm20608 设备
 * @param – reg : 要读取的寄存器首地址
 * @param – val : 读取到的数据
 * @param – len : 要读取的数据长度
 * @return : 操作结果
 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */
	if(!rxdata) {
		goto out1;
	}
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 表示读*/			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

out2:
	kfree(rxdata);					/* 释放内存 */
out1:	
	kfree(t);						/* 释放内存 */
	
	return ret;
}

/*
 * @description : 向 icm20608 多个寄存器写入数据
 * @param – dev : icm20608 设备
 * @param – reg : 要写入的寄存器首地址
 * @param – val : 要写入的数据缓冲区
 * @param – len : 要写入的数据长度
 * @return : 操作结果
 * */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{
   int ret = -1;
	unsigned char *txdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
	
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}
/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    u8 data = 0;
    icm20608_read_regs(dev, reg, &data, 1);
    return data;
}
/*icm20608写一个寄存器*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 buf = value;
    icm20608_write_regs(dev, reg, &buf, 1);
}
/*
 * @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
 * 				: 三轴加速度计和内部温度。
 * @param - dev	: ICM20608设备
 * @return 		: 无。
 */
void icm20608_readdata(struct icm20608_dev *dev)
{
    unsigned char data[14] = {0};
    icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);

    dev->accel_x_adc   = (signed short)((data[0] << 8)  | data[1]);
    dev->accel_y_adc   = (signed short)((data[2] << 8)  | data[3]);
    dev->accel_z_adc   = (signed short)((data[4] << 8)  | data[5]);
    dev->temp_adc      = (signed short)((data[6] << 8)  | data[7]);
    dev->gyro_x_adc    = (signed short)((data[8] << 8)  | data[9]);
    dev->gyro_y_adc    = (signed short)((data[10] << 8) | data[11]);
    dev->gyro_z_adc    = (signed short)((data[12] << 8) | data[13]);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev;
    return 0;
}
/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    signed int data[7];
    long err = 0;
    struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;

    icm20608_readdata(dev);
    data[0] = dev->gyro_x_adc;
    data[1] = dev->gyro_y_adc;
    data[2] = dev->gyro_z_adc;
    data[3] = dev->accel_x_adc;
    data[4] = dev->accel_y_adc;
    data[5] = dev->accel_z_adc;
    data[6] = dev->temp_adc;
    err = copy_to_user(buf, data, sizeof(data));
    return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* icm20608操作函数 */
struct file_operations icm20608_fops = {
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .release = icm20608_release,
    .read = icm20608_read,
};

/*
 * ICM20608内部寄存器初始化函数 
 * @param  	: 无
 * @return 	: 无
 */
void icm20608_reginit(void)
{
    u8 value = 0;

    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80); /* 复位,复位后为0x40,睡眠模式          */
    mdelay(50);
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠,自动选择时钟                   */
    mdelay(50);

    value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
    printk("ICM20608 ID = %#X\r\n", value);

    value = icm20608_read_onereg(&icm20608dev, ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1 = %#X\r\n", value);

    icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV,   0x00);    /* 输出速率是内部采样率					*/
    icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG,  0x18);   /* 陀螺仪±2000dps量程 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);  /* 加速度计±16G量程 					*/
    icm20608_write_onereg(&icm20608dev, ICM20_CONFIG,       0x04);        /* 陀螺仪低通滤波BW=20Hz 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2,0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2,   0x00);    /* 打开加速度计和陀螺仪所有轴 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG,  0x00);   /* 关闭低功耗 						*/
    icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN,      0x00);       /* 关闭FIFO	 */
}
static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;
    /* 1、构建设备号 */
    icm20608dev.major = 0;
    if (icm20608dev.major)
    {
        icm20608dev.devid = MKDEV(icm20608dev.major, 0);
        ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
        icm20608dev.minor = MINOR(icm20608dev.devid);
    }
    if (ret < 0)
    {
        printk("icm20608 chrdev_region err!\r\n");
        goto fail_devid;
    }
    printk("icm20608dev major = %d, minor = %d\r\n", icm20608dev.major, icm20608dev.minor);
    /* 2、注册设备 */
    icm20608dev.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608dev.cdev, &icm20608_fops);
    ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
    if (ret < 0)
    {
        printk("icm20608 cdev_add err!\r\n");
        goto fail_cdev;
    }
    /* 3、创建类 */
    icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
    if (IS_ERR(icm20608dev.class))
    {
        ret = PTR_ERR(icm20608dev.class);
        printk("icm20608 chrdev_class err!\r\n");
        goto fail_class;
    }
    /* 4、创建设备 */
    icm20608dev.device = device_create(icm20608dev.class, NULL,
                                       icm20608dev.devid, NULL, ICM20608_NAME);
    if (IS_ERR(icm20608dev.device))
    {
        ret = PTR_ERR(icm20608dev.device);
        printk("icm20608 chrdev_device err!\r\n");
        goto fail_device;
    }

    /*初始化spi_device*/
    spi->mode = SPI_MODE_0;
    spi_setup(spi);
    /*设置icm20608私有数据*/
    icm20608dev.private_data = spi;

    /*初始化icm20608*/
    icm20608_reginit();

    printk("icm20608_probe\r\n");
    return 0;

fail_device:
    class_destroy(icm20608dev.class);
fail_class:
    cdev_del(&icm20608dev.cdev);
fail_cdev:
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:
    return ret;
}

static int icm20608_remove(struct spi_device *spi)
{
    /* 删除字符设备 */
    cdev_del(&icm20608dev.cdev);
    /*注销设备号*/
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
    /*摧毁设备*/
    device_destroy(icm20608dev.class, icm20608dev.devid);
    /*摧毁类*/
    class_destroy(icm20608dev.class);
    
    printk("icm20608_remove\r\n");
    return 0;
}

/*传统匹配*/
struct spi_device_id icm20608_id[] = {
    {"my,icm20608", 0},
    {}
};
/*设备树匹配*/
static const struct of_device_id icm20608_of_match[] = {
    {.compatible = "my,icm20608"},
    {}
};
/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {
    .probe = icm20608_probe,
    .remove = icm20608_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "icm20608",
        .of_match_table = icm20608_of_match,
    },
    .id_table = icm20608_id,
};

/*驱动入口*/
static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}
/*驱动出口*/
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

四、APP编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/input.h>

/*
    argc:应用程序参数个数(argv数组元素个数)
    argv:具体参数,也可以写作char **argv
    ./icm20608APP <filename>    
    ./icm20608APP  /dev/icm20608
*/


int main(int argc, char *argv[])
{
    int fd;
	char *filename;
	signed int databuf[7];
	unsigned char data[14];
	signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
	signed int accel_x_adc, accel_y_adc, accel_z_adc;
	signed int temp_adc;

	float gyro_x_act, gyro_y_act, gyro_z_act;
	float accel_x_act, accel_y_act, accel_z_act;
	float temp_act;

	int ret = 0;
    
    /*判断命令行输入参数是否正确*/
    if(argc != 2){
        printf("error usage!\r\n");
        return -1;
    }
    /*用指针指向文件*/
    filename = argv[1];
    /*打开文件*/
    fd = open(filename , O_RDWR);
    if(fd < 0){
        printf("file open failed\r\n",filename);
        return -1;
    }
   while(1)
   {
   ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) { 			/* 数据读取成功 */
			gyro_x_adc = databuf[0];
			gyro_y_adc = databuf[1];
			gyro_z_adc = databuf[2];
			accel_x_adc = databuf[3];
			accel_y_adc = databuf[4];
			accel_z_adc = databuf[5];
			temp_adc = databuf[6];

			/* 计算实际值 */
			gyro_x_act = (float)(gyro_x_adc)  / 16.4;
			gyro_y_act = (float)(gyro_y_adc)  / 16.4;
			gyro_z_act = (float)(gyro_z_adc)  / 16.4;
			accel_x_act = (float)(accel_x_adc) / 2048;
			accel_y_act = (float)(accel_y_adc) / 2048;
			accel_z_act = (float)(accel_z_adc) / 2048;
			temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;


			printf("\r\n原始值:\r\n");
			printf("gx = %d, gy = %d, gz = %d\r\n", 
                    gyro_x_adc, gyro_y_adc, gyro_z_adc);
			printf("ax = %d, ay = %d, az = %d\r\n", 
                        accel_x_adc,accel_y_adc, accel_z_adc);
			printf("temp = %d\r\n", temp_adc);
			printf("实际值:");
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", 
                        gyro_x_act, gyro_y_act, gyro_z_act);
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", 
                        accel_x_act, accel_y_act, accel_z_act);
			printf("act temp = %.2f°C\r\n", temp_act);
		}
		usleep(100000); /*100ms */
   }

    /*关闭文件*/
    close(fd);

    return 0;
}

最终将传感器原始数据和得到的实际值显示在终端上。

编译验证

在编译APP的时候,编译命令加入如下参数即可使能硬件浮点编译

-march-armv7-a -mfpu-neon -mfloat=hard

编译之后有没有使用硬件浮点呢?使用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了,输入如下命令:

arm-linux-gnueabihf-readelf -A icm20608App

 使用APP测试如下

 

 这是会一直打印的,部分截图如上图

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

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

相关文章

深度学习目标检测ui界面-交通标志检测识别

深度学习目标检测ui界面-交通标志检测识别 为了将算法封装起来&#xff0c;博主尝试了实验pyqt5的上位机界面进行封装&#xff0c;其中遇到了一些坑举给大家避开。这里加载的训练模型参考之前写的博客&#xff1a; 自动驾驶目标检测项目实战(一)—基于深度学习框架yolov的交通…

C++ 二级指针的理解

指针一直很抽象&#xff0c;不是很容易理解&#xff0c;尤其是二级指针。有天路上&#xff0c;脑子里突然想起二级指针&#xff0c;并开始思考什么是二级指针&#xff0c;经过分析和调试验证&#xff0c;对二级指针有了进一步的认识。故写下此篇。 一级指针 假设变量 a、p 的…

消息队列 rabbitmq 学习

RabbitMQ是一个在AMQP基础上完整的&#xff0c;可复用的企业消息系统。他遵循Mozilla Public License开源协议。 MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息&#xff08;针对应用程序的数…

[YOLO] yolov4 博客笔记

在有了yolov3的基础上&#xff0c;yolov4的网络结构还是很容易看懂的。 这篇博客写的整个yolov4的网络概况 【经典论文解读】YOLOv4 目标检测https://blog.csdn.net/qq_41204464/article/details/119673960?ops_request_misc%257B%2522request%255Fid%2522%253A%25221677916…

SpringBoot一行代码实现文件上传20个平台!少写代码到极致!

又是做好人好事的一天&#xff0c;有个小可爱私下问我有没有好用的springboot文件上传工具&#xff0c;这不巧了嘛&#xff0c;正好我私藏了一个好东西&#xff0c;顺便给小伙伴们也分享一下&#xff0c;demo地址放在文末了。文件上传在平常不过的一个功能&#xff0c;做后端开…

SpringBoot (三) 整合数据库访问 jdbcTemplate、MyBatis

哈喽&#xff0c;大家好&#xff0c;我是有勇气的牛排&#xff08;全网同名&#xff09;&#x1f42e;&#x1f42e;&#x1f42e; 有问题的小伙伴欢迎在文末评论&#xff0c;点赞、收藏是对我最大的支持&#xff01;&#xff01;&#xff01;。 Spring Data了解下&#xff1…

实习日记_C#——Day2

运算符重载 通过关键字operator后跟运算符的符号来定义。 public static Box operator (Box b, Box c) {Box box new Box();box.height b.height c.height;return box; }Box box1 new Box(1); Box box2 new Box(2); Box box3 new Box(); bo3 box1 box2;接口&#xff…

基于灵动 MM32 微控制器的便携式血氧仪方案

基于灵动 MM32 微控制器的便携式血氧仪&#xff1a; - Cortex-M0() 最高主频 72MHz 可实现血氧饱和度信号采集、算法操作和 LED 显示操作 - 高性能的 1Msps 12b ADC 能对光电采样结果进行大数据量的暂存和处理&#xff0c;提高采样的效率并有助于对结果做高精度的计算 - 100…

排序中常见的一些指标

1、错误率与精度 错误率与精度是分类任务中最常用的两种性能度量&#xff0c;错误率是指分类错误的样本占样本总数的比例&#xff0c;精度则是分类正确的样本数占样本总数的比例。 错误率&#xff1a; 精度&#xff1a; 2、准确率/召回率/FScore True Positive(真正例, TP)&…

SpringMVC中的常用注解

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.2、常用的注解3.2、常用的注解 Controller&#xff1a;代表此类是一个控制器&#xff0c;需要配置包的扫描。Spring MVC 是通过组件扫描机制查找应用中的控制器类的 在Spring6.0之后要求控制层必须添加该注解才会被识别成…

c++11 标准模板(STL)(std::unordered_map)(二)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…

计算机网络-- 网络层(day06)

文章目录网络层思维导图IPv4地址的应用规划定长的子网掩码变长的子网掩码VLSMIP数据报的发送和转发过程主机发送IP数据报路由器转发IP数据报静态路由选择动态路由选择路由选择协议概述常见的路由选择协议路由器的基本结构路由信息协议RIP的基本工作原理开放最短路径优先OSPF的基…

nandflash VS norflash

介绍下nandflash NAND Flash是一种非易失性存储器&#xff08;Non-Volatile Memory&#xff0c;简称NVM&#xff09;&#xff0c;以闪存为基础&#xff0c;是目前应用最广泛的存储器之一。 它利用了固态电路技术&#xff0c;可以将数百万个晶体管集成在一张芯片上&#xff0c…

从输入URL到Web页面呈现的全过程

当用户在浏览器的地址栏中输入 URL 并点击回车后&#xff0c;页面是如何呈现的。 简单来说&#xff0c;当用户在浏览器的地址栏中输入 URL 并点击回车后&#xff0c;浏览器从服务端获取资源&#xff0c;然后将内容显示在页面上。这个过程经过了&#xff1a;浏览器缓存 -> D…

利用Nginx给RStudio-Server配置https

前篇文档&#xff0c;我这边写了安装RStudio-Server的方法。默认是http的访问方式&#xff0c;现在我们需要将其改成https的访问方式。 1、给服务器安装Nginx&#xff1a;参照之前的安装Nginx的方法。 2、创建/usr/local/nginx/ssl目录&#xff1a; mkdir /usr/local/nginx/ss…

P6入门:了解P6 Professional 工具栏及地图分享

目录 引言 相关分享 引言 凭借更大的灵活性和增强的自定义功能&#xff0c;最新版本的 Oracle Primavera P6 Professional 的界面比早期版本有了巨大改进。对于有经验的伙伴来说&#xff0c;它仍然是熟悉的领域&#xff0c;几乎所有预期的功能都显示在前面。该界面可以更好地…

【汇编】三、寄存器(一只 Assember 的成长史)

嗨~你好呀&#xff01; 我是一名初二学生&#xff0c;热爱计算机&#xff0c;码龄两年。最近开始学习汇编&#xff0c;希望通过 Blog 的形式记录下自己的学习过程&#xff0c;也和更多人分享。 上篇系列文章链接&#xff1a;【汇编】二、预备知识&#xff08;一只 Assember 的…

Spring Boot结合IDEA自带Maven插件快速切换profile | Spring Cloud 10

一、前言 IDEA是目前 Java 开发者中使用最多的开发工具&#xff0c;它有着简约的设计风格&#xff0c;强大的集成工具&#xff0c;便利的快捷键。 在项目项目整个开发运维周期中&#xff0c;我们的的项目往往需要根据不同的环境&#xff0c;使用不同的文件配置。 比如以下部…

机器学习:Transformer

Transformer sequence-to-sequence(seq2seq) 很大语音没有文本&#xff0c;7000种中超半数没有文字。 遇到的问题&#xff1a; 遇到问题时候可以先不管它&#xff0c;先出一个baseline看看效果&#xff0c;后续再进行提升。 tts&#xff1a; 文本转语音&#xff0c;语音合成…

【图神经网络】李宏毅

GNN 引入 假如要预测一个人是否是凶手。可以通过每个角色的特征训练出一个分类器。 有没有我们忽略的信息&#xff0c;或者我们可以利用但没有完全利用的信息。就是角色的关系。 这些角色关系可以让我们在做分类的时候获得一些额外的信息&#xff0c;可以帮助我们做更好的mode…