前言:
我想把值通过OLED显示出来,以及用串口把数据发送到VOFA+生成3D图像,所以会先讲解这部分的内容,大家可以根据目录直接跳到MPU6050部分。
直接跳到扩展部分!!封装的更完整,两种我亲测过都可以,看你们喜好。
注意!!本OLED的代码移植于江协科技的代码,如果有什么不理解的,欢迎大家观看江协科技的OLED课程,这里我只将如何使用。
1.OLED
配置PB8、PB9为开漏输出,上拉电阻
修改GPIO口输出函数,参数也要修改。
初始化OLED,写输出函数,更新OLED,即可显示。
2.串口
int my_printf(UART_HandleTypeDef *huart, const char *format, ...)
{
char buffer[512];
va_list arg;
int len;
va_start(arg, format);
len = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);
return len;
}
只需这段代码即可,串口重定向
1.MPU6050介绍
MPU6050是一种广泛应用于姿态检测的6轴运动处理传感器,由InvenSense公司研发生产。它集成了以下测量功能:
-
三轴加速度测量(X/Y/Z轴)
- 测量范围可配置(±2g、±4g、±8g、±16g)
- 典型应用:检测设备的倾斜角度、运动方向
- 示例:手机自动旋转屏幕的横竖屏切换功能
-
三轴角速度测量(陀螺仪)
- 测量范围可配置(±250°/s、±500°/s、±1000°/s、±2000°/s)
- 典型应用:检测设备的旋转速度
- 示例:无人机飞控系统的姿态稳定
该芯片采用I2C通信协议,工作电压3.3V-5V,内部还集成了:
- 数字运动处理器(DMP)
- 1024字节的FIFO缓冲区
- 温度传感器
- 内置时钟发生器
应用场景包括:
- 四轴飞行器姿态控制
- 智能手机运动检测
- 游戏控制器动作捕捉
- 智能手环计步功能
- 机器人平衡系统
典型工作流程:
- 初始化I2C通信
- 配置传感器量程
- 校准传感器(去除零点偏移)
- 读取原始数据
- 通过DMP或算法计算姿态角
- 应用数据处理(滤波、融合等)
2.读取MPU6050数据
1.写寄存器函数
HAL_I2C_Mem_Write(hi2c,DevAddress,MemAddress, MemAddSize, pData, Size,Timeout);
hi2c:操作句柄指针
DevAddress:从机设备地址,7位,但是填入时记得左移一位
MemAddress:要读取的寄存器地址
MemAddSize:地址长度
pData:发送数据
Size:数据长度
Timeout:超时参数
/**
* 函 数:MPU6050写寄存器
* 返 回 值:无
*/
void MPU6050_WriteReg(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t pData, uint16_t Size, uint32_t Timeout)
{
HAL_I2C_Mem_Write(hi2c,DevAddress,MemAddress, MemAddSize, &pData, Size,Timeout);
}
2.获取寄存器值函数
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
从一个特定的内存地址以阻塞模式读取一定量的数据
hi2c:操作句柄指针
DevAddress:从机地址
MemAddress:寄存器地址
MemAddSize:地址长度
pData:数据
Size:数据长度
Timeout:超时时间
int16_t MPU6050_ReadData(I2C_HandleTypeDef *hi2c,uint8_t RegAddress)
{
uint8_t Data[2];
HAL_I2C_Mem_Read(hi2c,MPU6050_ADDRESS,RegAddress,1,Data,2,HAL_MAX_DELAY);
return Data[0]<<8 | Data[1];
}
3.封装各数据
/* 函 数:MPU6050获取数据
* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 返 回 值:无
*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
*AccX = MPU6050_ReadData(&hi2c2,MPU6050_ACCEL_XOUT_H);
*AccY = MPU6050_ReadData(&hi2c2,MPU6050_ACCEL_YOUT_H);
*AccZ = MPU6050_ReadData(&hi2c2,MPU6050_ACCEL_ZOUT_H);
*GyroX = MPU6050_ReadData(&hi2c2,MPU6050_GYRO_XOUT_H);
*GyroY =MPU6050_ReadData(&hi2c2,MPU6050_GYRO_YOUT_H);
*GyroZ =MPU6050_ReadData(&hi2c2,MPU6050_GYRO_ZOUT_H);
}
4.获取设备ID
/**
* 函 数:MPU6050获取ID号
* 参 数:无
* 返 回 值:无
*/
void MPU6050_GetID(uint8_t *Data)
{
HAL_I2C_Mem_Read(&hi2c2,MPU6050_ADDRESS,MPU6050_WHO_AM_I,1,Data,1,HAL_MAX_DELAY);
}
5.初始化MPU6050
/**
* 函 数:MPU6050初始化
* 参 数:无
* 返 回 值:无
*/
void MPU6050_Init(void)
{
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_PWR_MGMT_1, 1,0x01,1,HAL_MAX_DELAY); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_PWR_MGMT_2, 1,0x00,1,HAL_MAX_DELAY); //电源管理寄存器2,保持默认值0,所有轴均不待机
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_SMPLRT_DIV, 1,0x09,1,HAL_MAX_DELAY); //采样率分频寄存器,配置采样率
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_CONFIG, 1,0x06,1,HAL_MAX_DELAY); //配置寄存器,配置DLPF
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_GYRO_CONFIG, 1,0x18,1,HAL_MAX_DELAY); //陀螺仪配置寄存器,选择满量程为±2000°/s
MPU6050_WriteReg(&hi2c2,MPU6050_ADDRESS,MPU6050_ACCEL_CONFIG, 1,0x18,1,HAL_MAX_DELAY);
}
电源管理器1
设备复位 | 睡眠 | 循环模式 | 无关位 | 温度传感器使能 | 选择时钟
电源管理器2
循环模式唤醒频率 | 每个轴的待机位
采样率分频
值越小越快
配置寄存器
外部同步 | 数字低通滤波器
陀螺仪配置寄存器
自测使能 | 满量程选择
加速度计配置寄存器
自测使能 | 满量程选择
6.定义变量
/* USER CODE BEGIN PV */
uint8_t IDData;
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量
/* USER CODE END PV */
7.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_USART1_UART_Init();
MX_I2C2_Init();
/* USER CODE BEGIN 2 */
OLED_Init();//��ʼ��OLED
MPU6050_Init();
MPU6050_GetID(&IDData);
OLED_ShowHexNum(0, 50, IDData, 2, OLED_8X16); /* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
OLED_ShowSignedNum(0, 0, AX, 4, OLED_8X16);
OLED_ShowSignedNum(0, 16, AY, 4, OLED_8X16);
OLED_ShowSignedNum(0, 33, AZ, 4, OLED_8X16);
OLED_ShowSignedNum(50, 0, GX, 4, OLED_8X16);
OLED_ShowSignedNum(50, 16, GY, 4, OLED_8X16);
OLED_ShowSignedNum(50, 33, GZ, 4, OLED_8X16);
OLED_Update();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
扩展—读取MPU6050升级版
mpu6050.c
#include "stm32f1xx_hal.h" // HAL库头文件
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 // I2C从机地址(含读写位)
#define I2C_TIMEOUT 100 // I2C操作超时时间(ms)
extern I2C_HandleTypeDef hi2c2; // 在外部定义I2C句柄(根据实际使用的I2C修改)
/**
* 函 数:MPU6050写寄存器
* 参 数:RegAddress 寄存器地址
* 参 数:Data 要写入的数据
* 返 回 值:HAL状态
*/
HAL_StatusTypeDef MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
return HAL_I2C_Mem_Write(&hi2c2, MPU6050_ADDRESS, RegAddress,
I2C_MEMADD_SIZE_8BIT, &Data, 1, I2C_TIMEOUT);
}
/**
* 函 数:MPU6050读寄存器
* 参 数:RegAddress 寄存器地址
* 返 回 值:读取的数据
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t data;
HAL_I2C_Mem_Read(&hi2c2, MPU6050_ADDRESS, RegAddress,
I2C_MEMADD_SIZE_8BIT, &data, 1, I2C_TIMEOUT);
return data;
}
/**
* 函 数:MPU6050初始化
* 参 数:hi2c I2C句柄指针
* 返 回 值:HAL状态
*/
HAL_StatusTypeDef MPU6050_Init(I2C_HandleTypeDef *hi2c)
{
/* 检查I2C是否就绪 */
if (hi2c->State != HAL_I2C_STATE_READY)
return HAL_ERROR;
/* 寄存器初始化(配置与原始代码相同) */
if (MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01) != HAL_OK) return HAL_ERROR;
if (MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00) != HAL_OK) return HAL_ERROR;
if (MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09) != HAL_OK) return HAL_ERROR;
if (MPU6050_WriteReg(MPU6050_CONFIG, 0x06) != HAL_OK) return HAL_ERROR;
if (MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18) != HAL_OK) return HAL_ERROR;
if (MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18) != HAL_OK) return HAL_ERROR;
return HAL_OK;
}
/**
* 函 数:MPU6050获取ID号
* 返 回 值:ID号
*/
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* 函 数:MPU6050获取数据(单次读取优化)
* 参 数:Acc/Gyro 各轴数据指针
* 返 回 值:HAL状态
*/
HAL_StatusTypeDef MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t data[14]; // 存储14字节数据(加速度XYZ+温度+陀螺仪XYZ)
/* 一次性读取所有传感器数据 */
if (HAL_I2C_Mem_Read(&hi2c2, MPU6050_ADDRESS, MPU6050_ACCEL_XOUT_H,
I2C_MEMADD_SIZE_8BIT, data, 14, I2C_TIMEOUT) != HAL_OK)
return HAL_ERROR;
/* 拼接数据(注意高位在前) */
*AccX = (int16_t)((data[0] << 8) | data[1]);
*AccY = (int16_t)((data[2] << 8) | data[3]);
*AccZ = (int16_t)((data[4] << 8) | data[5]);
// 跳过温度数据 data[6]和data[7]
*GyroX = (int16_t)((data[8] << 8) | data[9]);
*GyroY = (int16_t)((data[10] << 8) | data[11]);
*GyroZ = (int16_t)((data[12] << 8) | data[13]);
return HAL_OK;
}
mpu6050.h
#ifndef __MPU6050_H
#define __MPU6050_H
#include "mydefine.h"
HAL_StatusTypeDef MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
HAL_StatusTypeDef MPU6050_Init(I2C_HandleTypeDef *hi2c);
uint8_t MPU6050_GetID(void);
HAL_StatusTypeDef MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif
mpu6050_reg
#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
2.解算
缺点:
使用陀螺仪解算随着时间的推移漂移会很大(也就是随着时间的增加误差会增大),因为我们使用陀螺仪解算欧拉角是用递推的方式,误差会被累积
使用加速度计测量受噪声影响大,因为姿态传感器在抖动,我们以重力为参考系,在各个方向上的加速度分量会一直变化。主要还是因为加速度计不累加,所以不会有漂移。
前置概念理解
、
如图,把MPU6050放到小车上
欧拉角如下
yaw偏航角:绕z轴旋转所产生的角度
pitch俯仰角:绕x轴旋转所产生的角度
roll翻滚角:绕y轴旋转所产生的角度
如图,我们从小车的上方看下去,发现小车偏移后的角度为10°,这就是偏航角
①陀螺仪解算
我们知道陀螺仪测到的是角速度,角速度对时间积分就是角度,我们可以通过当前的角速度去预期下一时刻的角速度,比如我们测得当前z轴的角速度为20°/s,当前的偏航角为10°,那下一时刻是不是就可以预测是
10+20*t,t越小越准确,所以我们可以得到
yaw[k] = yaw[k-1] + gz * t
同理,我们可以得到
pitch[k] =pitch[k-1] + gx * t
roll[k] = roll[k-1] - gy * t(为什么有负号,请看前面的图)
/**
* 函 数:MPU6050获取数据(单次读取优化)
* 参 数:Acc/Gyro 各轴数据指针
* 返 回 值:HAL状态
*/
void MPU6050_proc(void)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
//计算欧拉角
yaw = yaw + GZ * sampling_time;
pitch = pitch + GX * sampling_time;
roll = roll + GY * sampling_time;
}
②加速度计解算
加速度计测量姿态(俯仰pitch和横滚roll)依赖于重力向量
使用重力的方向作为参考方向,绕Z轴旋转不会改变重力在X、Y、Z轴上的分量比例
因此无法检测偏航变化
总结:加速度计无法测量准确的yaw值
yaw = 0.0f;
pitch = atan2(AY,AZ) / 3.1415927f * 180.0f;
roll = atan2(AX,AZ) / 3.1415927f * 180.0f;
就这三行代码,通过各方向加速度的正切,使用反三角函数,求解出欧拉角。
重力向下,理应来说重力加速度不应该和重力一样向下吗,z轴是竖直向上,加速度计测得的应该为-g才对,其实我们加速度计测得的是惯性力。
③互补滤波
陀螺仪使用递推的方式计算欧拉角,所以会有漂移。
加速度计易受噪声干扰。
本质上是根据权重来减小 影响,还是没有完全消除,但是这个滤波算法适合初学者理解和应用。
漂移属于低频,噪声属于高频
yaw = yaw_g;
pitch = 0.95238* pitch_g + (1- 0.95238) * pitch_a;
roll = 0.95238* roll_g + (1- 0.95238) * roll_g;
阿法 = 0.95238,根据上面的公式计算。
④卡尔曼滤波
⑤dmp库——四元数
说明:内容可以比较精简,因为本人刚开始学,还望各位大佬纠正!后续内容等我学完在更新。