Linux下SPI设备驱动实验:读取ICM20608设备的数据

一. 简介

前面文章实现了向SPI设备写数据,从SPI设备中读数据的驱动代码。文章如下:

Linux下SPI设备驱动实验:实现SPI发送/接收数据的函数-CSDN博客

本文在此基础上,使用所实现的SPI读写函数, 来读取SPI设备中的数据,也就是读取 ICM20608设备中的数据。

实现效果:可以通过运行应用程序,(从而调用驱动程序),读取到ICM20608设备中的数据。

二. Linux下SPI设备驱动实验:读取ICM20608设备的数据

打开ubuntu系统,通过 vscode软件打开 18_spi工程,添加读取ICM20608设备中数据后,spi_icm20608.c文件中的代码实现如下:

复制代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include "icm20608_reg.h"

#define  ICM20608_NAME   "icm20608"
#define  ICM20608_CNT    1

//设备结构体
struct icm20608_Dev{
    dev_t devid;  //设备号
    int major;     //主设备号
    int minor;     //次设备号 
    struct cdev led_cdev; 
    struct class * class;   //类(自动创建设备节点用)
    struct device * device; //设备(自动创建设备节点用) 
    void* private_data;     //私有数据指针,用于传递spi_device结构体
    signed int accel_x_adc;		/* 加速度计X轴原始值 			*/
	signed int accel_y_adc;		/* 加速度计Y轴原始值 			*/
	signed int accel_z_adc;		/* 加速度计Z轴原始值 			*/
	signed int temp_adc;		/* 温度原始值 				*/
    signed int gyro_x_adc;		/* 陀螺仪X轴原始值 			*/
	signed int gyro_y_adc;		/* 陀螺仪Y轴原始值 			*/
	signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 			*/
};

struct icm20608_Dev icm20608_dev;

 /*向icm20608设备中多个寄存器写入数据*/
static int spi_write_regs(struct icm20608_Dev* dev, u8 reg_addr, void* buf, int len)
{
    int ret = 0;
    unsigned char* tx_data = NULL;
    struct spi_transfer spi_t = {0};
    struct spi_message spi_msg; 
    struct spi_device* spi_dev = (struct spi_device*)dev->private_data;
    tx_data = kzalloc((1+len)*sizeof(unsigned char), GFP_KERNEL);

    tx_data[0] = reg_addr & ~0x80; //第一个字节的最高位为写标志位,其他七位为SPI设备地址
    memcpy(tx_data+1, buf, len);
    spi_t.tx_buf = tx_data;
    spi_t.len = len+1;  //发送数据的长度+接收数据的长度

    spi_message_init(&spi_msg); //初始化spi_message队列
    spi_message_add_tail(&spi_t, &spi_msg); //将spi_transfer添加到spi_message队列中
    ret = spi_sync(spi_dev, &spi_msg); //同步发送
    if(ret != 0)
        printk("spi_write_regs error!\n");

    kfree(tx_data);
    return ret;
 }

/*从SPI设备多个寄存器中读取数据*/
static int spi_read_regs(struct icm20608_Dev* dev, u8 reg_addr, void* buf, int len)
{
    int ret = 0;
    unsigned char tx_data[1] = {0};
    unsigned char* rx_data = NULL;
    struct spi_transfer spi_t = {0};
    struct spi_message spi_msg;
    struct spi_device* spi_dev = (struct spi_device*)dev->private_data;

    rx_data = kzalloc((len+1)*sizeof(unsigned char), GFP_KERNEL);
    
    tx_data[0] = reg_addr|0x80; //第一个字节的最高位为读标志位,置1,其他七位为SPI设备地址
    spi_t.tx_buf = tx_data;
    spi_t.rx_buf = rx_data;
    spi_t.len = len+1;

    spi_message_init(&spi_msg); //初始化spi_message队列
    spi_message_add_tail(&spi_t, &spi_msg); //将spi_transfer添加到spi_message队列中
    ret = spi_sync(spi_dev, &spi_msg); //同步发送
    if(ret < 0)
        printk("spi_read_regs error!\n");

    //第一个字节是告诉设备我们要进行读还是写,后面的 n个字节数据才是要读取的数据
    memcpy(buf, rx_data+1, len); 

    kfree(rx_data);
    return ret;
}

/*从SPI设备读取一个寄存器的数据*/
static int spi_write_reg_onebyte(struct icm20608_Dev* dev, u8 reg_addr, int data)
{
    int ret = 0;
    ret = spi_write_regs(dev, reg_addr, &data, 1);
    if(ret < 0)
        printk("spi_write_reg_onebyte error!\n");
    return ret;
}

/*向SPI设备的一个寄存器写入数据*/
static int spi_read_reg_onebyte(struct icm20608_Dev* dev, u8 reg_addr)
{
    int ret = 0,data = 0;
    ret = spi_read_regs(dev, reg_addr, &data, 1);
    if(ret < 0)
        printk("spi_read_reg_onebyte error!\n");

    return data;
}

/*ICM20608设备初始化(即SPI设备初始化)*/
static int icm20608_register_init(struct icm20608_Dev* dev)
{
    unsigned char value = 0;

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

    value = spi_read_reg_onebyte(&icm20608_dev,ICM20_WHO_AM_I);
    printk("ICM20_WHO_AM_I: 0x%02X\r\n", value);
    if((value != ICM20608G_ID) && (value != ICM20608D_ID))
    {
        return 1;
    }
    // value = spi_read_reg_onebyte(&icm20608_dev,ICM20_PWR_MGMT_1);
    // printk("ICM20_PWR_MGMT_1: 0x%02X\r\n", value);
    spi_write_reg_onebyte(&icm20608_dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
    spi_write_reg_onebyte(&icm20608_dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */

    return 0;
}

/* 读取 ICM20608 的数据:读取原始数据
*包括三轴陀螺仪、三轴加速度计和内部温度。
*/
int read_icm20608_data(struct icm20608_Dev* dev)
{
    int ret = 0;
    unsigned char data[14] = {0};

    ret = spi_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
    if(ret)
        printk("read_icm20608_data error\n");

    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]);

    return ret;
}

/*打开设字符备函数 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608_dev;

    return 0;
}

/*读字符设备中数据的函数*/
ssize_t icm20608_read(struct file * filp, char __user * buf, size_t count, loff_t * ppos)
{
    int ret = 0;
    signed int data[7] = {0};
    struct icm20608_Dev* dev = (struct icm20608_Dev*)filp->private_data;
    
    read_icm20608_data(dev);
    data[0] = dev->accel_x_adc;
    data[1] = dev->accel_y_adc;
    data[2] = dev->accel_z_adc;
    data[3] = dev->gyro_x_adc;
    data[4] = dev->gyro_y_adc;
    data[5] = dev->gyro_z_adc;
    data[6] = dev->temp_adc;
    ret = copy_to_user(buf, data, count);
    if(ret)
        printk("copy_to_user error\n");

    return ret;
}

/*关闭设备函数*/
int icm20608_release(struct inode * inode, struct file * filp)
{
    return 0;
}

//字符设备的函数集
const struct file_operations fops = {  
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .read = icm20608_read,
    .release = icm20608_release,
};

static int icm20608_probe(struct spi_device* spi_dev)
{
    int ret = 0;
    printk("icm20608_probe!\n");
        //设备号的分配
    icm20608_dev.major = 0;
    if(icm20608_dev.major) //给定主设备号
    {
        icm20608_dev.devid = MKDEV(icm20608_dev.major, 0);
        ret = register_chrdev_region(icm20608_dev.devid, ICM20608_CNT, ICM20608_NAME);
    }
    else{ //没有给定主设备号
        ret = alloc_chrdev_region(&(icm20608_dev.devid), 0, ICM20608_CNT, ICM20608_NAME);	
        icm20608_dev.major = MAJOR(icm20608_dev.devid);
        icm20608_dev.minor = MINOR(icm20608_dev.devid);
    }
    printk("dev.major: %d\n", icm20608_dev.major);
    printk("dev.minor: %d\n", icm20608_dev.minor);
	if (ret < 0) {
		printk("register-chrdev failed!\n");
		goto fail_devid;
	}
    //初始化设备
    icm20608_dev.led_cdev.owner = THIS_MODULE;
    cdev_init(&icm20608_dev.led_cdev, &fops);
    
    //注册设备
    ret = cdev_add(&icm20608_dev.led_cdev, icm20608_dev.devid, ICM20608_CNT);
    if(ret < 0)
    {
        printk("cdev_add failed!\r\n");
        goto fail_adddev;
    }

/* 3.自动创建设备节点 */
    //创建类
    icm20608_dev.class = class_create(THIS_MODULE, ICM20608_NAME); 
    if (IS_ERR(icm20608_dev.class)) {
		ret = PTR_ERR(icm20608_dev.class);
		goto fail_class;
	}   
    //创建设备
    icm20608_dev.device = device_create(icm20608_dev.class, NULL, icm20608_dev.devid, NULL, ICM20608_NAME);
    if (IS_ERR(icm20608_dev.device)) {
		ret = PTR_ERR(icm20608_dev.device);
		goto fail_dev_create;
	}  

    /*SPI通信:设置模式*/
    spi_dev->mode = SPI_MODE_0;
    spi_setup(spi_dev);  //调用spi_setup函数设置模式,字节数,传输速率等
    icm20608_dev.private_data = spi_dev; //设置私有数据
    //ICM20608设备初始化
    icm20608_register_init(&icm20608_dev);

    return 0;

fail_dev_create:
    class_destroy(icm20608_dev.class);
fail_class:
    cdev_del(&icm20608_dev.led_cdev);
fail_adddev:
    unregister_chrdev_region(icm20608_dev.devid, ICM20608_CNT);
fail_devid:
    return ret;
}

static int icm20608_remove(struct spi_device* spi_dev)
{
    printk("icm20608_remove!\n");
    /*注销字符设备*/
    //1. 删除设备
    cdev_del(&icm20608_dev.led_cdev);
    //2. 注销设备号
    unregister_chrdev_region(icm20608_dev.devid, ICM20608_CNT);

    /*摧毁类与设备(自动创建设备节点时用) */
    //3. 摧毁设备
    device_destroy(icm20608_dev.class, icm20608_dev.devid);
    //4. 摧毁类
    class_destroy(icm20608_dev.class);
    return 0;
}

//传统驱动与设备匹配方法
static struct spi_device_id spi_device_id_table[] = {
    { "icm20608", 0},
    { }
};

//设备树匹配方法
static struct of_device_id of_device_table[] = {
    { .compatible = "alientek,icm20608" },  //必须与设备树中设备节点compatible值一致
    { }
};

/*SPI驱动结构体*/
struct spi_driver icm20608_driver = {
    .driver = {
        .name = "icm20608",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(of_device_table),
    },
    .id_table = spi_device_id_table,
    .probe = icm20608_probe,
    .remove = icm20608_remove,
};

/*模块加载 */
static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}

/*模块卸载 */
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

/*驱动加载与卸载 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL"); //模块 Licence
MODULE_AUTHOR("WeiWuXian"); //作者

编译驱动代码

编译驱动代码,ubuntu终端进入 18_spi工程根目录下,输入 "make"命令进行模块化编译:

复制代码
make

这里经过测试是可以编译成功。会生成驱动文件:spi_icm20608.ko。

三. 编写应用程序

编写测试程序,打开 icm20608_app.c文件,实现后代码如下:

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

struct icm20608_str {
    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;
};

struct icm20608_str icm20608;

/*
* 打开/关闭 Led灯
* 参数:
* ./icm20608_app /dev/icm20608 
*/
int main(int argc, char* argv[])
{
    int fd = 0,ret = 0;
    char * device_name = NULL;
    signed int data[7] = {0};


    if(argc != 2)
    {
        printf("main's param number error!\n");
        return -1;
    }

    device_name = argv[1];
    fd = open(device_name, O_RDWR);
    if(fd < 0)
    {
        printf("open led device failed!\n");
        return -1;
    }
    
    while(1)
    {
        ret = read(fd, data, sizeof(data));
        if(!ret)
        {
            icm20608.accel_x_adc = data[0];
            icm20608.accel_y_adc = data[1];
            icm20608.accel_z_adc = data[2];
            icm20608.gyro_x_adc = data[3];
            icm20608.gyro_y_adc = data[4];
            icm20608.gyro_z_adc = data[5];
            icm20608.temp_adc = data[6];

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

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

编译测试程序

对应用程序 icm20608.c文件进行交叉编译:

复制代码
angtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/18_spi$ arm-linux-gnueabihf-gcc icm20608_app.c -o icm20608_app

这里可以编译通过,生成可执行应用程序 icm20608_app文件。

接下来将驱动模块与应用程序拷贝到开发板系统下,进行测试。确定读取ICM20608设备数据是否正常。

相关推荐
hello_ world.14 分钟前
RHCA10NUMA
linux
神秘人X70743 分钟前
Linux高效备份:rsync + inotify实时同步
linux·服务器·rsync
轻松Ai享生活1 小时前
一步步学习Linux initrd/initramfs
linux
轻松Ai享生活1 小时前
一步步深入学习Linux Process Scheduling
linux
绵绵细雨中的乡音3 小时前
网络基础知识
linux·网络
Peter·Pan爱编程3 小时前
Docker在Linux中安装与使用教程
linux·docker·eureka
kunge20134 小时前
Ubuntu22.04 安装virtualbox7.1
linux·virtualbox
清溪5494 小时前
DVWA中级
linux
Sadsvit5 小时前
源码编译安装LAMP架构并部署WordPress(CentOS 7)
linux·运维·服务器·架构·centos
xiaok5 小时前
为什么 lsof 显示多个 nginx 都在 “使用 443”?
linux