STM32MP157驱动开发——SPI驱动

news2025/1/14 2:13:53

STM32MP157驱动开发——SPI驱动

  • 一、简介
    • 1.SPI介绍
    • 2.STM32MP1 SPI介绍
    • 3. ICM-20608 简介
    • 4.Linux下的SPI框架
  • 二、驱动开发
    • 1)IO 的 pinctrl 子节点创建与修改
    • 2)SPI 设备节点的创建与修改
    • 3)ICM20608驱动
    • 4)测试App
    • 5)运行测试


参考文章:【正点原子】STM32MP1嵌入式Linux驱动开发——SPI总线框架

一、简介

   之前已经学习了 Linux 下的 platform 总线框架、 I2C 总线框架,这一节就学习第三种总线框架——SPI总线。与 I2C 总线一样,SPI 是物理总线,也是一种很常用的串行通信协议,本节将使用 SPI 框架对接开发板上的 ICM-20608 这个六轴传感器,可以在应用程序中读取 ICM-20608 的原始传感器数据。

1.SPI介绍

   I2C 是串行通信的一种,只需要两根线就可以完成主机和从机之间的通信,但是 I2C 的速度最高只能到 400KHz,如果对于访问速度要求比价高的话 I2C 就不适合了。
   SPI 全称是 Serial Perripheral Interface,也就是串行外围设备接口,是一种高速、全双工的同步通信总线,SPI 时钟频率相比 I2C 要高很多,最高可以工作在上百 MHz。SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要 4 根线,但是也可以使用三根线(单向传输),本节介绍标准的 4 线 SPI:

  • CS/SS,Slave Select/Chip Select,片选信号线,用于选择需要进行通信的从设备。I2C 主机是通过发送从机设备地址来选择需要进行通信的从机设备, SPI 主机不需要发送从机设备,直接将相应的从机设备片选信号拉低即可
  • SCK, Serial Clock,串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟
  • MOSI/SDO,Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线只能用于主机向从机发送数据,也就是主机输出,从机输入
  • MISO/SDI,Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只能用户从机向主机发送数据,也就是主机输入,从机输出

2.STM32MP1 SPI介绍

STM32MP1 自带的 SPI 全称为:Serial peripheral interface。 其特性如下:

  • 全双工同步串口接口
  • 半双工模式
  • 可配置的主/从模式
  • 支持 I2S 协议
  • 在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断
  • 允许 16 位, 24 位或者 32 位数据长度
  • 支持软件片选和硬件片选

3. ICM-20608 简介

  ICM-20608 是一款 6 轴 MEMS 传感器,包括 3 轴加速度和 3 轴陀螺仪。陀螺仪和加速度计都是 16 位的 ADC,并且支持 I2C 和 SPI 两种协议,使用 I2C 接口的话通信速度最高可以达到 400KHz,使用 SPI 接口的话通信速度最高可达到 8MHz。开发板上的 ICM-20608 通过 SPI 接口和 STM32MP157 连接在一起。
  如果使用 IIC 接口,ICM-20608 的 AD0 引脚决定 I2C 设备从地址的最后一位,如果 AD0 为 0,ICM-20608 从设备地址是 0X68,如果 AD0 为 1,ICM-20608 从设备地址为0X69。

4.Linux下的SPI框架

在 Linux 内核当中,与 I2C 总线框架一样,SPI 总线框架(也可以叫做 SPI 子系统)也可以分为三个部分:
SPI 核心层:SPI 核心层是 Linux 的 SPI 子系统的核心代码部分,提供了核心数据结构的定义、SPI 控制器驱动和设备驱动的注册、注销、管理等 API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便 SPI设备驱动通过总线控制器进行数据收发。在 Linux 系统中, SPI 核心层的代码位于 drivers/spi/spi.c。
SPI 控制器驱动层:每种处理器平台都有自己的 SPI 控制器驱动程序,它的职责是为系统中的 SPI 总线实现相应的读写方法。例如 STM32MP1 就有六个 SPI,那么就有六个 SPI 控制器,每个控制器都有一条特定的 SPI 总线的读写。SPI 子系统使用 struct spi_master 数据结构体来描述 SPI 控制器。在内核源码 drivers/spi 目录下有很多以 spi-xxxx.c 命名的源文件。
SPI 设备驱动层: SPI 从设备对应的驱动程序,比如一些 SPI 接口的芯片器件对应的驱动程序。

二、驱动开发

原理图:
在这里插入图片描述
PZ0~3 分别连接到 ICM-20608 的 SCK、SDA、AD0 和 CS。其中 6D_INT 为 ICM20608 的中断引脚,连接到 PA14 引脚上,本节没有使用到。

1)IO 的 pinctrl 子节点创建与修改

首先根据所使用的 IO 来创建或者修改 pinctrl 子节点,并且要注意检查相应的 IO 有没有被其它的设备所使用,如果有多个 pinctrl 配置相同的 IO 是没有关系的,只要保证没有被设备调用就行。
打开stm32mp15-pinctrl.dtsi文件,在其中设置SPI1接口:
在这里插入图片描述
在官方的SPI节点基础上,添加pins3片选引脚。

2)SPI 设备节点的创建与修改

采用设备树方式的情况下,SPI 从机设备信息描述就通过创建相应的设备子节点来完成,在 stm32mp157d-atk.dts 这个设备树文件中,创建一个 SPI 从机设备节点,描述该设备的相关信息:

&spi1 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&spi1_pins_a>;
	pinctrl-1 = <&spi1_sleep_pins_a>;
	cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;
	status = "okay";
	
	spidev: icm20608@0 {
		compitable = "alientek,icm20608";
		reg = <0>;
		spi-max-frequency = <8000000>;
	};
};

“cs-gpios”属性是用来设置 SPI 的片选引脚。SPI 主机驱动就会根据此属性去控制
设备的片选引脚,本节使用 PZ3 作为片选引脚。如果一个 SPI 接口下连接了多个 SPI 芯片,就使用如下的描述:

cs-gpios = <&gpio1 0 0>, <&gpio1 1 0>, <&gpio1 2 0>, <&gpio1 3 0>;

3)ICM20608驱动

icm20608.h:

#ifndef ICM20608_H
#define ICM20608_H

#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 */

/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#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

主要是一些相关的寄存器数值设置。

icm20608.c:

#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include "icm20608.h"

#define ICM20608_CNT	1
#define ICM20608_NAME	"icm20608"

struct icm20608_dev {
	struct spi_device *spi;		/* spi设备 */
	dev_t devid;				/* 设备号 	 */
	struct cdev cdev;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	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;		/* 温度原始值 			*/
};

/*从icm20608读取多个寄存器数据*/
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->spi;
    
	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;
}

/*向icm20608多个寄存器写入数据*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->spi;
	
	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;
}

/*读取icm20608指定寄存器值,读取一个寄存器*/
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);
}

/*读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、三轴加速度计和内部温度*/
void icm20608_readdata(struct icm20608_dev *dev)
{
	unsigned char data[14];
	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]);
}

/*open函数*/
static int icm20608_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*从设备读取数据*/
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 cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	struct icm20608_dev *dev = container_of(cdev, struct icm20608_dev, cdev);
            
	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;
}
/*设备操作函数集*/
static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};

/*icm初始化操作*/
void icm20608_reginit(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 ID = %#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						*/
}

/*probe函数*/
static int icm20608_probe(struct spi_device *spi)
{
	int ret;
	struct icm20608_dev *icm20608dev;
	
	/* 分配icm20608dev对象的空间 */
	icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev), GFP_KERNEL);
	if(!icm20608dev)
		return -ENOMEM;
		
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT, ICM20608_NAME);
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", ICM20608_NAME, ret);
        return 0;
	}

	/* 2、初始化cdev */
	icm20608dev->cdev.owner = THIS_MODULE;
	cdev_init(&icm20608dev->cdev, &icm20608_ops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&icm20608dev->cdev, icm20608dev->devid, ICM20608_CNT);
	if(ret < 0) {
		goto del_unregister;
	}
	
	/* 4、创建类 */
	icm20608dev->class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev->class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	icm20608dev->device = device_create(icm20608dev->class, NULL, icm20608dev->devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev->device)) {
		goto destroy_class;
	}
	icm20608dev->spi = spi;
	
	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	
	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit(icm20608dev);	
	/* 保存icm20608dev结构体 */
	spi_set_drvdata(spi, icm20608dev);

	return 0;
destroy_class:
	device_destroy(icm20608dev->class, icm20608dev->devid);
del_cdev:
	cdev_del(&icm20608dev->cdev);
del_unregister:
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
	return -EIO;
}

/*remove函数,移除设备驱动*/
static int icm20608_remove(struct spi_device *spi)
{
	struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
	/* 注销字符设备驱动 */
	/* 1、删除cdev */
	cdev_del(&icm20608dev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT); 
	/* 3、注销设备 */
	device_destroy(icm20608dev->class, icm20608dev->devid);
	/* 4、注销类 */
	class_destroy(icm20608dev->class); 
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},
	{}
};

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

/* 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("amonter");
MODULE_INFO(intree, "Y");

主要分为三个部分:
1.icm20608的设备结构体设置,以及设备寄存器的初始化、寄存器数据读取与写入(包括单个寄存器与多个寄存器)
2.设备的open、read、release函数
3.设备的probe挂载(本质是一个字符设备)与remove函数,以及设备的匹配表
注:本节的icm20608设备的寄存器数据读写没有进行加锁,可以通过原子操作或自旋锁等机制,对寄存器数据的读写进行加锁操作,提高鲁棒性

4)测试App

icm20608App.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>

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;
}

5)运行测试

首先,在Linux内核的menuconfig中使能SPI控制器:
在这里插入图片描述
编译出新的内核镜像和设备树,用于开发板启动。
之后将编译出的icm20608驱动和测试App文件放入相应文件夹,启动开发板。
注:在编译测试App是可以使能硬件浮点,可以加速计算,使用如下命令即可:

arm-none-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o icm20608App

可以使用以下命令查看是否使能成功:

arm-none-linux-gnueabihf-readelf -A icm20608App

在开机时就会显示以下信息:
在这里插入图片描述
将驱动文件挂载,然后使用测试程序进行测试即可。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分享】订阅用友U8集简云连接器同步费用审批数据至用友U8系统

方案简介 集简云通过与钉钉连接平台深度融合&#xff0c;实现OA审批通过后&#xff0c;将采购、报销、收款、售后等费用审批单数据自动同步至用友U8系统&#xff0c;大大提高了企业日常采购、报销、付款等的工作效率&#xff0c;实现企业业务流程的自动化&#xff0c;为企业降…

Docker的数据管理

目录 一、数据卷 二、数据卷容器 三、容器互联 容器中管理数据主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumes&#xff09;数据卷容器&#xff08;Data Volume Dontainers&#xff09; 一、数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可…

5点电容式触摸屏控制芯片GT811介绍

5点电容式触摸屏控制芯片GT811简介 5点电容式触摸屏控制芯片GT811&#xff0c;采用投射式电容检测原理&#xff0c;由16个驱动通道与10个感应通道组成触摸检测网络&#xff0c;通过内置模拟放大电路、数字运算模块&#xff0c;及高性能MPU得到实时准确的触摸信息&#xff0c;并…

rocketmq安装

链接&#xff1a;https://pan.baidu.com/s/14ziQH62MeYmM8N6JsH5RcA 提取码&#xff1a;yyds 下载rocketmq-all-4.9.3-bin-release.zip 下载、修改配置 mkdir -p /app/rocketmq cd /app/rocketmqunzip rocketmq-all-4.9.3-bin-release.zip cd rocketmq-4.9.3/修改 配置文件&…

F28335第十六篇——Flash操作

文章目录前言配置方法擦除函数编写和校验函数前言 本文主要介绍如何对DSP28335进行Flash操作。 本文主要参考资料&#xff1a; TI.Flash2833x_API_Readme DSP的Flash操作需要借助官方提供API。官方提供的API主要完成Flash的擦除&#xff0c;编写&#xff0c;校验三种功能。…

八、Java 15 新特性

八、Java 15 新特性 JDK 15 在 2020 年 9 月 15 号正式发布了&#xff01;根据发布的规划&#xff0c;这次发布的 JDK 15 将是一个短期的过度版&#xff0c;只会被 Oracle 支持&#xff08;维护&#xff09;6 个月&#xff0c;直到明年 3 月的 JDK 16 发布此版本将停止维护。而…

VUE3-生命周期钩子《六》

目录 1.onMounted() 2.onUpdated() 生命周期的作用&#xff0c;页面加载的时候&#xff0c;主动执行某些程序。生命周期钩子有很多种&#xff0c;每一种都是有顺序的&#xff0c;如果不按照顺序执行的话&#xff0c;那么就不会触发某种效果&#xff0c;所以先要了解生命周期钩…

Java Web高级面试题(一)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…

常用的工具网站(网址 + 效果图)

一&#xff0c;阿里图标库 https://www.iconfont.cn/?spma313x.7781069.1998910419.d4d0a486a 二&#xff0c;AI人工智能图片放大 https://bigjpg.com/zh 三&#xff0c;一个有情怀的免费PPT模板下载网站&#xff01; https://www.ypppt.com/ 四&#xff0c;照片抠图…

AOP技术

目录 一、简介 1.1、OCP原则 1.2、AOP介绍及使用场景 二、AOP在项目中的使用 2.1、集成使用 2.2、定义全局异常通知 2.3、AOP注解的含义 2.4、多个切面的执行顺序 三、通知的执行顺序&#xff08;基于spring-aop5版本&#xff09; 四、常用的四种切入点表达式 4.1、…

【技术分享】手机端代理网络共享至win电脑端

【技术分享】手机端代理网络共享至win电脑端使用前提工具准备具体步骤1. 打开并实现安卓模拟器内的vP|n2. 打开并配置安卓模拟器内的Proxy Server3. 配置win的代理4. 完成使用前提 手机端具备代理功能&#xff0c;而电脑端不具备&#xff1b;希望电脑端使用手机端的代理功能上…

嵌入式技术之IAP,自从有了它老板再也不担心我的代码了!(中)

上篇文章我们一起学习了IAP的工作原理和IAP包含的3个重要功能&#xff1a;数据交互、数据存储和程序跳转。 这3个重要功能称为“IAP的三板斧”&#xff0c;接下来我们看这三板斧具体完成哪些细节工作&#xff0c;如何实现这三板斧。 1.数据交互 数据交互的功能是IAP核心功能…

Gson解析JSON

1.介绍 Gson是Google提供的处理JSON数据的Java类库&#xff0c;主要用于转换Java对象和JSON对象。 2.依赖 <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency><groupId>com.google.code.gson</groupId><artifac…

python协程--yield和yield from

字典为动词“to yield”给出了两个释义&#xff1a;产出和让步。对于 Python 生成器中的 yield 来说&#xff0c;这两个含义都成立。yield item 这行代码会产出一个值&#xff0c;提供给 next(...) 的调用方&#xff1b;此外&#xff0c;还会作出让步&#xff0c;暂停执行生成器…

Web应用怎样获取Access Token?

1.在联盟创建服务器应用 参考文档&#xff1a;开发准备 2.获取用户级Access Token 2.1 获取code 参考文档&#xff1a;接入华为帐号获取凭证 2.1.1 先按照跳转链接进行配置url https://oauth-login.cloud.huawei.com/oauth2/v3/authorize? response_typecode& acces…

docker(七)容器监控(CAdvisor+InfluxDB+Granfana)

docker可能会运行多个容器&#xff0c;一个宿主机上有多个容器时&#xff0c;需要监控容器的&#xff1a;CPU使用率&#xff0c;内存使用率&#xff0c;网络状态&#xff0c;磁盘空间等数据。 一、docker stats docker stats命令可以监控以下数据&#xff1a; 数据是实时的&…

FFmpeg简单使用:过滤器 ---- 视频过滤2

1. 简介 FFmpeg filter提供了很多⾳视频特效处理的功能&#xff0c;⽐如视频缩放、截取、翻转、叠加等。 其中定义了很多的filter&#xff0c;例如以下常⽤的⼀些filter。 scale&#xff1a;视频/图像的缩放 overlay&#xff1a;视频/图像的叠加 crop&#xff1a;视频/图像的裁…

新建unity项目

在此处点击新项目按钮&#xff0c;建立新的项目。 选择对应的项目模板和项目名称&#xff0c;位置。 项目新建会花费几分钟到十几分钟的时间。 新建完项目打开后就可以进入到unity引擎中。 新建项目会默认创建一个场景&#xff0c;场景保存在Assets--> Scenes中&#xff0c;…

服务器,只有“鞋盒”大小

上期,我们谈到了边缘端的远中近分类法,并介绍了戴尔科技集团最新发布的“远边缘”服务器PowerEdge XR4000。今天我们来继续说说这款服务器的奥秘。      服务器,只有“鞋盒”大小      PowerEdge XR4000代表了戴尔继续致力于边缘创新的承诺,它的大小只与鞋盒差不多,是P…

Java 集合有哪些内容?

今天我们就来简单的了解下java中的集合&#xff0c;有了解过得朋友都知道&#xff0c;也都用过&#xff0c;比如说做常用的List&#xff0c;还有Set、Map&#xff0c;而且像List和Set都是用于存储单列数据的集合&#xff0c;他们的父接口都是Conllection&#xff0c;List的特点…