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设备数据是否正常。

相关推荐
JunLan~1 小时前
Rocky Linux 系统安装/部署 Docker
linux·docker·容器
方竞2 小时前
Linux空口抓包方法
linux·空口抓包
海岛日记3 小时前
centos一键卸载docker脚本
linux·docker·centos
AttackingLin4 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
学Linux的语莫5 小时前
Ansible使用简介和基础使用
linux·运维·服务器·nginx·云计算·ansible
踏雪Vernon5 小时前
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
linux·docker·容器·harmonyos
学Linux的语莫5 小时前
搭建服务器VPN,Linux客户端连接WireGuard,Windows客户端连接WireGuard
linux·运维·服务器
legend_jz5 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
Komorebi.py5 小时前
【Linux】-学习笔记04
linux·笔记·学习