目录
4.5 修改inv_mpu_dmp_motion_driver.c
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 写时序
这是一个完整的写时序图,主机经过如下操作:
- 空闲状态:SDA和SCL都为高电平
- 主机发起起始信号(不管读写都是主机发起的):SCL高电平时,SDA由高电平向低电平转换
- 主机发送7位设备地址+1位读写位:7位设备地址位,加上1位读/写位(0表示写,1表示读)。如MPU6050的地址位0x68(这是8位表示的0x01101000),要修改为七位,左移一位所以为(0x11010000 = 0xD0)。所以此处发送0x11010001。
- 等待从机应答:主机释放SDA到高电平状态,从机通过拉低SDA来表示ACK,如果SDA仍是高电平则表示NACK。
- 主机发送寄存器地址:8位寄存器地址。
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 主机发送数据:8位数据
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 可以不断发送数据,重复执行7和8
- 主机发送中止信号:SCL高电平时,SDA由低电平向高电平转换
绿色部分为什么SDA有一个高电平状态,这是因为主机会将SDA拉高释放(这是软件I2C的操作,硬件I2C是主机释放SDA,由上拉电阻拉高SDA),从机才可以去拉低SDA,所以这里有一个短时间的高电平状态。
2.3 读时序
这是一个完整的读时序图,主机经过如下操作:
- 空闲状态:SDA和SCL都为高电平
- 主机发起起始信号:SCL高电平时,SDA由高电平向低电平转换
- 主机发送7位设备地址+1位读写位:7位设备地址位,加上写位0。
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 主机发送寄存器地址:8位寄存器地址。
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 主机再次发起起始信号 :SCL高电平时,SDA由高电平向低电平转换
- 主机发送7位设备地址+1位读写位:7位设备地址位,加上读位0。
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 主机接收数据:8位数据
- 等待从机应答:主机等待从设备发送应答信号,以确保从设备已成功接收到数据。
- 可以不断接收数据,重复执行10和11
- 主机发送中止信号: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, ×tamp, &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. 参考
分析一下到底是上升沿还是下降沿读写数据_读命令和写命令是下降-CSDN博客
keil中 debug调试问题_keil进入不了调试模式-CSDN博客