46.Linux SPI 驱动

46.Linux SPI 驱动

整个框架与I2C十分类似,

不同的是数据通信那部分

实战

修改设备树

c 复制代码
&ecspi3 {
        fsl,spi-num-chipselects = <1>;    			//一个片选
        cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;		//如果是cs-gpios则时硬件片选,这里修改为cs-gpio,使用自己的软件控制,低电平有效
        pinctrl-names = "default";					//
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";

		//对应的spi子节点
		spidev: icm20608@0 {				//接到了spi3 ss0 第0号片选上,我们没有使用到,无效
			compatible = "alientek,icm20608";
			spi-max-frequency = <8000000>;	//spi速率(时钟频率) 8Mhz
			reg = <0>;
		};
};


...
pinctrl_ecspi3:icm20608 {
			fsl,pin = <
				MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x10b1  /* MISO*/
				MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1  /* MOSI*/
				MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK      0x10b1  /* CLK*/
				MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20       0x10b0  /* CS,作为普通的GPIO,使用软件片选*/  
			>;
		};

注意屏蔽其他引脚,复用、gpio

编译 复制 上电

c 复制代码
ls /sys/bus/spi/devices/
spi2.0

spi2.0是在linux下的命名,对应节点 spi3 0号片选

编写驱动

框架:

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define icm20608_CNT      1
#define icm20608_NAME     "icm20608"


struct icm20608_dev
{
    dev_t devid;
    int MAJOR;
    int MINOR;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    void *private_data;
};
struct icm20608_dev icm20608;



static int icm20608_open (struct inode *inode, struct file *file){
    file->private_data = &icm20608;
    printk("icm20608_open\r\n");    
    return 0; 
}  

static int icm20608_close(struct inode *inode, struct file *file){
    //struct icm20608_dev *dev = file->private_data;
    printk("icm20608_close\r\n");
    return 0;
}

static ssize_t icm20608_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    //struct icm20608_dev *dev = file->private_data;
    printk("icm20608_read\r\n");

    //读取icm20608的数据

    
    return 0;

}
const struct file_operations icm20608_opts={
    .owner = THIS_MODULE,
    .open  = icm20608_open,
    .release = icm20608_close,
    .read = icm20608_read,
};

static int icm20608_probe(struct spi_device *spi){

    printk("icm20608_probe\r\n");
    //搭建字符设备驱动框架

    int ret = 0;
    //注册设备号
    icm20608.MAJOR = 0;
    if ( icm20608.MAJOR )
    {
        icm20608.devid = MKDEV(icm20608.MAJOR,0);
        ret  = register_chrdev_region(icm20608.devid ,icm20608_CNT,icm20608_NAME);
    }else{
        ret = alloc_chrdev_region(&icm20608.devid,0,icm20608_CNT,icm20608_NAME);
         icm20608.MAJOR  = MAJOR(icm20608.devid);
         icm20608.MINOR  = MINOR(icm20608.devid);
    }
    if (ret<0)
    {
        printk("icm20608 chrdev_region error\r\n");
        goto fail_devid;
        
    }

    //注册设备
    icm20608.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608.cdev,&icm20608_opts);


    ret  = cdev_add(&icm20608.cdev, icm20608.devid, icm20608_CNT);
    if (ret < 0 )
    {
        goto fail_cdev;
    }
    

    //自动创建设备节点
    icm20608.class = class_create(THIS_MODULE,icm20608_NAME);
    if (IS_ERR(icm20608.class))
    {
        ret = PTR_ERR(icm20608.class);
        goto fail_class;
    }
    
    icm20608.device = device_create(icm20608.class,NULL,icm20608.devid,NULL,icm20608_NAME);
    if (IS_ERR(icm20608.device ))
    {
        ret  = PTR_ERR(icm20608.device); 
        goto fail_device;
    }

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

static int icm20608_remove(struct spi_device *spi){

    cdev_del(&icm20608.cdev);

    unregister_chrdev_region(icm20608.devid,icm20608_CNT);
    device_destroy(icm20608.class,icm20608.devid);
    class_destroy(icm20608.class);

    
    return 0;

}
//设备树匹配
const struct of_device_id icm20608_of_match[]= {
    { .compatible = "alientek,icm20608"},
    {},
};

//传统匹配表
const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608",0},
    {},
};
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){
    int ret = 0;
    ret  =  spi_register_driver(&icm20608_driver);
    return ret;

}
static void __exit icm20608_exit(void){
    spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

对于spi的协议传输

需要用到两个结构体:spi_message 和 spi_transfer

使用方法:

c 复制代码
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
    int ret;
    struct spi_message m;
    struct spi_transfer t = {
        .tx_buf = buf,
        .len = len,
    };
    spi_message_init(&m); /* 初始化 spi_message */
    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
    ret = spi_sync(spi, &m); /* 同步传输 */
    return ret;
}
/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
    int ret;
    struct spi_message m;
    struct spi_transfer t = {
        .rx_buf = buf,
        .len = len,
    };
    spi_message_init(&m); /* 初始化 spi_message */
    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
    ret = spi_sync(spi, &m); /* 同步传输 */
    return ret;
}

构建spi_transfer,然后将其打包到spi_message里面,需要使用spi_message_init初始

化spi_message,然后在使用spi_message_add_tail将spi_transfer添加到spi_message里面,

最终使用spi_sync 和spi_async 来发送。

获取父节点

值得注意的时,spi_driver自带了device_driver结构体 其指向的of_node,表示设备节点,即带有compatible = "alientek,icm20608";属性的节点,我们需要控制spi的pinctl就需要获取其父节点ecspi3,从而控制pinctrl_ecspi3中的MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20

c 复制代码
&ecspi3 {
        fsl,spi-num-chipselects = <1>;    			//一个片选
        cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;		//如果是cs-gpios则时硬件片选,这里修改为cs-gpio,使用自己的软件控制,低电平有效
        pinctrl-names = "default";					//
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";

		//对应的spi子节点
		spidev: icm20608@0 {				//接到了spi3 ss0 第0号片选上,我们没有使用到,无效
			compatible = "alientek,icm20608";
			spi-max-frequency = <8000000>;	//spi速率(时钟频率) 8Mhz
			reg = <0>;
		};
};
c 复制代码
pinctrl_ecspi3:icm20608 {
			fsl,pin = <
				MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x10b1  /* MISO*/
				MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1  /* MOSI*/
				MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK      0x10b1  /* CLK*/
				MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20       0x10b0  /* CS,作为普通的GPIO,使用软件片选*/  
			>;
		};

控制该节点的代码:

c 复制代码
   //获取父节点
    icm20608.nd= of_get_parent(spi->dev.of_node);
    icm20608.cs_gpio = of_get_named_gpio(icm20608.nd,"cs-gpio",3);

完善驱动1

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm_20608_reg.h"


#define icm20608_CNT      1
#define icm20608_NAME     "icm20608"


struct icm20608_dev
{
    dev_t devid;
    int MAJOR;
    int MINOR;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    void *private_data;
    int cs_gpio;
    struct device_node *nd;
};
struct icm20608_dev icm20608;
/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
//spi读寄存器
static int icm20608_read_regs(struct icm20608_dev *dev , u8 reg , void *buf , int  len){
    int ret=  0 ;
     int value = 0;
    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = dev->private_data;

    //拉低片选
    gpio_set_value(dev->cs_gpio,0);
   t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);  //向内核申请内存
    
    //第一步、发送要读取的地址
    txdata[0] = reg | 0x80 ; //读取时最高位为1 即1000 0000
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);

    //第二部,读取数据
    txdata[0] = 0xff;  //作为数据交换的无效值
    t->rx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);


    kfree(t); //释放内核中申请的内存

    //拉高片选
    gpio_set_value(dev->cs_gpio,1);
    
    return ret;
}

//spi写寄存器
static int icm20608_write_regs(struct icm20608_dev *dev , u8 reg, u8 *buf , int len){
    int ret=  0;
    int value = 0;
    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = dev->private_data;

    //拉低片选
    gpio_set_value(dev->cs_gpio,0);
    t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);  //向内核申请内存

    
    

    //第一步、发送要写的地址
    
    txdata[0] = reg &~ 0x80 ; //读取时最高位为0 即0XXX XXXX
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);

    //第二部,读取数据
    t->tx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);


    kfree(t); //释放内核中申请的内存

    //拉高片选
    gpio_set_value(dev->cs_gpio,1);
    return ret;
    
}


//读取单个寄存器
static unsigned char icm20608_read_Onereg(struct icm20608_dev *dev , u8 reg){
    u8 data = 0;
    icm20608_read_regs(dev,reg,&data,1);
    return data;
}

//写入单个寄存器
static void icm20608_write_Onereg(struct icm20608_dev *dev , u8 reg , u8 value){
    u8 buf = value;
    icm20608_write_regs(dev,reg,&buf,1);
}

//初始化icm20608
void driver_icm20608_init(struct icm20608_dev *dev){
    u8 value = 0;

    //从睡眠模式唤醒
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x80); //复位,睡眠模式
    mdelay(50);
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x01); //关闭睡眠模式
    mdelay(50);

    value = icm20608_read_Onereg(dev,ICM20_WHO_AM_I);
    printk("icm20608 reg = %x\r\n",value);
}


static int icm20608_open (struct inode *inode, struct file *file){
    file->private_data = &icm20608;
    printk("icm20608_open\r\n");    
    return 0; 
}  

static int icm20608_close(struct inode *inode, struct file *file){
    //struct icm20608_dev *dev = file->private_data;
    printk("icm20608_close\r\n");
    return 0;
}

static ssize_t icm20608_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    //struct icm20608_dev *dev = file->private_data;
    printk("icm20608_read\r\n");

    //读取icm20608的数据

    
    return 0;

}
const struct file_operations icm20608_opts={
    .owner = THIS_MODULE,
    .open  = icm20608_open,
    .release = icm20608_close,
    .read = icm20608_read,
};

static int icm20608_probe(struct spi_device *spi){

    int ret = 0;
    printk("icm20608_probe\r\n");
    //搭建字符设备驱动框架

    
    //注册设备号
    icm20608.MAJOR = 0;
    if ( icm20608.MAJOR )
    {
        icm20608.devid = MKDEV(icm20608.MAJOR,0);
        ret  = register_chrdev_region(icm20608.devid ,icm20608_CNT,icm20608_NAME);
    }else{
        ret = alloc_chrdev_region(&icm20608.devid,0,icm20608_CNT,icm20608_NAME);
         icm20608.MAJOR  = MAJOR(icm20608.devid);
         icm20608.MINOR  = MINOR(icm20608.devid);
    }
    if (ret<0)
    {
        printk("icm20608 chrdev_region error\r\n");
        goto fail_devid;
        
    }

    //注册设备
    icm20608.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608.cdev,&icm20608_opts);


    ret  = cdev_add(&icm20608.cdev, icm20608.devid, icm20608_CNT);
    if (ret < 0 )
    {
        goto fail_cdev;
    }
    

    //自动创建设备节点
    icm20608.class = class_create(THIS_MODULE,icm20608_NAME);
    if (IS_ERR(icm20608.class))
    {
        ret = PTR_ERR(icm20608.class);
        goto fail_class;
    }
    
    icm20608.device = device_create(icm20608.class,NULL,icm20608.devid,NULL,icm20608_NAME);
    if (IS_ERR(icm20608.device ))
    {
        ret  = PTR_ERR(icm20608.device); 
        goto fail_device;
    }

    
    //获取父节点
    icm20608.nd= of_get_parent(spi->dev.of_node);
    icm20608.cs_gpio = of_get_named_gpio(icm20608.nd,"cs-gpio",0);

    if (icm20608.cs_gpio<0)
    {
        printk("can't find gpio \r\n");
        goto fail_gpio;
    }


    ret  = gpio_request(icm20608.cs_gpio,"CS");
    if (ret<0)
    {
        printk("failed request  gpio !\r\n");
        goto fail_gpio;
    }
    printk("request  gpio %d!\r\n",icm20608.cs_gpio);

    ret  = gpio_direction_output(icm20608.cs_gpio,1);  //默认高电平
    
    
    //初始化spi,设置spi的模式
    spi->mode = SPI_MODE_0;         /*MODE0, CPOL=0, CPHA=0*/
    spi_setup(spi);   

    //设置私有数据
    icm20608.private_data = spi;

    //初始化icm20608芯片
    driver_icm20608_init(&icm20608);

    return 0;


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

static int icm20608_remove(struct spi_device *spi){

    cdev_del(&icm20608.cdev);
    gpio_free(icm20608.cs_gpio);
    unregister_chrdev_region(icm20608.devid,icm20608_CNT);
    device_destroy(icm20608.class,icm20608.devid);
    class_destroy(icm20608.class);

    return 0;

}
//设备树匹配
const struct of_device_id icm20608_of_match[]= {
    { .compatible = "alientek,icm20608"},
    {},
};

//传统匹配表
const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608",0},
    {},
};
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){
    int ret = 0;
    ret  =  spi_register_driver(&icm20608_driver);
    return ret;

}
static void __exit icm20608_exit(void){
    spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

这里有一个坑

c 复制代码
/lib/modules/4.1.15 # modprobe icm20608.ko
icm20608_probe
request  gpio 20!
icm20608 reg = 0x0

读取出来是0 ,有可能不是代码的问题

回到设备树试试将所有uart2的pinctl给屏蔽了

或者&uart2的状态改为disabled

再不行试试将例程源码复制,将设备树中的cs-gpio 改为 cs-gpios(由spi硬件直接控制,不需要我们手动控制),再试试。

两个函数

spi_write
c 复制代码
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}
spi_read
c 复制代码
static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}

可以使用内核操作spi的这两个函数来实现更简介的代码

c 复制代码
static int icm20608_read_regs(struct icm20608_dev *dev , u8 reg , void *buf , int  len){
    struct spi_device *spi = dev->private_data;
    u8 data =0;
     //拉低片选
    gpio_set_value(dev->cs_gpio,0);
    data = reg | 0x80;
    spi_write(spi,&data,1);//发送要读取的寄存器地址

    spi_read(spi,buf,len);
    //拉高片选
    gpio_set_value(dev->cs_gpio,1);
}


static int icm20608_write_regs(struct icm20608_dev *dev , u8 reg, u8 *buf , int len){
    struct spi_device *spi = dev->private_data;
    u8 data = 0;
     //拉低片选 
    gpio_set_value(dev->cs_gpio,0);
    data = reg &~ 0x80;
    spi_write(spi,&data,1);//发送要读取的寄存器地址
    spi_write(spi,buf,len);//发送要读取的寄存器地址

    //拉高片选
    gpio_set_value(dev->cs_gpio,1);

}

NXP将SPI的cs片选信号作为软件片选,只需要指定gpio为cs-gpios即可。

c 复制代码
&ecspi3 {
        fsl,spi-num-chipselects = <1>;    			//一个片选
        cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;		//如果是cs-gpios则时硬件片选,这里修改为cs-gpio,使用自己的软件控制,低电平有效
        pinctrl-names = "default";					//
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";

		//对应的spi子节点
		spidev: icm20608@0 {				//接到了spi3 ss0 第0号片选上,我们没有使用到,无效
			compatible = "alientek,icm20608";
			spi-max-frequency = <8000000>;	//spi速率(时钟频率) 8Mhz
			reg = <0>;
		};
};


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

但注意

c 复制代码
u8 data =0;
    
data = reg | 0x80;
//前面不需要手动设置gpio的cs拉低
spi_write(spi,&data,1);//发送要读取的寄存器地址
spi_read(spi,buf,len);
//后面不需要手动设置gpio的cs拉高

spi_write为一次操作,其软件片选会在结束时拉高,这不符合spi的时序

使用spi_read_then_write函数即可解决。

c 复制代码
    u8 data =0;
    
    data = reg | 0x80;

    //使用一个函数完成
    spi_write_then_read(spi,&data,1,buf,len);
   
c 复制代码
static int icm20608_read_regs(struct icm20608_dev *dev , u8 reg , void *buf , int  len){
    struct spi_device *spi = dev->private_data;
    u8 data =0;
    
    data = reg | 0x80;


    // spi_write(spi,&data,1);//发送要读取的寄存器地址
    // spi_read(spi,buf,len);
    //使用一个函数完成
    spi_write_then_read(spi,&data,1,buf,len);
   
    return 0;
}
c 复制代码
static int icm20608_write_regs(struct icm20608_dev *dev , u8 reg, u8 *buf , int len){
    struct spi_device *spi = dev->private_data;
    
    u8 *txdata;
    
    txdata = kzalloc(len+1,GFP_KERNEL);

    txdata[0] = reg &~ 0x80;
    memcpy(&txdata[1],buf,len);
    spi_write(spi,txdata,len+1);
    
    kfree(txdata);
    
    return 0;
}
c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm_20608_reg.h"


#define icm20608_CNT      1
#define icm20608_NAME     "icm20608"


struct icm20608_dev
{
    dev_t devid;
    int MAJOR;
    int MINOR;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    void *private_data;
    int cs_gpio;
    struct device_node *nd;

    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 icm20608;
/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
//spi读寄存器
/* static int icm20608_read_regs(struct icm20608_dev *dev , u8 reg , void *buf , int  len){
    int ret=  0 ;
     int value = 0;
    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = dev->private_data;

    
    t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);  //向内核申请内存
    
    //第一步、发送要读取的地址
    txdata[0] = reg | 0x80 ; //读取时最高位为1 即1000 0000
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);

    //第二部,读取数据
    txdata[0] = 0xff;  //作为数据交换的无效值
    t->rx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);


    kfree(t); //释放内核中申请的内存


    
    return ret;
} */

static int icm20608_read_regs(struct icm20608_dev *dev , u8 reg , void *buf , int  len){
    struct spi_device *spi = dev->private_data;
    u8 data =0;
    
    data = reg | 0x80;


    // spi_write(spi,&data,1);//发送要读取的寄存器地址
    // spi_read(spi,buf,len);
    //使用一个函数完成
    spi_write_then_read(spi,&data,1,buf,len);
   
    return 0;
}

//spi写寄存器
/* static int icm20608_write_regs(struct icm20608_dev *dev , u8 reg, u8 *buf , int len){
    int ret=  0;
    int value = 0;
    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = dev->private_data;

    //拉低片选
    gpio_set_value(dev->cs_gpio,0);
    t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);  //向内核申请内存

    
    spi_write()

    //第一步、发送要写的地址
    
    txdata[0] = reg &~ 0x80 ; //读取时最高位为0 即0XXX XXXX
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);

    //第二部,读取数据
    t->tx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t,&m);
    ret = spi_sync(spi,&m);


    kfree(t); //释放内核中申请的内存

    //拉高片选
    gpio_set_value(dev->cs_gpio,1);
    value = gpio_get_value(dev->cs_gpio);
    return ret;
    
} */


static int icm20608_write_regs(struct icm20608_dev *dev , u8 reg, u8 *buf , int len){
    struct spi_device *spi = dev->private_data;
    
    u8 *txdata;
    
    txdata = kzalloc(len+1,GFP_KERNEL);

    txdata[0] = reg &~ 0x80;
    memcpy(&txdata[1],buf,len);
    spi_write(spi,txdata,len+1);
    
    kfree(txdata);
    
    return 0;
}

//读取单个寄存器
static unsigned char icm20608_read_Onereg(struct icm20608_dev *dev , u8 reg){
    u8 data = 0;
    icm20608_read_regs(dev,reg,&data,1);
    return data;
}

//写入单个寄存器
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]);
}



//初始化icm20608
void driver_icm20608_init(struct icm20608_dev *dev){
    u8 value = 0;

    //从睡眠模式唤醒
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x80); //复位,睡眠模式
    mdelay(50);
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x01); //关闭睡眠模式
    mdelay(50);

    value = icm20608_read_Onereg(dev,ICM20_WHO_AM_I);
    printk("ICM20_WHO_AM_I reg = %#x\r\n",value);

    value = icm20608_read_Onereg(dev,ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1 reg = %#x\r\n",value);


    icm20608_write_Onereg(dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/
	icm20608_write_Onereg(dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/
	icm20608_write_Onereg(dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/
	icm20608_write_Onereg(dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/
	icm20608_write_Onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
	icm20608_write_Onereg(dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/
	icm20608_write_Onereg(dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/
	icm20608_write_Onereg(dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/
}


static int icm20608_open (struct inode *inode, struct file *file){
    file->private_data = &icm20608;
    return 0; 
}  

static int icm20608_close(struct inode *inode, struct file *file){
    return 0;
}

static ssize_t icm20608_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    signed int data[7];
	long err = 0;
    struct icm20608_dev *dev = file->private_data;

    //读取icm20608的数据
    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;

    return 0;

}
const struct file_operations icm20608_opts={
    .owner = THIS_MODULE,
    .open  = icm20608_open,
    .release = icm20608_close,
    .read = icm20608_read,
};

static int icm20608_probe(struct spi_device *spi){

    int ret = 0;
    printk("icm20608_probe\r\n");
    //搭建字符设备驱动框架

    
    //注册设备号
    icm20608.MAJOR = 0;
    if ( icm20608.MAJOR )
    {
        icm20608.devid = MKDEV(icm20608.MAJOR,0);
        ret  = register_chrdev_region(icm20608.devid ,icm20608_CNT,icm20608_NAME);
    }else{
        ret = alloc_chrdev_region(&icm20608.devid,0,icm20608_CNT,icm20608_NAME);
         icm20608.MAJOR  = MAJOR(icm20608.devid);
         icm20608.MINOR  = MINOR(icm20608.devid);
    }
    if (ret<0)
    {
        printk("icm20608 chrdev_region error\r\n");
        goto fail_devid;
        
    }

    //注册设备
    icm20608.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608.cdev,&icm20608_opts);


    ret  = cdev_add(&icm20608.cdev, icm20608.devid, icm20608_CNT);
    if (ret < 0 )
    {
        goto fail_cdev;
    }
    

    //自动创建设备节点
    icm20608.class = class_create(THIS_MODULE,icm20608_NAME);
    if (IS_ERR(icm20608.class))
    {
        ret = PTR_ERR(icm20608.class);
        goto fail_class;
    }
    
    icm20608.device = device_create(icm20608.class,NULL,icm20608.devid,NULL,icm20608_NAME);
    if (IS_ERR(icm20608.device ))
    {
        ret  = PTR_ERR(icm20608.device); 
        goto fail_device;
    }

    
    //初始化spi,设置spi的模式
    spi->mode = SPI_MODE_0;         /*MODE0, CPOL=0, CPHA=0*/
    spi_setup(spi);   

    //设置私有数据
    icm20608.private_data = spi;

    //初始化icm20608芯片
    driver_icm20608_init(&icm20608);

    return 0;


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

static int icm20608_remove(struct spi_device *spi){

    cdev_del(&icm20608.cdev);
    gpio_free(icm20608.cs_gpio);
    unregister_chrdev_region(icm20608.devid,icm20608_CNT);
    device_destroy(icm20608.class,icm20608.devid);
    class_destroy(icm20608.class);

    return 0;

}
//设备树匹配
const struct of_device_id icm20608_of_match[]= {
    { .compatible = "alientek,icm20608"},
    {},
};

//传统匹配表
const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608",0},
    {},
};
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){
    int ret = 0;
    ret  =  spi_register_driver(&icm20608_driver);
    return ret;

}
static void __exit icm20608_exit(void){
    spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
c 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: icm20608App.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: icm20608设备测试APP。
其他	   	: 无
使用方法	 :./icm20608App /dev/icm20608
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/9/20 左忠凯创建
***************************************************************/

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
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("can't open file %s\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;
}
相关推荐
傲世(C/C++,Linux)1 小时前
Linux系统编程——UDP广播
linux·单片机·udp
深圳市恒讯科技1 小时前
如果超出防护峰值,会发生什么——服务器会挂掉吗?
运维·服务器
努力也学不会java1 小时前
【docker】Docker Register(镜像仓库)
运维·人工智能·机器学习·docker·容器
9527华安1 小时前
紫光同创FPGA实现 TCP/IP 协议栈,千兆网服务器版本,提供5套工程源码和技术支持
服务器·tcp/ip·fpga开发
qq_479875431 小时前
Linux 网络实验(2)
linux·网络·php
liberty8881 小时前
dppt如何找到弹框
java·服务器·前端
yugi9878381 小时前
使用SCP命令在CentOS 7上向目标服务器传输文件
linux·服务器·centos
博语小屋1 小时前
Linux线程
linux·c++·spring
小鹿学程序1 小时前
04-模块一平台搭建与运维
运维