Linux SPI 驱动实验
目录
一、Linux 下 SPI 驱动框架简介
1、SPI 主机驱动
2、SPI 设备驱动
SPI 设备数据收发处理流程
3、SPI 设备和驱动匹配过程
二、添加SPI 设备信息
1、添加 ICM20608 所使用的 IO
2、 在 ecspi3 节点追加 icm20608 子节点
三、编写 ICM20608 驱动
1、修改makefile编辑
2、icm20608reg.h
3、icm20608 设备结构体创建
4、probe 函数
5、icm20608 寄存器读写与初始化
①icm20608_read_regs函数
②icm20608_write_regs函数
③icm20608_read_onereg函数
④ icm20608_write_onereg函数
⑤ icm20608_readdata函数
⑥icm20608_reginit 函数
6、icm20608_read函数
代码如下
四、APP编写
编译验证
一、Linux 下 SPI 驱动框架简介
1、SPI 主机驱动
SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核
使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,部分如下
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);
}
transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去
SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。和 I2C 适配器驱动一样, SPI 主机驱动一般都是 SOC 厂商去编写
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master
2、SPI 设备驱动
spi 设备驱动也和 i2c 设备驱动也很类似, Linux 内核使用 spi_driver 结构体来表示 spi 设备
驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。 spi_driver 结构体定义在
include/linux/spi/spi.h 文件中,结构体内容如下:
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 设备数据收发处理流程
SPI 设备驱动的核心是 spi_driver,当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容部分如下:
struct spi_transfer {1 const void *tx_buf;
2 void *rx_buf;
3 unsigned len;
........
4 struct list_head transfer_list;
};
第1行, tx_buf 保存着要发送的数据。
第2行, rx_buf 用于保存接收到的数据。
第3行, len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。
spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,在使用spi_message之前需要对其进行初始化,spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,异步传输不会阻塞的等到 SPI 数据传输完成,下面采用同步传输方式来完成 SPI 数据的传输工作
3、SPI 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、 I2C 等驱动一样, SPI
总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
static int spi_match_device(struct device *dev,struct device_driver *drv)
{
1 const struct spi_device *spi = to_spi_device(dev);
2 const struct spi_driver *sdrv = to_spi_driver(drv);
3
4 /* Attempt an OF style match */
5 if (of_driver_match_device(dev, drv))
6 return 1;
7
8 /* Then try ACPI */
9 if (acpi_driver_match_device(dev, drv))
10 return 1;
11
12 if (sdrv->id_table)
13 return !!spi_match_id(sdrv->id_table, spi);
14
15 return strcmp(spi->modalias, drv->name) == 0;
}
第5 行, of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配
第9 行, acpi_driver_match_device 函数用于 ACPI 形式的匹配
第13 行, spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配
第15 行,比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。
二、添加SPI 设备信息
1、添加 ICM20608 所使用的 IO
在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点
中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚
pinctrl_ecspi3: icm20608 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
>;
};
注意的就是检查相应的 IO 有没有被其他的设备所使用,分别搜UART2_TX、UART2_RX、UART2_RTS和UART2_CTS,有复用的全部屏蔽,如下
2、 在 ecspi3 节点追加 icm20608 子节点
打开 imx6qdl-sabresd.dtsi 这个设备树头文件
&ecspi3 {
1 fsl,spi-num-chipselects = <1>;
2 cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
3 pinctrl-names = "default";
4 pinctrl-0 = <&pinctrl_ecspi3>;
5 status = "okay";6 spidev: icm20608@0 {
7 compatible = "my,icm20608";
8 spi-max-frequency = <8000000>;
9 reg = <0>;};
};
第1行,设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备
第2行,设置“cs-gpios”属性,也就是片选信号,SPI 主机驱动就会控制片选引脚
第3行,设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字
第4行,设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点
第5行,“status”属性为“okay”
第6行,每一个 SPI 设备都采用一个子节点来描述其设备信息,icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0
第7行,SPI 设备的 compatible 属性值,用于匹配设备驱动
第8行,“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据使用的SPI 设备来设置
第9行,icm20608 连接在通道 0 上,因此 reg 为 0。
三、编写 ICM20608 驱动
新建 icm20608.c 和 icm20608reg.h 这两个文件, icm20608.c 为 ICM20608的驱动代码, icm20608reg.h 是 ICM20608 寄存器头文件
1、修改makefile
2、icm20608reg.h
#ifndef __IMC20608REG_H
#define __IMC20608REG_H/* ID值 */
#define ICM20608G_ID (0XAF)
#define ICM20608D_ID (0XAE)/* 定义寄存器 */
/* 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
3、icm20608 设备结构体创建
#include <linux/module.h>
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>#define ICM20608_CNT 1
#define ICM20608_NAME "icm20680"/*设备结构体*/
struct icm20608_dev{dev_t devid;/* 设备号 */int major;/* 主设备号 */int minor;/* 次设备号 */struct cdev cdev;/* cdev */struct class *class;/* 类 */struct device *device;/* 设备 */void *private_data; /*私有数据 */
};
struct icm20608_dev icm20608dev;static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev;return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}/* icm20608操作函数 */
struct file_operations icm20608_fops = {.owner = THIS_MODULE,.open = icm20608_open,.release = icm20608_release,.read = icm20608_read,
};static int icm20608_probe(struct spi_device *spi)
{int ret = 0;/* 1、构建设备号 */icm20608dev.major=0;if(icm20608dev.major){icm20608dev.devid = MKDEV(icm20608dev.major,0);ret = register_chrdev_region(icm20608dev.devid,ICM20608_CNT,ICM20608_NAME);}else{ret = alloc_chrdev_region(&icm20608dev.devid,0,ICM20608_CNT,ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);icm20608dev.minor = MINOR(icm20608dev.devid);}if(ret < 0){printk("icm20608 chrdev_region err!\r\n");goto fail_devid;}printk("icm20608dev major = %d, minor = %d\r\n",icm20608dev.major,icm20608dev.minor);/* 2、注册设备 */icm20608dev.cdev.owner = THIS_MODULE;cdev_init(&icm20608dev.cdev,&icm20608_fops);ret = cdev_add(&icm20608dev.cdev,icm20608dev.devid,ICM20608_CNT);if(ret <0){printk("icm20608 cdev_add err!\r\n");goto fail_cdev;}/* 3、创建类 */icm20608dev.class = class_create(THIS_MODULE,ICM20608_NAME);if(IS_ERR(icm20608dev.class)){ret = PTR_ERR(icm20608dev.class);printk("icm20608 chrdev_class err!\r\n");goto fail_class;}/* 4、创建设备 */icm20608dev.device = device_create(icm20608dev.class,NULL,icm20608dev.devid,NULL,ICM20608_NAME);if(IS_ERR(icm20608dev.device)){ret = PTR_ERR(icm20608dev.device);printk("icm20608 chrdev_device err!\r\n");goto fail_device;}/*设置icm20608私有数据*/icm20608dev.private_data = spi;printk("icm20608_probe\r\n");return 0;
fail_device:class_destroy(icm20608dev.class);
fail_class: cdev_del(&icm20608dev.cdev);
fail_cdev:unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);
fail_devid:return ret;
}static int icm20608_remove(struct spi_device *spi)
{/* 删除字符设备 */cdev_del(&icm20608dev.cdev);/*注销设备号*/unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);/*摧毁设备*/device_destroy(icm20608dev.class,icm20608dev.devid);/*摧毁类*/class_destroy(icm20608dev.class);printk("icm20608_remove\r\n");return 0;
}/*传统匹配*/
struct spi_device_id icm20608_id[] ={{"my,icm20608",0},{}
};
/*设备树匹配*/
static const struct of_device_id icm20608_of_match[] = {{.compatible = "my,icm20608"},{}
};
/* SPI驱动结构体 */
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,
};/*驱动入口*/
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");
MODULE_AUTHOR("ba che kai qi lai");
当 icm20608 设备和此驱动匹配成功以后 icm20608_probe 函数就会执行。同样的,当注销此驱动的时候 icm20608_remove 函数会执行。
4、probe 函数
在probe函数里面,添加初始化spi_device和icm20608
315行,设置 SPI 为模式 0,也就是 CPOL=0, CPHA=0
316行,设置好 spi_device 以后需要使用 spi_setup 配置一下
318行,设置 icm20608dev 的 private_data 成员变量为 spi_device
321行,调用 icm20608_reginit 函数初始化 ICM20608,主要是初始化 ICM20608 指定寄存器
5、icm20608 寄存器读写与初始化
SPI 驱动最终是通过读写 icm20608 的寄存器来实现的,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对 icm20608 的初始化
①icm20608_read_regs函数
从 icm20608 中读取连续多个寄存器数据;注意:在这实验中, SPI 为全双工通讯没有所谓的发送和接收长度之分。要读取或者发送 N 个字节就要封装 N+1 个字节,第 1 个字节是告诉设备我们要进行读还是写,后面的 N 个字节才是我们要读或者发送的数据。 因为是读操作,因此在第 77行设置第一个数据 bit7 位 1,表示读操作
67和71行,用kzalloc申请内存的时候, 效果等同于先是用 kmalloc() 申请空间 , 然用 memset() 来初始化 ,所有申请的元素都被初始化为 0,GFP_KERNEL,内核内存的正常分配. 可能睡眠.
81行,在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init, 函数原型如下:
void spi_message_init(struct spi_message *m)
m:要初始化的 spi_message。 返回值:无。
82行,spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用 到 spi_message_add_tail 函数,此函数原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
t:要添加到队列中的 spi_transfer。 m:spi_transfer 要加入的 spi_message。 返回值:无
83行,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步 传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
spi:要进行数据传输的 spi_device。 message:要传输的 spi_message。
返回值:错误返回负值。
89-92行,用kfree函数释放内存
②icm20608_write_regs函数
向 icm20608 连续写入多个寄存器数据。此函数和icm20608_read_regs 函数区别不大。
123行,清零表示写操作
③icm20608_read_onereg函数
读取 icm20608 指定寄存器数据
④ icm20608_write_onereg函数
向 icm20608 指定寄存器写入数据
⑤ icm20608_readdata函数
读取 icm20608 六轴传感器和温度传感器原始数据值,应用程序读取 icm20608 的时候这些传感器原始数据就会上报给应用程序,先在设备结构体中添加对应数据类型
⑥icm20608_reginit 函数
初始化 icm20608
6、icm20608_read函数
当应用程序调用 read 函数读取 icm20608 设备文件的时候此函数就会执行。此函数调用上面编写好的icm20608_readdata 函数读取 icm20608 的原始数据并将其上报给应用程序。
代码如下
#include <linux/module.h>
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include "icm20608reg.h"#define ICM20608_CNT 1
#define ICM20608_NAME "icm20680"/*设备结构体*/
struct icm20608_dev
{dev_t devid; /* 设备号 */int major; /* 主设备号 */int minor; /* 次设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */void *private_data; /*私有数据 */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 icm20608dev;/** @description : 从 icm20608 读取多个寄存器数据* @param – dev : icm20608 设备* @param – reg : 要读取的寄存器首地址* @param – val : 读取到的数据* @param – len : 要读取的数据长度* @return : 操作结果*/
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;
}/** @description : 向 icm20608 多个寄存器写入数据* @param – dev : icm20608 设备* @param – reg : 要写入的寄存器首地址* @param – val : 要写入的数据缓冲区* @param – len : 要写入的数据长度* @return : 操作结果* */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{int ret = -1;unsigned char *txdata;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;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata; /* 要发送的数据 */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;}out2:kfree(txdata); /* 释放内存 */
out1:kfree(t); /* 释放内存 */return ret;
}
/** @description : 读取icm20608指定寄存器值,读取一个寄存器* @param - dev: icm20608设备* @param - reg: 要读取的寄存器* @return : 读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data = 0;icm20608_read_regs(dev, reg, &data, 1);return data;
}
/*icm20608写一个寄存器*/
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]);
}/** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量* 一般在open的时候将private_data似有向设备结构体。* @return : 0 成功;其他 失败*/
static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev;return 0;
}
/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;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;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}/* icm20608操作函数 */
struct file_operations icm20608_fops = {.owner = THIS_MODULE,.open = icm20608_open,.release = icm20608_release,.read = icm20608_read,
};/** ICM20608内部寄存器初始化函数 * @param : 无* @return : 无*/
void icm20608_reginit(void)
{u8 value = 0;icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80); /* 复位,复位后为0x40,睡眠模式 */mdelay(50);icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠,自动选择时钟 */mdelay(50);value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value);value = icm20608_read_onereg(&icm20608dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1 = %#X\r\n", value);icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2,0x04); /* 加速度计低通滤波BW=21.2Hz */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); /* 关闭FIFO */
}
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;/* 1、构建设备号 */icm20608dev.major = 0;if (icm20608dev.major){icm20608dev.devid = MKDEV(icm20608dev.major, 0);ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);}else{ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);icm20608dev.minor = MINOR(icm20608dev.devid);}if (ret < 0){printk("icm20608 chrdev_region err!\r\n");goto fail_devid;}printk("icm20608dev major = %d, minor = %d\r\n", icm20608dev.major, icm20608dev.minor);/* 2、注册设备 */icm20608dev.cdev.owner = THIS_MODULE;cdev_init(&icm20608dev.cdev, &icm20608_fops);ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);if (ret < 0){printk("icm20608 cdev_add err!\r\n");goto fail_cdev;}/* 3、创建类 */icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);if (IS_ERR(icm20608dev.class)){ret = PTR_ERR(icm20608dev.class);printk("icm20608 chrdev_class err!\r\n");goto fail_class;}/* 4、创建设备 */icm20608dev.device = device_create(icm20608dev.class, NULL,icm20608dev.devid, NULL, ICM20608_NAME);if (IS_ERR(icm20608dev.device)){ret = PTR_ERR(icm20608dev.device);printk("icm20608 chrdev_device err!\r\n");goto fail_device;}/*初始化spi_device*/spi->mode = SPI_MODE_0;spi_setup(spi);/*设置icm20608私有数据*/icm20608dev.private_data = spi;/*初始化icm20608*/icm20608_reginit();printk("icm20608_probe\r\n");return 0;fail_device:class_destroy(icm20608dev.class);
fail_class:cdev_del(&icm20608dev.cdev);
fail_cdev:unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:return ret;
}static int icm20608_remove(struct spi_device *spi)
{/* 删除字符设备 */cdev_del(&icm20608dev.cdev);/*注销设备号*/unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);/*摧毁设备*/device_destroy(icm20608dev.class, icm20608dev.devid);/*摧毁类*/class_destroy(icm20608dev.class);printk("icm20608_remove\r\n");return 0;
}/*传统匹配*/
struct spi_device_id icm20608_id[] = {{"my,icm20608", 0},{}
};
/*设备树匹配*/
static const struct of_device_id icm20608_of_match[] = {{.compatible = "my,icm20608"},{}
};
/* SPI驱动结构体 */
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,
};/*驱动入口*/
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");
MODULE_AUTHOR("ba che kai qi lai");
四、APP编写
#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数组元素个数)argv:具体参数,也可以写作char **argv./icm20608APP <filename> ./icm20608APP /dev/icm20608
*/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("file 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); /*100ms */}/*关闭文件*/close(fd);return 0;
}
最终将传感器原始数据和得到的实际值显示在终端上。
编译验证
在编译APP的时候,编译命令加入如下参数即可使能硬件浮点编译
-march-armv7-a -mfpu-neon -mfloat=hard
编译之后有没有使用硬件浮点呢?使用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了,输入如下命令:
arm-linux-gnueabihf-readelf -A icm20608App
使用APP测试如下
这是会一直打印的,部分截图如上图
相关文章:

Linux SPI 驱动实验
目录 一、Linux 下 SPI 驱动框架简介 1、SPI 主机驱动 2、SPI 设备驱动 SPI 设备数据收发处理流程 3、SPI 设备和驱动匹配过程 二、添加SPI 设备信息 1、添加 ICM20608 所使用的 IO 2、 在 ecspi3 节点追加 icm20608 子节点 三、编写 ICM20608 驱动 1、修改makefile…...

[1.4]计算机系统概述——操作系统的体系结构
第一章 计算机系统概述 操作系统的体系结构 大内核/单内核/宏内核微内核 通过之前的学习,我们知道计算机系统的层次结构是这样的。 但是操作系统的内部其实还可以再进一步地划分。 一部分是内核的功能,一部分是非内核的功能。 操作系统最核心的功能&…...

FPGA的GigE Vision IP相机图像采集方案设计,转换为千兆UDP,支持10G MAC
1 概述 GigE Vision是一个比较复杂的协议,要在FPGA中完全实现具有较大的难度。如果FPGA作为接收端希望实现GigE Vision相机的配置和图像采集功能,则只需要实现其中小部分功能即可。本文对原有GigE Vision协议的结构进行了裁剪,仅保留设备搜索…...

大数据相关面试题
linux 常见linux高级命令? top、iotopnetstatdf -hjmap -heaptarrpmps -efshell 用过的shell工具? awk Awk 命令详解 - 简书 awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来…...

AI绘画第二步,抄作业复现超赞的效果!
上一篇,讲了如何安装AI绘画软件,但是装完后发现生成效果很渣!而网上那些效果都很赞。真的是理想很丰满,现实很骨感。今天就是来聊聊如何抄作业,最大程度的还原那些超赞的效果。换一种说法就是,教大家如何使…...
Python的并发编程
我们将一个正在运行的程序称为进程。每个进程都有它自己的系统状态,包含内存状态、打开文件列表、追踪指令执行情况的程序指针以及一个保存局部变量的调用栈。通常情况下,一个进程依照一个单序列控制流顺序执行,这个控制流被称为该进程的主线…...

【Linux】基本系统维护命令
😊😊作者简介😊😊 : 大家好,我是南瓜籽,一个在校大二学生,我将会持续分享C/C相关知识。 🎉🎉个人主页🎉🎉 : 南瓜籽的主页…...

高数:数列的收敛
数列特点无限个数特定顺序数列和集合区别集合可以乱序,数列不行集合出现重复元素依然相同,数列出现新的重复元素就不相等[1,2,3,4][1,2,3,3,4]对集合来说相等,…...

不平凡的一天——
作者:指针不指南吗 专栏:个人日常记录 🐾或许会很慢,但是不可以停下来🐾 文章目录1.自我介绍2.上学期3.不凡的一天4.新学期写个博客,简单记录一下,新学期加油!!ÿ…...
【Java基础】Map遍历的5种方式
目录 创建一个集合 方式一:Iterator 迭代器遍历 map.entrySet().iterator(); map.keySet().iterator(); 方式二:For Each方式遍历 map.forEach(BiConsumer action) 方式三:获取Collection集合 map.values().forEach() 方式四&#x…...

第十四届蓝桥杯三月真题刷题训练——第 2 天
目录 题目1:奇数倍数 代码: 题目2:求值 代码: 题目3:求和 代码: 题目4:数位排序 代码: 题目1:奇数倍数 题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即…...

自然语言处理历史最全预训练模型(部署)汇集分享
什么是预训练模型?预练模型是其他人为解决类似问题而创建的且已经训练好的模型。代替从头开始建立模型来解决类似的问题,我们可以使用在其他问题上训练过的模型作为起点。预训练的模型在相似的应用程序中可能不是100%准确的。本文整理了自然语…...

csdn写文章自定义表格怎么做
前言 CSDN写文章时,经常会用到表格,不同于Word文档中直接插入表格(自定义几行几列),使用CSDN自带的md文本编辑器时,很难快速插入想要的表格样式,追究原因,也是因为md的语法问题&…...
Pytorch处理数据与训练网络问题汇总(协同训练)
基础语法 模型训练 【Swin-Unet】官方代码预训练权重加载函数load_from() 实际上由于SwinUnet是一个encoder-decoder对称的结构,因此加载权重时,作者并没有像通常那样仅仅加载encoder部分而不加载decoder部分,而是同时将encoder的权重对称地…...

机器学习:基于神经网络对用户评论情感分析预测
机器学习:基于神经网络对用户评论情感分析预测 作者:AOAIYI 作者简介:Python领域新星作者、多项比赛获奖者:AOAIYI首页 😊😊😊如果觉得文章不错或能帮助到你学习,可以点赞Ǵ…...

Vue3之组件间传值避坑指南
组件间传值的两个坑 我们都知道父组件可以把值传递到自组件中,但是有时候子组件需要修改这个父组件传递过来的这个值,我们可以想象下能修改成功吗?这是坑之一。我们在组件间传值的时候,都是一个属性名对应一个值,接收…...

02-问题思考维度:抓住核心用户、场景化分析、需求收集与辨别、用户故事
文章目录2.1 抓住核心用户2.1.1 为什么要抓住核心用户2.1.2 核心用户的特征根据不同维度,描述核心用户2.1.3 如何抓住核心用户2.2 场景化分析2.2.1 场景五要素2.2.2 场景化分析方法2.2.3 场景化分析方法的应用2.3 需求收集与辨别2.3.1 需求的定义及层次2.3.2 需求收…...
C 语言编程 — GCC Attribute 语法扩展
目录 文章目录目录Attribute 属性扩展机制__attribute__((packed))__attribute__((aligned(n)))__attribute__((noreturn))__attribute__((unused))Attribute 属性扩展机制 GCC 的特点之一就是 Attribute 语法扩展机制,通过使用 __attribute__ 关键字可以设置以下对…...
LeetCode 热题 C++ 399. 除法求值 406. 根据身高重建队列
LeetCode 399 给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。 另有一些以数组 queries 表示的问题,其…...

提升Mac使用性能的5大方法,CleanMyMacX 2023非常的好用哦~
近些年伴随着苹果生态的蓬勃发展,越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现,它的使用逻辑与Windows存在很多不同,而且随着使用时间的增加,一些奇奇怪怪的文件也会占据有限的磁盘空间,进而影响使用…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
智能职业发展系统:AI驱动的职业规划平台技术解析
智能职业发展系统:AI驱动的职业规划平台技术解析 引言:数字时代的职业革命 在当今瞬息万变的就业市场中,传统的职业规划方法已无法满足个人和企业的需求。据统计,全球每年有超过2亿人面临职业转型困境,而企业也因此遭…...