驱动程序开发:SPI设备驱动_spi驱动-程序员宅基地

技术标签: linux  Linux驱动开发  驱动开发  嵌入式  

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;	
......

  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;
};

  可以看出,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);

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 };

  可以看出, 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 }

  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
		>;
	};

第二步:在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最大时钟频率 */
	};
};

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

具体的imc20608驱动程序

icm20608.c

/* 
 *  根据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 "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, &reg_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");  

在这里插入图片描述

  这里解释一下上图:红色框框是使用软件片选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

icm20608APP.c

#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[]:具体打参数内容,字符串形式 
 * ./imc20608APP <filename>
 * ./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;
}
操作及现象

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/morecrazylove/article/details/126850983

智能推荐

leetcode 172. 阶乘后的零-程序员宅基地

文章浏览阅读63次。题目给定一个整数 n,返回 n! 结果尾数中零的数量。解题思路每个0都是由2 * 5得来的,相当于要求n!分解成质因子后2 * 5的数目,由于n中2的数目肯定是要大于5的数目,所以我们只需要求出n!中5的数目。C++代码class Solution {public: int trailingZeroes(int n) { ...

Day15-【Java SE进阶】IO流(一):File、IO流概述、File文件对象的创建、字节输入输出流FileInputStream FileoutputStream、释放资源。_outputstream释放-程序员宅基地

文章浏览阅读992次,点赞27次,收藏15次。UTF-8是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。文件字节输入流:每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1。注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码。定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。UTF-8字符集:汉字占3个字节,英文、数字占1个字节。GBK字符集:汉字占2个字节,英文、数字占1个字节。GBK规定:汉字的第一个字节的第一位必须是1。_outputstream释放

jeecgboot重新登录_jeecg 登录自动退出-程序员宅基地

文章浏览阅读1.8k次,点赞3次,收藏3次。解决jeecgboot每次登录进去都会弹出请重新登录问题,在utils文件下找到request.js文件注释这段代码即可_jeecg 登录自动退出

数据中心供配电系统负荷计算实例分析-程序员宅基地

文章浏览阅读3.4k次。我国目前普遍采用需要系数法和二项式系数法确定用电设备的负荷,其中需要系数法是国际上普遍采用的确定计算负荷的方法,最为简便;而二项式系数法在确定设备台数较少且各台设备容量差..._数据中心用电负荷统计变压器

HTML5期末大作业:网页制作代码 网站设计——人电影网站(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 dreamweaver作业静态HTML网页设计模板_网页设计成品百度网盘-程序员宅基地

文章浏览阅读7k次,点赞4次,收藏46次。HTML5期末大作业:网页制作代码 网站设计——人电影网站(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 dreamweaver作业静态HTML网页设计模板常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 明星、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 军事、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他 等网页设计题目, A+水平作业_网页设计成品百度网盘

【Jailhouse 文章】Look Mum, no VM Exits_jailhouse sr-iov-程序员宅基地

文章浏览阅读392次。jailhouse 文章翻译,Look Mum, no VM Exits!_jailhouse sr-iov

随便推点

CSS中设置背景的7个属性及简写background注意点_background设置背景图片-程序员宅基地

文章浏览阅读5.7k次,点赞4次,收藏17次。css中背景的设置至关重要,也是一个难点,因为属性众多,对应的属性值也比较多,这里详细的列举了背景相关的7个属性及对应的属性值,并附上演示代码,后期要用的话,可以随时查看,那我们坐稳开车了······1: background-color 设置背景颜色2:background-image来设置背景图片- 语法:background-image:url(相对路径);-可以同时为一个元素指定背景颜色和背景图片,这样背景颜色将会作为背景图片的底色,一般情况下设置背景..._background设置背景图片

Win10 安装系统跳过创建用户,直接启用 Administrator_windows10msoobe进程-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏8次。Win10 安装系统跳过创建用户,直接启用 Administrator_windows10msoobe进程

PyCharm2021安装教程-程序员宅基地

文章浏览阅读10w+次,点赞653次,收藏3k次。Windows安装pycharm教程新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入下载安装PyCharm1、进入官网PyCharm的下载地址:http://www.jetbrains.com/pycharm/downl_pycharm2021

《跨境电商——速卖通搜索排名规则解析与SEO技术》一一1.1 初识速卖通的搜索引擎...-程序员宅基地

文章浏览阅读835次。本节书摘来自异步社区出版社《跨境电商——速卖通搜索排名规则解析与SEO技术》一书中的第1章,第1.1节,作者: 冯晓宁,更多章节内容可以访问云栖社区“异步社区”公众号查看。1.1 初识速卖通的搜索引擎1.1.1 初识速卖通搜索作为速卖通卖家都应该知道,速卖通经常被视为“国际版的淘宝”。那么请想一下,普通消费者在淘宝网上购买商品的时候,他的行为应该..._跨境电商 速卖通搜索排名规则解析与seo技术 pdf

SpringBoot:自定义线程池配置类-程序员宅基地

文章浏览阅读846次,点赞23次,收藏18次。有时候我们在项目中做一些长链路的跑批任务时,基于Springboot项目的定时任务,我们可以指定一个自定义的线程配置类进行单独提供给具体跑批任务使用,而不占用整个系统资源。_线程池配置类

浅谈LLAMA2核心函数generate源码_llama temperature-程序员宅基地

文章浏览阅读1.5k次。本文介绍了Temperature以及sample_top_p的原理,并且阅读了LLAMA2的核心生成函数的源码。关于更多细节实现,请关注llama源码。_llama temperature

推荐文章

热门文章

相关标签