• 驱动程序开发:SPI设备驱动


    Linux下SPI驱动简介

      SPI驱动框架和I2C驱动框架是十分相似的,不同的是因为SPI是通过片选引脚来选择从机设备的,因此SPI不再需要像I2C那样先进行寻址操作(查询从机地址)后再进行对应寄存器的数据交互,并且SPI是全双工通信,通信速率要远高于I2C。

      但是SPI显然占用的硬件资源也比I2C要多,并且SPI没有了像I2C那样指定的流控制(例如开始、停止信号)和没有了像I2C应当机制(导致无法确认数据是否接收到了)。

    SPI架构概述

      Linux的SPI体系结构可以分为3个组成部分:
      spi核心(SPI Core):SPI Core是Linux内核用来维护和管理spi的核心部分,SPI Core提供操作接口函数,允许一个spi master,spi driver和spi device初始化时在SPI Core中进行注册,以及退出时进行注销。
      spi控制器驱动或适配器驱动(SPI Master Driver):SPI Master针对不同类型的spi控制器硬件,实现spi总线的硬件访问操作。SPI Master 通过接口函数向SPI Core注册一个控制器。
      spi设备驱动(SPI Device Driver):SPI Driver是对应于spi设备端的驱动程序,通过接口函数向SPI Core进行注册,SPI Driver的作用是将spi设备挂接到spi总线上;Linux的软件架构图如下图所示:
    在这里插入图片描述

    SPI适配器(控制器)

    /* 有省略 */
    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);
    	int	*cs_gpios;	
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    SPI 主机驱动(或设配器驱动)的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
    spi_master 申请与释放:
    struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
    void spi_master_put(struct spi_master *master)
    spi_master 的注册与注销:
    int spi_register_master(struct spi_master *master)
    void spi_unregister_master(struct spi_master *master)

    SPI设备驱动

     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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    spi_driver注册示例

    示例代码 62.1.1.3 spi_driver 注册示例程序
    1 /* probe 函数 */
    2 static int xxx_probe(struct spi_device *spi)
    3 {
    4 	/* 具体函数内容 */
    5 		return 0;
    6 }
    7 8
    /* remove 函数 */
    9 static int xxx_remove(struct spi_device *spi)
    10 {
    11 /* 具体函数内容 */
    12		 return 0;
    13 }
    14 /* 传统匹配方式 ID 列表 */
    15 static const struct spi_device_id xxx_id[] = {
    16 		{"xxx", 0},
    17 		{}
    18 };
    19
    20 /* 设备树匹配列表 */
    21 static const struct of_device_id xxx_of_match[] = {
    22 		{ .compatible = "xxx" },
    23 		{ /* Sentinel */ }
    24 };
    25
    26 /* SPI 驱动结构体 */
    27 static struct spi_driver xxx_driver = {
    28 		.probe = xxx_probe,
    29 		.remove = xxx_remove,
    30 		.driver = {
    31 		.owner = THIS_MODULE,
    32 		.name = "xxx",
    33 		.of_match_table = xxx_of_match,
    34 },
    35 		.id_table = xxx_id,
    36 };
    37
    38 /* 驱动入口函数 */
    39 static int __init xxx_init(void)
    40 {
    41 		return spi_register_driver(&xxx_driver);
    42 }
    43
    44 /* 驱动出口函数 */
    45 static void __exit xxx_exit(void)
    46 {
    47		spi_unregister_driver(&xxx_driver);
    48 }
    49
    50 module_init(xxx_init);
    51 module_exit(xxx_exit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    SPI 设备和驱动匹配过程

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

    131 struct bus_type spi_bus_type = {
    132 	.name = "spi",
    133 	.dev_groups = spi_dev_groups,
    134 	.match = spi_match_device,
    135 	.uevent = spi_uevent,
    136 };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    99 static int spi_match_device(struct device *dev, struct device_driver *drv)
    100 {
    101 	const struct spi_device *spi = to_spi_device(dev);
    102 	const struct spi_driver *sdrv = to_spi_driver(drv);
    103
    104 /* Attempt an OF style match */
    105 if (of_driver_match_device(dev, drv))
    106 	return 1;
    107
    108 /* Then try ACPI */
    109 if (acpi_driver_match_device(dev, drv))
    110 	return 1;
    111
    112 if (sdrv->id_table)
    113 	return !!spi_match_id(sdrv->id_table, spi);
    114
    115 return strcmp(spi->modalias, drv->name) == 0;
    116 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

      spi_match_device 函数和 i2c_match_device 函数对于设备和驱动的匹配过程基本一样。
      第 105 行, of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。
      第 109 行, acpi_driver_match_device 函数用于 ACPI 形式的匹配。
      第 113 行, spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
      第 115 行,比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。

    编写imc20608六轴传感器SPI驱动

    编写可以参考NXP官方spi-imx.c驱动程序和\Documentation\devicetree\bindings\spi\目录下的fsl-imx-cspi.txt、 spi-bus.txt绑定文档

    硬件原理图:
    在这里插入图片描述
    具体引脚复用和什么配置参数不懂的可以查看I2C驱动那篇。

    设备树编写操作

    第一步:在pinctrl_ecspi3子节点下添加属性(寄存器地址及配置参数),如下。

    	pinctrl_ecspi3: ecspi3grp {
    		fsl,pins = <
    			MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20		0x10B0	/* 不使用硬件片选,用普通IO在软件程序上自行控制 */
    			MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 	0x10B1
    			MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI		0x10B1
    			MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO		0x10B1
    		>;
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第二步:在ecspi3节点上追加属性,如下。

    /* 追加ecspi3字节点属性内容 */
    &ecspi3 {
    	fsl,spi-num-chipselects = <1>;	/* 1个片选 */
    	cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;		/* “cs-gpios”作为软件片选使用 */
    	pinctrl-names = "default";
    	pinctrl-0 = <&pinctrl_ecspi3>;
    	status = "okay";
    	
    	#address-cells = <1>;	/* ecspi3 node下使用1个u32来代表address */
    	#size-cells = <0>;	/* ecspi3 node下使用0个u32来代表size */
    
    	/* 挂载到ecspi3总线上的对应具体spi设备 */
    	/* 节点名称spidev0,芯片名称icm20608 */
    	/* @后面的0表示SPI芯片接到哪个硬件片选上,现在是软件片选,所以是无效的 */
    	spidev0: icm20608@0 {
    		reg = <0>;	/* 0存取spidev0设备的address,而spidev0设备的大小size为空,这里指片选第0个 */
    		compatible = "alientek,icm20608";	/* 兼容属性 */
    		spi-max-frequency = <8000000>;	/* SPI最大时钟频率 */
    	};
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    重新加载设备树文件,在/sys/bus/spi/devices目录下查看,如下。
    在这里插入图片描述
    在这里插入图片描述

    具体的imc20608驱动程序

    icm20608.c

    /* 
     *  根据linux内核的程序查找所使用函数的对应头文件。 
     */  
    #include 
    #include        //MODULE_LICENSE,MODULE_AUTHOR  
    #include          //module_init,module_exit  
    #include        //printk  
    #include            //struct file_operations  
    #include          //kmalloc, kfree  
    #include       //copy_to_user,copy_from_user  
    #include            //ioremap,iounmap  
    #include          //struct cdev,cdev_init,cdev_add,cdev_del  
    #include        //class  
    #include            //of_find_node_by_path  
    #include       //of_get_named_gpio  
    #include          //gpio_request,gpio_direction_output,gpio_set_number  
    #include        //atomic_t  
    #include        //irq_of_parse_and_map
    #include     //request_irq
    #include         //timer_list
    #include       //jiffies
    #include        //atomic_set
    #include         //input
    #include   //platform
    #include         //mdelay
    #include 
    #include 
    #include "icm20608.h"
    
    /* 0 设备结构体 */
    struct icm20608_dev {
        dev_t devid;            /* 设备号 */
        int major;              /* 主设备号 */
        int minor;              /* 主设备号 */
        int count;              /* 设备个数 */
        char* name;             /* 设备名字 */
        struct cdev cdev;       /* 注册设备结构体 */
        struct class *class;    /* 类 */
        struct device *device;  /* 设备 */
        void *private_data;     /* 私有数据 */
        struct device_node *nd; /* 设备节点 */
        int cs_gpio;            /* 软件片选IO */
    	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;		/* 温度原始值 */
    };
    static struct icm20608_dev icm20608dev;  /* 实例icm20608_dev结构体 */
    
    void icm20608_readdata(struct icm20608_dev *dev);
    
    /* 2.2.1 打开字符设备文件 */
    static int icm20608_open(struct inode *inde,struct file *filp) {
    	filp->private_data = &icm20608dev; /* 设置私有数据 */
        return 0;
    }
    
    /* 2.2.2 关闭字符设备文件 */
    static int icm20608_release(struct inode *inde,struct file *filp) {
        return 0;
    }
    /* 2.2.3  向字符设备文件读取数据 */
    static ssize_t icm20608_read(struct file *filp,char __user *buf,
    			size_t count,loff_t *ppos) {
    
    	signed int data[7];
    	long err = 0;
    	struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;   //提取私有属性
    
        /* 5.2 读取icm20608六轴传感器原始数据并拷贝到用户上  */
    	icm20608_readdata(dev);     //读取icm20608六轴传感器原始数据
    	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;
    }
    /* 2.2.4 向字符设备文件写入数据 */
    static ssize_t icm20608_write(struct file *filp,const char __user *buf,
                size_t count,loff_t *ppos) {
    
        struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
    	return 0;
    }
    
    #if 0
    /* 4.2.1 spi读寄存器,读取n个字节寄存器 */
    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;
    }
    
    /* 4.2.2 spi写寄存器 */
    static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len) {
        int ret = 0;
        unsigned char *txdata;
        struct spi_message m;
        struct spi_transfer *t;
        struct spi_device *spi = (struct spi_device *)dev->private_data;    //提取spi私有数据
    
        /* 构建spi_transfer */
        t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
        if(!t) {
    		return -ENOMEM;
    	}
        txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
    	if(!txdata) {
    		kfree(txdata);				/* 释放内存 */
    	}
        /* 第一步:发送要写入的寄存器地址 */
        txdata[0] = reg & 0XEF; //MOSI发送8位地址,但是实质地址只有前7位,第8位是读写位(W:0)
        memcpy(&txdata[1], buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
        t->tx_buf = txdata; //发送的数据
        t->len = 1 + len; //发送数据长度
        spi_message_init(&m); //初始化spi_message
        spi_message_add_tail(t,&m); //将spi_transfer添加进spi_message里面
        ret = spi_sync(spi,&m);   //以同步方式发送数据
        if(ret < 0) {
            printk("spi_sync error!\r\n");
        }    
        kfree(txdata);				/* 释放内存 */
        kfree(t);					/* 释放内存 */
        return ret;
    }
    #endif
    
    #if 1
    /***********************************************************************************/
    /******** 上面读写多个寄存器的方法函数可以使用内核提供的API:spi_write和spi_read代替 ********/
    /* 4.2.1 spi读寄存器,读取n个字节寄存器 */
    static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) {
        struct spi_device *spi = (struct spi_device *)dev->private_data;    //提取spi私有数据
        u8 reg_addr = 0;
        /* 片选引脚cs拉低,spi通讯有效 */
        // gpio_set_value(dev->cs_gpio, 0);
        reg_addr = reg | 0X80;  //寄存器地址,读
    
        spi_write_then_read(spi, &reg_addr, 1, buf, len);  //保证发送完地址后片选信号连续并接着读取数据
        // spi_write(spi, ®_addr, 1);   //发送读寄存器地址
        // spi_read(spi, buf, len);    //读寄存器内容
        // gpio_set_value(dev->cs_gpio, 1);
        return 0;
    }
    
    static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len) {
        struct spi_device *spi = (struct spi_device *)dev->private_data;    //提取spi私有数据
    
        u8 *txdata;
    
        txdata = kzalloc(len +1, GFP_KERNEL);  //申请一段内存,长度len+1字节
    
        txdata[0] = reg & 0XEF;  //寄存器地址,写
        memcpy(&txdata[1], buf, len);   //将发送的数据拷贝过来
        spi_write(spi, txdata, len+1);   //发送写寄存器地址
        return 0;
    }
    /***********************************************************************************/
    #endif
    
    /* 4.3.1 icm20608读取单个寄存器 */
    static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {
        u8 data = 0;
        icm20608_read_regs(dev,reg, &data, 1);
        return data;
    }
    
    /* 4.3.2 icm20608写一个寄存器 */
    static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {
        icm20608_write_regs(dev, reg, &value, 1);
    }
    
    /* 5.1 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、三轴加速度计和内部温度。 */
    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]);
    }
    
    /* 4.1 icm20608里面的寄存器初始化 */
    void icm20608_reg_init(struct icm20608_dev *dev) {
        u8 value = 0;
    
        /* 4.4 */
        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(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 
        icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 
        icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 
        icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 
        icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
        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);
    }
    
    /* 2 icm20608操作集合 */
    static const struct file_operations icm20608_ops = {
        .owner      =   THIS_MODULE,
        .open       =   icm20608_open,
        .release    =   icm20608_release,
        .read       =   icm20608_read,
        .write      =   icm20608_write,
    };
    
    /* 1.6 probe函数 */
    static int icm20608_probe(struct spi_device *spi) {
        int ret = 0;
        printk("icm20608_probe successful!\r\n");
        /*===================== 2.1 搭建字符设备框架 =====================*/
        // /* 2.1.1 配置设备结构体的参数 */
        icm20608dev.name = "icm20608";   //设备名称
        icm20608dev.major = 0;   //主设备号
        icm20608dev.count = 1;
        /* 2.1.2 分配或定义设备号并注册设备号 */
        /* 如果主设备号不为0,否则为自定义设备号,否则为由系统分配设备号 */
        if(icm20608dev.major) {
           icm20608dev.devid = MKDEV(icm20608dev.major, 0);
            ret = register_chrdev_region(icm20608dev.devid,icm20608dev.count,icm20608dev.name);   //注册设备号
        } else {
            alloc_chrdev_region(&icm20608dev.devid,0,icm20608dev.count,icm20608dev.name);   //自动分配设备号
            icm20608dev.major = MAJOR(icm20608dev.devid);   //主设备号
            icm20608dev.minor = MINOR(icm20608dev.devid);   //次设备号
        }    
        if (ret < 0) {
            printk("icm20608 chrdev region failed!\r\n");
            goto fail_devid;    //注册设备号失败
        }
        printk("icm20608 major = %d, minor = %d \r\n",icm20608dev.major,icm20608dev.minor);  //打印主次设备号
        /* 2.1.3 添加字符设备 */
        cdev_init(&icm20608dev.cdev, &icm20608_ops);
        ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, icm20608dev.count);
        if(ret < 0) {
            printk("icm20608 add char device failed!\r\n");
            goto fail_cdev;
        }
        /* 2.1.4 自动创建设备节点 */
        icm20608dev.class = class_create(THIS_MODULE,icm20608dev.name); //创建类
        if(IS_ERR(icm20608dev.class)) {
            ret = PTR_ERR(icm20608dev.class);
            printk("icm20608 class_create failed!\r\n");
            goto fail_class;
        }
        icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, icm20608dev.name); //创建设备
        if(IS_ERR(icm20608dev.device)) {
            ret = PTR_ERR(icm20608dev.device);
            printk("icm20608 device_create failed!\r\n");
            goto fail_device;
        }
    
        /* 2.1.1 设置icm20608的私有数据为spi,类似于I2C的client */
        icm20608dev.private_data = spi;  
        /* 2.1.2 设置SPI的模式 */
        spi->mode = SPI_MODE_0; //MODE0,CPOL=0, CPHA=0
    
    /************************************************************************************/
    /* 虽然使用的是GPIO模式的软件片选,但是使用spi_driver内核自动会给我们在调用API的时候控制
     * 非要自己去控制的话,cs-gpio这个属性名字不能带s(cs-gpios),因为of_get_named_gpio会报错
     */
        /* 3.1 获取片选引脚 */
        /* 3.1.1 获取片选引脚的节点,而icm20608dev.nd = spi->dev.of_node;获取的是spidev0的节点 */
        /* 因为我们需要的是子节点spidev0上一层的父节点ecspi3,因此需要使用of_get_parent */
        // icm20608dev.nd = of_get_parent(spi->dev.of_node);
        // /* 3.1.2 获取片选引脚的节点属性---GPIO编号 */
        // icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
        // if(icm20608dev.cs_gpio < 0) {
        //     printk("can't get cs-gpios!\r\n");
        //     ret = -EINVAL;
        //     goto fail_getgpio;
        // }    
        // /* 3.1.3 申请CS对应的GPIO */
        // ret = gpio_request(icm20608dev.cs_gpio,"cs");
        // if(ret) {
        //     printk("failed to request the cs-gpio!\r\n");
        //     ret = -EINVAL;
        //     goto fail_requestgpio;
        // }
        // /* 3.1.4 设置cs gpio的输出方向及默认电平 */
        // ret = gpio_direction_output(icm20608dev.cs_gpio,1);    //设置led对应的GPIO1-IO20为输出模式,并输出高电平
        // if(ret) {
        //     printk("failed to drive the cs reset gpio!\r\n");
        //     goto fail_setouput;
        // }
    /***********************************************************************************/
    
        /* 4.1 调用icm20608初始化函数 */
        icm20608_reg_init(&icm20608dev);
    
        return 0;
    
    fail_setouput:  //设置IO输出失败
        gpio_free(icm20608dev.cs_gpio);
    fail_requestgpio:   //申请IO失败
    fail_getgpio:   //获取CS对应的设备节点中的GPIO信息失败
        device_destroy(icm20608dev.class,icm20608dev.devid);
    fail_device:    //创建设备失败
        class_destroy(icm20608dev.class); 
    fail_class: //创建类失败
        cdev_del(&icm20608dev.cdev);
    fail_cdev:   //注册设备或者叫添加设备失败
        unregister_chrdev_region(icm20608dev.devid,icm20608dev.count);  
    fail_devid:  //分配设备号失败
        return ret;
    }
    
    /* 1.7 remove函数 */
    static int icm20608_remove(struct spi_device *spi) {
        printk("icm20608_remove finish\r\n");
    
        /* 3.5 释放申请的GPIO */
        gpio_free(icm20608dev.cs_gpio);
        /* 3.4 摧毁设备 */
        device_destroy(icm20608dev.class,icm20608dev.devid);    
        /* 3.3 摧毁类 */
        class_destroy(icm20608dev.class);  
        /* 3.2 注销字符设备 */
        cdev_del(&icm20608dev.cdev); 
        /* 3.1 注销设备号*/
        unregister_chrdev_region(icm20608dev.devid,icm20608dev.count);  
        return 0;
    }
    
    /* 1.4 传统的匹配表 */
    static const struct spi_device_id icm20608_id[] = {
        {"alientek,icm20608", 0},
        {}
    };
    
    /* 1.5 设备树匹配表 */
    static const struct of_device_id icm20608_of_match[] = {
        { .compatible = "alientek,icm20608" },
        {}
    };
    
    /* 1.3 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驱动设备
    }  
    	  
    // /* 驱动许可和个人信息 */ 
    module_init(icm20608_init);
    module_exit(icm20608_exit); 
    MODULE_LICENSE("GPL");  
    MODULE_AUTHOR("djw");  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421

    在这里插入图片描述

      这里解释一下上图:红色框框是使用软件片选cs并属性名称为“cs-gpio”不带s时,内核就不能自动给我们调用相应API控制片选cs,那么此时我们就要自己手动控制cs信号了。
    蓝色框框是在我们使用“cs-gpios”带s时,不能直接这样使用,因为在每次调用API后片选cs都会被内核置高(无效状态),那么读寄存器内容通信就被间断了(发送完寄存器地址后读寄存器内容期间片选cs是不能置为无效状态的)。

    icm20608.h

    #ifndef _BSP_ICM20608_H
    #define _BSP_ICM20608_H
    
    
    #define ICM20608G_ID			0XAF	/* ID值 */
    #define ICM20608D_ID			0XAE	/* ID值 */
    
    /* 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    icm20608APP.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /*
     * argc:应用程序参数个数 
     * argv[]:具体打参数内容,字符串形式 
     * ./imc20608APP 
     * ./imc20608APP /dev/imc20608
     */
    
    /*
    * @description : main 主程序
    * @param - argc : argv 数组元素个数
    * @param - argv : 具体参数
    * @return : 0 成功;其他 失败
    */
    int main(int argc, char *argv[])
    {
        int fd, ret;
        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;
    
        /* 判断输入的元素个数 */
        if(argc != 2) {
            printf("ERROR USAGE!\r\n");
            return -1;
        }
    
        filename = argv[1];     //获取驱动文件的路径
        fd = open(filename,O_RDWR); //根据文件路径以读写方式打开文件
        if(fd < 0) {
            printf("file %s 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);
        }
        
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    操作及现象

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    uniapp的h5项目怎样分享指定信息到Facebook
    React批处理原理及性能优化实践
    golang channel
    进程间的通信终章之【消息队列,信号量,共享内存】
    【POJ No. 3104】 烘干衣服 Drying
    tauri+vue开发小巧的跨OS桌面应用-股票体检
    基于机器学习的搜索推荐系统
    Proteus的编译运行(以AT89C51为例)
    【Day_15 0510】查找输入整数二进制中1的个数
    删除不成功的免密登录重新做免密
  • 原文地址:https://blog.csdn.net/morecrazylove/article/details/126850983