驱动程序开发: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;	
......

  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;
}
操作及现象

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

  • 9
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
嗨!对于SPI打印机驱动开发,你可以按照以下步骤进行: 1. 确定硬件接口:首先,你需要了解你使用的SPI打印机的硬件接口。SPI(Serial Peripheral Interface)是一种串行通信协议,它使用主从架构,其中有一个主设备(通常是微控制器或主板)和一个或多个从设备(例如打印机)。确保你了解打印机的SPI通信协议以及所需的引脚连接。 2. 熟悉SPI驱动程序开发:了解SPI驱动程序开发的基本知识,包括SPI总线的初始化、数据传输、中断处理等。根据你使用的开发平台,可以查阅相关文档或参考示例代码来学习。 3. 编写驱动程序:根据你的需求和硬件接口规范,编写SPI打印机的驱动程序。这包括初始化SPI总线、配置打印机的寄存器、发送打印命令和数据等。确保在编写驱动程序时考虑到错误处理和异常情况。 4. 测试和调试:完成驱动程序后,进行测试和调试。连接打印机到你的开发板或主控设备上,并使用适当的测试样例来验证驱动程序的功能和稳定性。确保打印机可以正常工作并输出预期的结果。 5. 集成到应用程序:一旦你的驱动程序通过测试和调试,你可以将其集成到你的应用程序中。根据你的应用需求,编写上层应用程序代码来使用打印机驱动程序,并实现所需的打印功能。 记住,SPI打印机驱动开发可能会因为硬件平台和具体需求而有所不同。在开发过程中,不断阅读相关文档、查阅资料,并与硬件供应商或社区进行交流,以便解决遇到的问题和困难。祝你成功完成SPI打印机驱动开发!如果你有任何其他问题,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值