一. 简介
前面文章实现了向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设备数据是否正常。