HAL STM32F103 软件I2C读写MPU6050(附DMP库移植)

目录

1. STM32CubeMX配置

 2. I2C通信

2.1 起始信号、终止信号、空闲信号及应答信号

2.2 写时序

2.3 读时序

2.4 I2C读写数据的时机(见参考文章2)

2.5 软件I2C实现

2.6 Keil逻辑分析仪的使用

 3. MPU读取加速度计和陀螺仪数据

 4. 移植DMP库,输出欧拉角

4.1 官网下载DMP库

4.2 添加文件

4.3 修改inv_mpu.h

4.4 修改inv_mpu.c

4.5 修改inv_mpu_dmp_motion_driver.c

4.6 添加DMP相关函数

 5. 参考


1. STM32CubeMX配置

创建一个新的STM32CubeMX项目

所使用的单片机型号为STM32F103C8T6。在1搜索单片机的具体型号,双击2即可创建一个基于STM32F103C8T6单片机的工程。

首先选择外部晶振。

配置时钟树,选择外部高速晶振作为时钟源,设置AHB总线时钟为72MHz。

配置调试口。配置不配置都可以正常使用,但是不可以将调试口的两个引脚复用为其他功能,如果复用,debug会失效。所以仍然配置一下,看是哪两个引脚。

配置完成后,调试口的引脚是PA13和PA14。 

在本项目中,为熟悉I2C通讯协议。OLED模块使用硬件I2C,MPU6050使用软件I2C。

设置OLED的I2C。将PB8设置为I2C1_SCL,将PB9设置为I2C1_SDA,

配置MPU6050 软件I2C所需要的引脚。 

 在这个实验中,使用PB3与SDA相连,PB4与SCL相连。

将PB3和PB4配置为GPIO输出模式,然后在GPIO中设置两个引脚都为高电平,开漏输出,上拉,高速率。 

项目工程设置。设置项目名称,选择相应设置,并调大项目堆栈。

Generate periphiral initialization as a pair of '.c/.h'files per periphiral.这个选项一定要勾上,每个外设单独一个c文件和h文件,如果不勾上所有配置都在一个文件里面,特别乱。 

可以生成项目工程代码了。

 2. I2C通信

在写具体的代码前,讲一下I2C的时序。

2.1 起始信号、终止信号、空闲信号及应答信号

起始信号:当SCL为高电平时,SDA从高电平向低电平转换

中止信号:当SCL为高电平时,SDA从低电平向高电平转换

空闲信号:SDA和SCL同时为高电平时。

应答信号:发送端向接收端发送完一个字节数据(一般设置为8位),第九个时钟周期,接收端发送给发送端一个应答信号,数据才算传输成功。低电平表示应答成功,高电平表示应答失败。

2.2 写时序

这是一个完整的写时序图,主机经过如下操作:

  1. 空闲状态:SDA和SCL都为高电平
  2. 主机发起起始信号(不管读写都是主机发起的):SCL高电平时,SDA由高电平向低电平转换
  3. 主机发送7位设备地址+1位读写位:7位设备地址位,加上1位读/写位(0表示写,1表示读)。如MPU6050的地址位0x68(这是8位表示的0x01101000),要修改为七位,左移一位所以为(0x11010000 = 0xD0)。所以此处发送0x11010001。
  4. 等待从机应答:主机释放SDA到高电平状态,从机通过拉低SDA来表示ACK,如果SDA仍是高电平则表示NACK。
  5. 主机发送寄存器地址:8位寄存器地址。
  6. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  7. 主机发送数据:8位数据
  8. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  9. 可以不断发送数据,重复执行7和8
  10. 主机发送中止信号:SCL高电平时,SDA由低电平向高电平转换

绿色部分为什么SDA有一个高电平状态,这是因为主机会将SDA拉高释放(这是软件I2C的操作,硬件I2C是主机释放SDA,由上拉电阻拉高SDA),从机才可以去拉低SDA,所以这里有一个短时间的高电平状态。

2.3 读时序

这是一个完整的读时序图,主机经过如下操作:

  1. 空闲状态:SDA和SCL都为高电平
  2. 主机发起起始信号:SCL高电平时,SDA由高电平向低电平转换
  3. 主机发送7位设备地址+1位读写位:7位设备地址位,加上写位0。
  4. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  5. 主机发送寄存器地址:8位寄存器地址。
  6. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  7. 主机再次发起起始信号 :SCL高电平时,SDA由高电平向低电平转换
  8. 主机发送7位设备地址+1位读写位:7位设备地址位,加上读位0。
  9. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  10. 主机接收数据:8位数据
  11. 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
  12. 可以不断接收数据,重复执行10和11
  13. 主机发送中止信号:SCL高电平时,SDA由低电平向高电平转换

2.4 I2C读写数据的时机(见参考文章2)

需要非常清楚地明白I2C是在什么时候才读写数据的?不清楚的话会很影响软件I2C的实现。

1) 所谓读即是指MCU从器件的数据总线上根据一定的时序来读取器件的数据。一般而言,MCU提供一个边沿信号(上升沿或者下降沿均可)告诉器件可以发数据了,器件检测到边沿信号以后,立即在数据总线上更新数据,待数据稳定以后,MCU即可读取数据。所以一般所说的上升沿(下降沿)开始读数据是不准确地说法,上升沿(下降沿)这是数据总线上的数据发生改变,MCU并没有在此时刻读取数据,而是等待数据稳定之后才开始读取数据。

2)所谓写即是指MCU向器件写入数据,其操作是:先将数据放置在数据总线上,等待其稳定之后,MCU产生一个边沿信号,将数据写入器件。

写操作必须先将数据准备在数据总线上,等待数据稳定之后,MCU产生一个边沿信号,写入数据到器件。从图中可以看出,在起始状态,数据总线上准备数据,稳定后遇到上升沿MCU将数据写入到器件。写完之后,数据总线上出现第二位数据A0,等待其稳定之后,MCU产生一个上升沿将A0写入器件。可以简单理解为“写稳读变”。MCU在数据总线上的数据稳定之后,检测边沿信号写数据到器件;MCU发出边沿信号告诉器件发送数据,检测到边沿信号之后,器件改变(更新)数据,等待稳定之后MCU读取数据。

2.5 软件I2C实现

Soft_I2C.h

#ifndef __SOFT_I2C_H
#define __SOFT_I2C_H
#include "stdint.h"
#include "stm32f1xx_hal.h"
extern int SDA_bit;
extern int SCL_bit;
void SOFT_I2C_W_SCL(uint8_t BitValue);

void SOFT_I2C_W_SDA(uint8_t BitValue);

uint8_t SOFT_I2C_R_SDA(void);

void SOFT_I2C_Start(void);

void SOFT_I2C_Stop(void);

void SOFT_I2C_SendByte(uint8_t Byte);

uint8_t SOFT_I2C_ReceiveByte(void);

void SOFT_I2C_SendAck(uint8_t AckBit);

uint8_t SOFT_I2C_ReceiveAck(void);

void RCCdelay_us(uint32_t udelay);
#endif

 Soft_I2C.c

#include "Soft_I2C.h"
#include "sr04.h"
int SCL_bit = 1;
int SDA_bit = 1;
/*
*函数:I2C写SCL电平
*参数:BitValue 当前需要写入SCL的电平,值为0或1
*返回值:无
*注意事项:当BitValue为0时,置SCL为低电平;当BitValue为1时,置SCL为高电平
*/
void SOFT_I2C_W_SCL(uint8_t BitValue){
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, (BitValue ? GPIO_PIN_SET : GPIO_PIN_RESET));
	SCL_bit = BitValue;
	RCCdelay_us(5);
}

/*
*函数:I2C写SDA电平
*参数:BitValue 当前需要写入SDA的电平,值为0或1
*返回值:无
*注意事项:当BitValue为0时,置SDA为低电平;当BitValue为1时,置SCL为高电平
*/
void SOFT_I2C_W_SDA(uint8_t BitValue){
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, (BitValue ? GPIO_PIN_SET : GPIO_PIN_RESET));
	SDA_bit = BitValue;
	RCCdelay_us(5);
}

/*
*函数:I2C读取SDA引脚的电平状态
*参数:无
*返回值:读取出的电平
*注意事项:低电平为0,高电平为1
*/
uint8_t SOFT_I2C_R_SDA(void){
	uint8_t BitValue;
	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == GPIO_PIN_RESET){
		BitValue = 0;
	}
	else{
		BitValue = 1;
	}
	return BitValue;
}

/*
*函数:I2C开始信号
*参数:无
*返回值:无
*注意事项:无
*/
void SOFT_I2C_Start(void){
	SOFT_I2C_W_SDA(1);
	SDA_bit = 1;
	SOFT_I2C_W_SCL(1);
	SCL_bit = 1;
	SOFT_I2C_W_SDA(0);
	SDA_bit = 0;
	SOFT_I2C_W_SCL(0);
	SCL_bit = 0;
}

/*
*函数:I2C停止信号
*参数:无
*返回值:无
*注意事项:无
*/
void SOFT_I2C_Stop(void){
	SOFT_I2C_W_SDA(0);
	SDA_bit = 0;
	SOFT_I2C_W_SCL(1);
	SCL_bit = 1;
	SOFT_I2C_W_SDA(1);
	SDA_bit = 1;
}

/*
*函数:I2C发送8位数据
*参数:数据
*返回值:无
*注意事项:无
*/
void SOFT_I2C_SendByte(uint8_t Byte){
	uint8_t i;
	for(i = 0; i < 8; i++){
		SOFT_I2C_W_SDA(Byte & (0x80 >> i));
		SDA_bit = (Byte & (0x80 >> i));
		SOFT_I2C_W_SCL(1);
		SCL_bit = 1;
		SOFT_I2C_W_SCL(0);
		SCL_bit = 0;
	}
}
/*
*函数:I2C接收8位数据
*参数:无
*返回值:数据
*注意事项:无
*/
uint8_t SOFT_I2C_ReceiveByte(void){
	uint8_t Byte = 0x00;
	SOFT_I2C_W_SDA(1); //先拉高sda,之前主机一直占用着sda,释放sda,让从机占据。
	SDA_bit = 1;
	uint8_t i;
	for(i = 0; i < 8; i++){
		SOFT_I2C_W_SCL(1); //先拉高scl,从机会把数据放到sda上
		SCL_bit = 1;
		if(SOFT_I2C_R_SDA() == 1){ //是0的时候不用改因为Byte是00000000
			Byte |= (0x80 >> i);
		}
		SOFT_I2C_W_SCL(0); 
		SCL_bit = 0;
	}
	return Byte;
}
/*
*函数:I2C发送应答位
*参数:0或1
*返回值:无
*注意事项:无
*/
void SOFT_I2C_SendAck(uint8_t AckBit){
	SOFT_I2C_W_SDA(AckBit);
	SDA_bit = AckBit;
	SOFT_I2C_W_SCL(1);
	SCL_bit = 1;
	SOFT_I2C_W_SCL(0);
	SCL_bit = 0;
}
/*
*函数:I2C接收应答位
*参数:无
*返回值:0或1
*注意事项:无
*/
uint8_t SOFT_I2C_ReceiveAck(void){
	uint8_t AckBit;
	SOFT_I2C_W_SDA(1);
	SDA_bit = SOFT_I2C_R_SDA();
	SOFT_I2C_W_SCL(1);
	SCL_bit = 1;
	AckBit = SOFT_I2C_R_SDA();
	SOFT_I2C_W_SCL(0);
	SCL_bit = 0;
	return AckBit;
}

/*
*函数:微秒延迟函数
*参数:多少微秒
*返回值:无
*注意事项:无
*/
void RCCdelay_us(uint32_t udelay){
	__IO uint32_t Delay = udelay * 72 / 8;
	
	do
	{
		__NOP();
	}
	while(Delay--);
}

2.6 Keil逻辑分析仪的使用

软件I2C中的SDA_bit和SCL_bit是自己创建的全局变量,以便于使用逻辑分析仪看时序图。为什么要使用全局变量,一开始我使用逻辑分析仪监测PB3和PB4引脚的电平,但是一直不变。网上查询了一下,好像是因为PB3和PB4设置为开漏输出,所以使用逻辑分析仪无法看到变化,因此采用全局变量跟踪。

使用以上代码,使用SDA_bit和SCL_bit代替PB3和PB4引脚的电平,然后进入Keil的debug模式。使用debug前需要先进行如下配置:

点击Keil魔术棒。

 点击debug,Use Simulator使用仿真器debug,将CPU DLL更改为SARMCM3.DLL,Parameter更改为-REMAP;Dialog DLL更改为DARMSTM.DLL,Parameter更改为-pSTM32F103C8。

进入debug模式。

 找到所需要跟踪的全局变量,右键选择Add 'SDA_bit' to...,然后选择Analyzer,SCL_bit同理。

跟踪的两个全局变量就会出现在逻辑分析仪中了。

 3. MPU读取加速度计和陀螺仪数据

mpu6050_regs.h

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif

mpu6050.h

#ifndef _MPU6050_H
#define _MPU6050_H
#include "stdint.h"
#include "stm32f1xx_hal.h"
// 向MPU6050寄存器写入数据的函数
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);

// 从MPU6050寄存器读取数据的函数
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

// 初始化MPU6050的函数
void MPU6050_Init(void);

// 获取MPU6050的设备ID的函数
uint8_t MPU6050_GetID(void);

// 获取MPU6050的加速度计和陀螺仪数据的函数
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);


#endif

 mpu6050.c

#include "mpu6050.h"
#include "Soft_I2C.h"
#include "mpu6050_regs.h"
#include "oled.h"
#include "string.h"
#include "stdio.h"
#define MPU6050_ADDRESS		0xD0 // 定义MPU6050的地址


// 向MPU6050寄存器写入数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	SOFT_I2C_Start(); // 开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS); // 发送设备地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(RegAddress); // 发送寄存器地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(Data); // 发送数据
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_Stop(); // 停止I2C通信
}

// 从MPU6050寄存器读取数据
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	SOFT_I2C_Start(); // 开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS); // 发送设备地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(RegAddress); // 发送寄存器地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	
	SOFT_I2C_Start(); // 重新开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS | 0x01); // 发送设备地址,并设置读操作位
	SOFT_I2C_ReceiveAck(); // 等待应答
	Data = SOFT_I2C_ReceiveByte(); // 接收数据
	SOFT_I2C_SendAck(0); // 发送应答信号
	SOFT_I2C_Stop(); // 停止I2C通信
	
	return Data; // 返回读取到的数据
}

// 初始化MPU6050
void MPU6050_Init(void)
{
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 设置电源管理寄存器
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 设置电源管理寄存器
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 设置采样率寄存器
	MPU6050_WriteReg(MPU6050_CONFIG, 0x03); // 设置配置寄存器
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 设置陀螺仪配置寄存器
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 设置加速度计配置寄存器
}

// 获取MPU6050的设备ID
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I); // 读取设备ID寄存器
}

// 获取MPU6050的加速度计和陀螺仪数据
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); // 读取加速度计X轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // 读取加速度计X轴低字节
	*AccX = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计X轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // 读取加速度计Y轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // 读取加速度计Y轴低字节
	*AccY = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计Y轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // 读取加速度计Z轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // 读取加速度计Z轴低字节
	*AccZ = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计Z轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // 读取陀螺仪X轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // 读取陀螺仪X轴低字节
	*GyroX = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪X轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // 读取陀螺仪Y轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // 读取陀螺仪Y轴低字节
	*GyroY = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪Y轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // 读取陀螺仪Z轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // 读取陀螺仪Z轴低字节
	*GyroZ = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪Z轴数据
}


 4. 移植DMP库,输出欧拉角

4.1 官网下载DMP库

官网网址:Home | TDK InvenSense | MEMS Sensor Solutions

依顺序点击Developers ——>Software Downloads ——>Smart Motion。

往页面最下面浏览,找到eMD 6.12点击下载。

4.2 添加文件

下载压缩包解压后,进入该目录

这六个文件就是移植所需的文件

 将以上文件添加到Keil项目中,将所有.h文件放入到项目工程目录下Core—>Inc中,将所有.c文件放入到Core—>Src中,并且在Keil中点击小方块

将两个.c文件加入到Core目录中。 

4.3 修改inv_mpu.h

 打开inv_mpu.h文件,添加两个宏定义

#define STM32_MPU6050
#define MPU6050

并在int_param_s结构体最后添加void (*cb)(void);

更改后的文件(右)与更改前的文件(左)对比

4.4 修改inv_mpu.c

添加I2C操作的头文件,本项目中相关的I2C操作封装在了mpu6050.h中。inv_mpu.c文件中有注释,提出了需要哪些函数,并明确了需要哪些参数,注释如下:

/* The following functions must be defined for this platform:
 * i2c_write(unsigned char slave_addr, unsigned char reg_addr,
 *      unsigned char length, unsigned char const *data)
 * i2c_read(unsigned char slave_addr, unsigned char reg_addr,
 *      unsigned char length, unsigned char *data)
 * delay_ms(unsigned long num_ms)
 * get_ms(unsigned long *count)
 * reg_int_cb(void (*cb)(void), unsigned char port, unsigned char pin)
 * labs(long x)
 * fabsf(float x)
 * min(int a, int b)
 */

需要i2c_write、i2c_read、delay_ms、get_ms、fabsf、min等函数,都要用自己的函数去替代他。

 已知i2c_write(unsigned char slave_addr, unsigned char reg_addr, unsigned char length, unsigned char const *data)需要这些参数,而上面软件模拟i2c的MPU6050_WriteReg和MPU6050_ReadReg并满足参数要求,需要修改,修改为:

mpu6050.h

#ifndef _MPU6050_H
#define _MPU6050_H
#include "stdint.h"
#include "stm32f1xx_hal.h"

int8_t MPU6050_WriteReg_HAL(
    unsigned char slave_addr,   // 设备地址(7位,例如0x68)
    unsigned char reg_addr,     // 寄存器地址(8位)
    unsigned char length,        // 数据长度
    unsigned char const *data    // 待写入的数据缓冲区
);
int8_t MPU6050_ReadReg_HAL(
    unsigned char slave_addr,   // 设备地址(7位)
    unsigned char reg_addr,     // 寄存器地址(8位)
    unsigned char length,       // 数据长度
    unsigned char *data         // 数据接收缓冲区
);// 向MPU6050寄存器写入数据的函数
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);

// 从MPU6050寄存器读取数据的函数
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

// 初始化MPU6050的函数
void MPU6050_Init(void);

// 获取MPU6050的设备ID的函数
uint8_t MPU6050_GetID(void);

// 获取MPU6050的加速度计和陀螺仪数据的函数
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif

mpu6050.c

#include "mpu6050.h"
#include "Soft_I2C.h"
#include "mpu6050_regs.h"
#include "oled.h"
#include "string.h"
#include "stdio.h"
#define MPU6050_ADDRESS		0xD0 // 定义MPU6050的地址

int8_t MPU6050_WriteReg_HAL(
    unsigned char slave_addr,   // 设备地址(7位,例如0x68)
    unsigned char reg_addr,     // 寄存器地址(8位)
    unsigned char length,       // 数据长度
    unsigned char const *data   // 待写入的数据缓冲区
) {
    SOFT_I2C_Start();

    // 发送设备地址(左移1位 + 写模式)
    SOFT_I2C_SendByte((slave_addr << 1) | 0x00); // 关键修改点
    if (SOFT_I2C_ReceiveAck() != 0) { // ACK=0表示成功
        SOFT_I2C_Stop();
        return -1; // 设备无响应
    }

    // 发送寄存器地址
    SOFT_I2C_SendByte(reg_addr);
    if (SOFT_I2C_ReceiveAck() != 0) {
        SOFT_I2C_Stop();
        return -2; // 寄存器地址ACK错误
    }

    // 发送数据
    for (unsigned char i = 0; i < length; i++) {
        SOFT_I2C_SendByte(data[i]);
        if (SOFT_I2C_ReceiveAck() != 0) {
            SOFT_I2C_Stop();
            return -3; // 数据ACK错误
        }
    }

    SOFT_I2C_Stop();
    return 0; // 成功
}
int8_t MPU6050_ReadReg_HAL(
    unsigned char slave_addr,   // 设备地址(7位)
    unsigned char reg_addr,     // 寄存器地址(8位)
    unsigned char length,       // 数据长度
    unsigned char *data         // 数据接收缓冲区
) {
    SOFT_I2C_Start();

    // 发送设备地址(左移1位 + 写模式)
    SOFT_I2C_SendByte((slave_addr << 1) | 0x00); // 关键修改点
    if (SOFT_I2C_ReceiveAck() != 0) { // ACK=0表示成功
        SOFT_I2C_Stop();
        return -1; // 设备无响应
    }

    // 发送寄存器地址
    SOFT_I2C_SendByte(reg_addr);
    if (SOFT_I2C_ReceiveAck() != 0) {
        SOFT_I2C_Stop();
        return -2; // 寄存器地址ACK错误
    }

    // 重新发送起始条件并切换为读模式
    SOFT_I2C_Start();
    SOFT_I2C_SendByte((slave_addr << 1) | 0x01); // 关键修改点
    if (SOFT_I2C_ReceiveAck() != 0) {
        SOFT_I2C_Stop();
        return -1; // 设备无响应
    }

    // 接收数据
    for (unsigned char i = 0; i < length; i++) {
        uint8_t ack = (i == length - 1) ? 1 : 0; // 最后一个字节发送NACK
        data[i] = SOFT_I2C_ReceiveByte();
        SOFT_I2C_SendAck(ack); // 发送ACK/NACK
    }

    SOFT_I2C_Stop();
    return 0; // 成功
}
// 向MPU6050寄存器写入数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	SOFT_I2C_Start(); // 开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS); // 发送设备地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(RegAddress); // 发送寄存器地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(Data); // 发送数据
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_Stop(); // 停止I2C通信
}

// 从MPU6050寄存器读取数据
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	SOFT_I2C_Start(); // 开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS); // 发送设备地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	SOFT_I2C_SendByte(RegAddress); // 发送寄存器地址
	SOFT_I2C_ReceiveAck(); // 等待应答
	
	SOFT_I2C_Start(); // 重新开始I2C通信
	SOFT_I2C_SendByte(MPU6050_ADDRESS | 0x01); // 发送设备地址,并设置读操作位
	SOFT_I2C_ReceiveAck(); // 等待应答
	Data = SOFT_I2C_ReceiveByte(); // 接收数据
	SOFT_I2C_SendAck(1); // 发送应答信号
	SOFT_I2C_Stop(); // 停止I2C通信
	
	return Data; // 返回读取到的数据
}

// 初始化MPU6050
void MPU6050_Init(void)
{
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 设置电源管理寄存器
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 设置电源管理寄存器
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 设置采样率寄存器
	MPU6050_WriteReg(MPU6050_CONFIG, 0x03); // 设置配置寄存器
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 设置陀螺仪配置寄存器
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 设置加速度计配置寄存器
}

// 获取MPU6050的设备ID
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I); // 读取设备ID寄存器
}

// 获取MPU6050的加速度计和陀螺仪数据
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); // 读取加速度计X轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // 读取加速度计X轴低字节
	*AccX = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计X轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // 读取加速度计Y轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // 读取加速度计Y轴低字节
	*AccY = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计Y轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // 读取加速度计Z轴高字节
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // 读取加速度计Z轴低字节
	*AccZ = (DataH << 8) | DataL; // 合并高字节和低字节,得到加速度计Z轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // 读取陀螺仪X轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // 读取陀螺仪X轴低字节
	*GyroX = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪X轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // 读取陀螺仪Y轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // 读取陀螺仪Y轴低字节
	*GyroY = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪Y轴数据
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // 读取陀螺仪Z轴高字节
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // 读取陀螺仪Z轴低字节
	*GyroZ = (DataH << 8) | DataL; // 合并高字节和低字节,得到陀螺仪Z轴数据
}

 使用自己定义的函数去替代注释中所提到的必须函数,结果如下:

4.5 修改inv_mpu_dmp_motion_driver.c

将文件修改成如下即可。

还需要将inv_mpu_dmp_motion_driver.c中的__no_operation();修改为__NOP();即可。

4.6 添加DMP相关函数

mpu6050_DMP.h

#ifndef _MPU6050_DMP_H
#define _MPU6050_DMP_H
#include "stdint.h"
#define ERROR_MPU_INIT      -1
#define ERROR_SET_SENSOR    -2
#define ERROR_CONFIG_FIFO   -3
#define ERROR_SET_RATE      -4
#define ERROR_LOAD_MOTION_DRIVER    -5 
#define ERROR_SET_ORIENTATION       -6
#define ERROR_ENABLE_FEATURE        -7
#define ERROR_SET_FIFO_RATE         -8
#define ERROR_SELF_TEST             -9
#define ERROR_DMP_STATE             -10

#define DEFAULT_MPU_HZ  100
#define Q30  1073741824.0f

int MPU6050_DMP_init(void);
int MPU6050_DMP_Get_Data(float *pitch, float *roll, float *yaw);

#endif

mpu6050_DMP.c


#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include "math.h"
#include "mpu6050.h"
#include "mpu6050_DMP.h"
#include "Soft_I2C.h"
#include "mpu6050_regs.h"
#include "oled.h"
#include "string.h"
#include "stdio.h"
/* The sensors can be mounted onto the board in any orientation. The mounting
 * matrix seen below tells the MPL how to rotate the raw data from thei
 * driver(s).
 * TODO: The following matrices refer to the configuration on an internal test
 * board at Invensense. If needed, please modify the matrices to match the
 * chip-to-body matrix for your particular set up.
 */
static signed char gyro_orientation[9] = {-1, 0, 0,
                                           0,-1, 0,
                                           0, 0, 1};

/* These next two functions converts the orientation matrix (see
 * gyro_orientation) to a scalar representation for use by the DMP.
 * NOTE: These functions are borrowed from Invensense's MPL.
 */
static unsigned short inv_row_2_scale(const signed char *row)
{
    unsigned short b;

    if (row[0] > 0)
        b = 0;
    else if (row[0] < 0)
        b = 4;
    else if (row[1] > 0)
        b = 1;
    else if (row[1] < 0)
        b = 5;
    else if (row[2] > 0)
        b = 2;
    else if (row[2] < 0)
        b = 6;
    else
        b = 7;      // error
    return b;
}

static unsigned short inv_orientation_matrix_to_scalar(
    const signed char *mtx)
{
    unsigned short scalar;

    /*
       XYZ  010_001_000 Identity Matrix
       XZY  001_010_000
       YXZ  010_000_001
       YZX  000_010_001
       ZXY  001_000_010
       ZYX  000_001_010
     */

    scalar = inv_row_2_scale(mtx);
    scalar |= inv_row_2_scale(mtx + 3) << 3;
    scalar |= inv_row_2_scale(mtx + 6) << 6;


    return scalar;
}

static int run_self_test(void)
{
    int result;
    long gyro[3], accel[3];

    result = mpu_run_self_test(gyro, accel);
    if (result == 0x3) {
        /* Test passed. We can trust the gyro data here, so let's push it down
         * to the DMP.
         */
        float sens;
        unsigned short accel_sens;
        mpu_get_gyro_sens(&sens);
        gyro[0] = (long)(gyro[0] * sens);
        gyro[1] = (long)(gyro[1] * sens);
        gyro[2] = (long)(gyro[2] * sens);
        dmp_set_gyro_bias(gyro);
        mpu_get_accel_sens(&accel_sens);
        accel[0] *= accel_sens;
        accel[1] *= accel_sens;
        accel[2] *= accel_sens;
        dmp_set_accel_bias(accel);
    } else {
        return -1;
    }

    return 0;
}

int MPU6050_DMP_init(void)
{
    int ret;
    struct int_param_s int_param;
    //mpu_init
    ret = mpu_init(&int_param);     
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "mpu_init :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_MPU_INIT;
    }
    //设置传感器
    ret = mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "mpu_set_sensors :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_SET_SENSOR;
    }
    //设置fifo
    ret = mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "mpu_configure_fifo :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_CONFIG_FIFO;
    }
    //设置采样率
    ret = mpu_set_sample_rate(DEFAULT_MPU_HZ);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "mpu_set_sample_rate :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_SET_RATE;
    }
    //加载DMP固件
    ret = dmp_load_motion_driver_firmware();
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "dmp_load_motion_driver_firmware :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_LOAD_MOTION_DRIVER;
    }
    //设置陀螺仪方向
    ret = dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "dmp_set_orientation :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_SET_ORIENTATION;
    }
    //设置DMP功能
    ret = dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP |
            DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | 
            DMP_FEATURE_SEND_CAL_GYRO | DMP_FEATURE_GYRO_CAL);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "dmp_enable_feature :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_ENABLE_FEATURE;
    }
    //设置输出速率
    ret = dmp_set_fifo_rate(DEFAULT_MPU_HZ);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "dmp_set_fifo_rate :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_SET_FIFO_RATE;
    }
//    //自检
//    ret = run_self_test();
//    if(ret != 0)
//    {
//		uint8_t dibuff[20];
//		sprintf((char *)dibuff, "run_self_test :%d     ", ret);
//		OLED_ShowString(0, 2, dibuff, 12);
//        return ERROR_SELF_TEST;
//    }
    //使能DMP
    ret = mpu_set_dmp_state(1);
    if(ret != 0)
    {
		uint8_t dibuff[20];
		sprintf((char *)dibuff, "mpu_set_dmp_state :%d     ", ret);
		OLED_ShowString(0, 2, dibuff, 12);
        return ERROR_DMP_STATE;
    }

    return 0;
}

	int MPU6050_DMP_Get_Data(float *pitch, float *roll, float *yaw)
	{
		float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;
		short gyro[3];
		short accel[3];
		long quat[4];
		unsigned long timestamp;
		short sensors;
		unsigned char more;
		if(dmp_read_fifo(gyro, accel, quat, &timestamp, &sensors, &more))
		{
			return -1;
		}

		if(sensors & INV_WXYZ_QUAT)
		{
			q0 = quat[0] / Q30;
			q1 = quat[1] / Q30;
			q2 = quat[2] / Q30;
			q3 = quat[3] / Q30;

			*pitch = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3; // pitch
			*roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3; // roll
			*yaw = atan2(2 * (q0 * q3 + q1 * q2), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3; // yaw
		}

		return 0;
	}

 自检函数run_self_test很容易出问题,注释即可。

main函数

int main(void)
{

    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_TIM3_Init();
    MX_USART3_UART_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
//    MPU6050_Init();
	MPU6050_DMP_init();
    ID = MPU6050_GetID();                 // 获取MPU6050传感器的ID并赋值给变量ID
    memset((void *)display_buf, 0, sizeof(display_buf));
    sprintf((char *)display_buf, "ID:%x ", ID);
    OLED_ShowString(0, 0, display_buf, 12);

    /* USER CODE END 2 */
    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
		HAL_Delay(100);

		MPU6050_DMP_Get_Data(&roll, &pitch, &yaw);
		memset((void *)display_buf, 0, sizeof(display_buf));
		sprintf((char *)display_buf, "roll :%06.4f     ", roll);
		OLED_ShowString(0, 2, display_buf, 12);
		memset((void *)display_buf, 0, sizeof(display_buf));
		sprintf((char *)display_buf, "pitch:%06.4f     ", pitch);
		OLED_ShowString(0, 3, display_buf, 12);
		memset((void *)display_buf, 0, sizeof(display_buf));
		sprintf((char *)display_buf, "yaw  :%06.4f     ", yaw);
		OLED_ShowString(0, 4, display_buf, 12);	
		
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}

 5. 参考

I2C通信协议时序_i2c时序-CSDN博客

分析一下到底是上升沿还是下降沿读写数据_读命令和写命令是下降-CSDN博客

keil中 debug调试问题_keil进入不了调试模式-CSDN博客

基于Keil软件实现硬件I2C读写MPU6050(江协科技HAL库)_i2c读写keli程序-CSDN博客

如何在官网下载MPU6050官方库_mpu6050 dmp库下载-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值