当前位置: 首页 > news >正文

【STM32】玩转IIC之驱动MPU6050及姿态解算

目录

前言

一.MPU6050模块介绍

 1.1MPU6050简介

1.2 MPU6050的引脚定义

1.3MPU6050寄存器解析

二.MPU6050驱动开发

2.1 配置寄存器

2.2对MPU6050寄存器进行读写

2.2.1 写入寄存器

2.2.2读取寄存器

2.3 初始化MPU6050

2.3.1 设置工作模式

2.3.2 配置采样率

2.3.3 启用传感器

2.4MPU6050数据读取

2.4.1 读取加速度数据

2.4.2 读取陀螺仪数据

2.4.3 数据格式转换

2.5 驱动代码实现

2.5.1 初始化函数

2.5.2 数据读取函数

3.5.2 数据读取函数

2.6 示例代码

三. 姿态解算

3.1姿态角与坐标系

3.1.1 姿态角的定义

3.1.2 坐标系的概念

3.2姿态解算算法

3.2.1四元数姿态解算实现

3.2.2四元数姿态解算算法

3.2.2.1初始化四元数

3.2.2.2读取传感器数据

3.2.2.3 加速度计归一化

3.2.2.4计算姿态误差

3.2.2.5四元数更新

3.2.2.6计算欧拉角

3.2.2.7主函数

 3.3注意事项

四. 应用与调试

4.1 数据可视化

4.2 常见问题与调试技巧

4.3 应用实例

五. 总结


前言

小伙伴们,大家好!已经迫不及待想分享新的技术了!今天,我们来搞点好玩的——MPU6050。这可是个好东西,一个集成了加速度计和陀螺仪的六轴传感器,能帮你轻松搞定姿态测量和运动检测。话不多说,直接开搞!!!干货满满,内容比较多,大家可以存下来慢慢看👀

在嵌入式系统和物联网的世界里,传感器就像是我们的“眼睛”和“耳朵”,能帮我们感知周围的一切。而MPU6050,就是这样一个超级厉害的传感器。它不仅能测加速度,还能测角速度,简直是姿态测量的神器!今天,我们就用STM32来驱动它,顺便搞搞姿态解算,让你的项目瞬间高大上起来。


一.MPU6050模块介绍

 1.1MPU6050简介

MPU6050是一款高性能的六轴运动传感器,集成了三轴加速度计和三轴陀螺仪。它能够测量加速度和角速度,广泛应用于姿态测量、运动检测、机器人控制等领域。MPU6050的主要特性如下:

  • 三轴加速度计:测量范围为±2g/±4g/±8g/±16g(可选 单位g为重力加速度)。

  • 三轴陀螺仪:测量范围为±250°/s/±500°/s/±1000°/s/±2000°/s(可选)。

  • 数字运动处理器(DMP):支持复杂的运动处理算法,如姿态解算。

  • IIC接口:支持标准IIC通信协议,易于与微控制器连接。

  • 低功耗:适合电池供电的便携式设备。

  • 高精度:能够提供高精度的加速度和角速度数据。

1.2 MPU6050的引脚定义

MPU6050模块通常具有以下引脚:

  • VCC:电源正极(3.3V或5V)。

  • GND:电源地。

  • SDA:IIC数据线。

  • SCL:IIC时钟线。

  • INT:中断输出引脚(可选)。

  • AD0:地址选择引脚,用于设置IIC设备地址。

1.3MPU6050寄存器解析

MPU6050通过IIC接口与微控制器通信,数据存储在内部寄存器中。下面将介绍我们完成功能所主要用寄存器:

  • PWR_MGMT_1:电源管理寄存器,用于设置传感器的工作模式。

  • SMPLRT_DIV:采样率分频寄存器,用于设置数据采样率。

  • CONFIG:配置寄存器,用于设置数字低通滤波器(DLPF)。

  • GYRO_CONFIG:陀螺仪配置寄存器,用于设置测量范围。

  • ACCEL_CONFIG:加速度计配置寄存器,用于设置测量范围。

  • ACCEL_XOUT_H/LACCEL_YOUT_H/LACCEL_ZOUT_H/L:加速度数据寄存器。

  • GYRO_XOUT_H/LGYRO_YOUT_H/LGYRO_ZOUT_H/L:陀螺仪数据寄存器。

寄存器地址功能描述
0x6BPWR_MGMT_1:电源管理寄存器,用于设备复位和时钟源设置。
0x6CPWR_MGMT_2:电源管理寄存器,用于设备睡眠模式控制。
0x1BGYRO_CONFIG:陀螺仪配置寄存器,用于设置陀螺仪的量程范围。
0x1CACCEL_CONFIG:加速度计配置寄存器,用于设置加速度计的量程范围。
0x1ACONFIG:配置寄存器,用于设置数字低通滤波器(DLPF)。
0x19SMPLRT_DIV:采样率分频寄存器,用于设置数据采样率。

0x3B-0x40

加速度计和陀螺仪数据寄存器,分别存储加速度和角速度的原始数据。

通过配置这些寄存器,可以实现对MPU6050的初始化和数据读取。

二.MPU6050驱动开发

2.1 配置寄存器

配置寄存器用于设置MPU6050的工作模式、采样率、滤波器等参数。以下是几个常用配置寄存器的详细说明:

  • PWR_MGMT_1(0x6B)

    • 位7:设备复位位,写入0x80复位设备。

    • 位0-2:时钟源选择,通常设置为0x00,使用内部时钟。

  • GYRO_CONFIG(0x1B)

    • 位3-0:陀螺仪量程选择,可选范围为±250°/s、±500°/s、±1000°/s、±2000°/s。

  • ACCEL_CONFIG(0x1C)

    • 位3-0:加速度计量程选择,可选范围为±2g、±4g、±8g、±16g。

  • SMPLRT_DIV(0x19)

    • 位7-0:采样率分频值,设置为0时,采样率最高。

对于寄存器这里我另外写了一个头文件包含了对MPU6050所有的寄存器地址进行声明,方便调用,简单明了!

#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.2对MPU6050寄存器进行读写

2.2.1 写入寄存器

通过IIC接口向MPU6050的寄存器写入数据,可以配置传感器的工作模式、量程、采样率等参数。以下是写入寄存器的代码实现:

#define MPU6050_Addr 0xD0void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) {IIC_Start();IIC_SendByte(MPU6050_Addr); // 发送设备地址(写操作)IIC_ReceiveAck();IIC_SendByte(RegAddress); // 发送寄存器地址IIC_ReceiveAck();IIC_SendByte(Data); // 发送数据IIC_ReceiveAck();IIC_Stop();
}

2.2.2读取寄存器

通过IIC接口从MPU6050的寄存器读取数据,可以获取传感器的配置状态或测量数据。以下是读取寄存器的代码实现:

uint8_t MPU6050_ReadReg(uint8_t RegAddress) {uint8_t Data;IIC_Start();IIC_SendByte(MPU6050_Addr); // 发送设备地址(写操作)IIC_ReceiveAck();IIC_SendByte(RegAddress); // 发送寄存器地址IIC_ReceiveAck();IIC_Start();IIC_SendByte(MPU6050_Addr | 0x01); // 发送设备地址(读操作)IIC_ReceiveAck();Data = IIC_ReceiveByte(); // 读取数据IIC_SendAck(1); // 发送NACK,结束读取IIC_Stop();return Data;
}

2.3 初始化MPU6050

2.3.1 设置工作模式

通过PWR_MGMT_1寄存器设置MPU6050的工作模式。通常在初始化时,先复位设备,然后将其设置为正常工作模式。

void MPU6050_SetPowerMode(uint8_t mode) 
{MPU6050_WriteReg(MPU6050_PWR_MGMT_1, mode);
}

2.3.2 配置采样率

通过SMPLRT_DIV寄存器设置采样率分频值,从而控制数据的采样频率。

void MPU6050_SetSampleRate(uint8_t div) 
{MPU6050_WriteReg(MPU6050_SMPLRT_DIV, div);
}

2.3.3 启用传感器

通过PWR_MGMT_1寄存器启用加速度计和陀螺仪。

void MPU6050_EnableSensors(void) 
{MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); // 启用设备
}

2.4MPU6050数据读取

2.4.1 读取加速度数据

加速度数据存储在寄存器0x3B0x40中,分别表示加速度计的X、Y、Z轴数据。

void MPU6050_ReadAccel(int16_t *ax, int16_t *ay, int16_t *az) 
{uint8_t buffer[6]; // 创建一个6字节的缓冲区,用于存储从MPU6050读取的加速度数据// 逐个读取加速度计的X、Y、Z轴的高字节和低字节数据buffer[0] = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); // X轴加速度数据的高字节buffer[1] = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // X轴加速度数据的低字节buffer[2] = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // Y轴加速度数据的高字节buffer[3] = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // Y轴加速度数据的低字节buffer[4] = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // Z轴加速度数据的高字节buffer[5] = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // Z轴加速度数据的低字节// 将高字节和低字节组合成完整的16位加速度数据*ax = (buffer[0] << 8) | buffer[1]; // 将X轴的高字节左移8位后与低字节进行按位或操作,得到完整的X轴加速度数据*ay = (buffer[2] << 8) | buffer[3]; // 同理,得到Y轴加速度数据*az = (buffer[4] << 8) | buffer[5]; // 同理,得到Z轴加速度数据
}

2.4.2 读取陀螺仪数据

陀螺仪数据存储在寄存器0x430x48中,分别表示陀螺仪的X、Y、Z轴数据。

void MPU6050_ReadGyro(int16_t *gx, int16_t *gy, int16_t *gz) 
{uint8_t buffer[6]; // 创建一个6字节的缓冲区,用于存储从MPU6050读取的陀螺仪数据// 逐个读取陀螺仪的X、Y、Z轴的高字节和低字节数据buffer[0] = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // X轴陀螺仪数据的高字节buffer[1] = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // X轴陀螺仪数据的低字节buffer[2] = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // Y轴陀螺仪数据的高字节buffer[3] = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // Y轴陀螺仪数据的低字节buffer[4] = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // Z轴陀螺仪数据的高字节buffer[5] = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // Z轴陀螺仪数据的低字节// 将高字节和低字节组合成完整的16位陀螺仪数据*gx = (buffer[0] << 8) | buffer[1]; // 将X轴的高字节左移8位后与低字节进行按位或操作,得到完整的X轴陀螺仪数据*gy = (buffer[2] << 8) | buffer[3]; // 同理,得到Y轴陀螺仪数据*gz = (buffer[4] << 8) | buffer[5]; // 同理,得到Z轴陀螺仪数据
}

2.4.3 数据格式转换

将读取到的原始数据转换为实际的物理量。

float MPU6050_ConvertAccel(int16_t value, uint8_t range) 
{float factor; // 用于存储转换因子// 根据加速度计的量程选择合适的转换因子switch (range) {case 0x00: factor = 16384.0; break; // ±2g,转换因子为16384case 0x08: factor = 8192.0; break;  // ±4g,转换因子为8192case 0x10: factor = 4096.0; break;  // ±8g,转换因子为4096case 0x18: factor = 1365.3; break;  // ±16g,转换因子为1365.3default: factor = 16384.0; break;    // 默认量程为±2g}// 将原始加速度值转换为g单位return value / factor;
}float MPU6050_ConvertGyro(int16_t value, uint8_t range) 
{float factor; // 用于存储转换因子// 根据陀螺仪的量程选择合适的转换因子switch (range) {case 0x00: factor = 131.0; break;  // ±250°/s,转换因子为131case 0x08: factor = 65.5; break;   // ±500°/s,转换因子为65.5case 0x10: factor = 32.8; break;   // ±1000°/s,转换因子为32.8case 0x18: factor = 16.4; break;   // ±2000°/s,转换因子为16.4default: factor = 131.0; break;     // 默认量程为±250°/s}// 将原始陀螺仪值转换为°/s单位return value / factor;
}

2.5 驱动代码实现

2.5.1 初始化函数

初始化MPU6050,设置工作模式、采样率和传感器量程。

void MPU6050_Init(void) 
{// 复位MPU6050MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x80);MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00);// 设置陀螺计量程为±250°/sMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x00);// 设置加速度计量程为±2gMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x00);// 设置采样率分频值为0MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x00);
}

2.5.2 数据读取函数

读取加速度和陀螺仪数据,并转换为实际的物理量。

void MPU6050_ReadSensors(float *ax, float *ay, float *az, float *gx, float *gy, float *gz) 
{int16_t raw_ax, raw_ay, raw_az;  // 用于存储原始加速度数据int16_t raw_gx, raw_gy, raw_gz;  // 用于存储原始陀螺仪数据// 读取加速度计的原始数据MPU6050_ReadAccel(&raw_ax, &raw_ay, &raw_az);// 读取陀螺仪的原始数据MPU6050_ReadGyro(&raw_gx, &raw_gy, &raw_gz);// 将加速度计的原始数据转换为浮点数形式(单位:g)*ax = MPU6050_ConvertAccel(raw_ax, 0x00); // ±2g量程*ay = MPU6050_ConvertAccel(raw_ay, 0x00); // ±2g量程*az = MPU6050_ConvertAccel(raw_az, 0x00); // ±2g量程// 将陀螺仪的原始数据转换为浮点数形式(单位:°/s)*gx = MPU6050_ConvertGyro(raw_gx, 0x00); // ±250°/s量程*gy = MPU6050_ConvertGyro(raw_gy, 0x00); // ±250°/s量程*gz = MPU6050_ConvertGyro(raw_gz, 0x00); // ±250°/s量程
}

2.6 示例代码

以下是一个完整的示例代码,展示如何初始化MPU6050并读取加速度和陀螺仪数据:

#include "mpu6050.h"
#include "iic.h"int main(void) {// 初始化IIC接口IIC_Init();// 初始化MPU6050MPU6050_Init();while (1) {float ax, ay, az;float gx, gy, gz;// 读取传感器数据MPU6050_ReadSensors(&ax, &ay, &az, &gx, &gy, &gz);// 打印数据printf("Accel: X=%.2fg, Y=%.2fg, Z=%.2fg\r\n", ax, ay, az);printf("Gyro: X=%.2f°/s, Y=%.2f°/s, Z=%.2f°/s\r\n", gx, gy, gz);// 延时delay_ms(1000);}
}

三. 姿态解算

为什么用姿态解算计算设备姿态角👀因为计算设备姿态角上一般所选用的惯性传感器,都是mems器件,精度相对较差,同时陀螺仪、加速度计、地磁计单个传感器无法得到满意的姿态角信息,所以需要一些融合算法,进行姿态估计。

姿态解算的目标是通过融合MPU6050的加速度计和陀螺仪数据,得到一个准确的姿态估计的👉即计算出设备在三维空间(坐标系)中的姿态角(如俯仰角、横滚角和偏航角)。姿态角是描述物体在空间中旋转状态的三个角度,通常用于飞控、机器人、VR设备等领域。

3.1姿态角与坐标系

3.1.1 姿态角的定义

姿态角描述了设备的机体坐标系相对于惯性坐标系的旋转关系。

  • 俯仰角(Pitch):表示设备绕X轴的旋转角度,范围为-90°到+90°。俯仰角描述了设备相对于水平面的上下倾斜程度。

  • 横滚角(Roll):表示设备绕Y轴的旋转角度,范围为-180°到+180°。横滚角描述了设备相对于水平面的左右倾斜程度。

  • 偏航角(Yaw):表示设备绕Z轴的旋转角度,范围为-180°到+180°。偏航角描述了设备在水平面上的旋转方向。

注:在姿态解算中,姿态角的定义通常是以惯性坐标系(世界坐标系)作为参考的。惯性坐标系是一个固定在空间中的参考系,通常以地球的重力方向为基准。

3.1.2 坐标系的概念

在姿态解算中,坐标系的选择至关重要。通常使用以下两种坐标系:

  • 惯性坐标系(世界坐标系):固定在空间中的坐标系,通常以地球的重力方向为Z轴,水平面为XY平面。

  • 机体坐标系(传感器坐标系):固定在设备上的坐标系,通常以设备的几何中心为原点,X轴指向设备的前方向,Y轴指向设备的右方向,Z轴指向设备的下方向。

姿态角描述了机体坐标系相对于惯性坐标系的旋转关系。通过姿态解算,可以将机体坐标系下的测量数据转换到惯性坐标系下,从而实现对设备姿态的描述。➡️抽象来说:姿态解算是“机体坐标系”与“惯性坐标系”之间的转换关系!

3.2姿态解算算法

在姿态解算中,姿态可以通过多种方式表示,常见的有欧拉角四元数旋转矩阵每种表示方法都有其特点和应用场景,且每个姿态表示方法可以相互转换。

在姿态解算中,常见的算法包括:

  1. 四元数法:通过四元数融合加速度计和陀螺仪数据,计算出设备的姿态。

  2. 互补滤波法:结合加速度计和陀螺仪数据,通过互补滤波器修正姿态角。

  3. 卡尔曼滤波法:一种更高级的滤波算法,能够更精确地融合传感器数据,适用于复杂的姿态解算

关于上面的所提到的每一个姿态表示方法和姿态解算算法并没有逐个的展开讲解,如果大家感兴趣的话可以自行查阅,这里我们主要讲四元数法应用!

3.2.1四元数姿态解算实现

四元数是一种用于表示三维旋转的数学工具,由四个分量组成:q0​,q1​,q2​,q3​。其中,q0​ 是实部,q1​,q2​,q3​ 是虚部。四元数可以表示为: q=q0​+q1​i+q2​j+q3​k

相比欧拉角,它具有更好的数值稳定性和抗奇异性的特点,避免欧拉角中的“万向锁”问题。以下是四元数姿态解算的基本步骤:

  1. 初始化四元数:初始状态下,四元数设为单位四元数,即 q0​=1,q1​=q2​=q3​=0。

  2. 读取传感器数据:从MPU6050读取加速度计和陀螺仪的原始数据,并将陀螺仪数据转换为弧度。

  3. 加速度计归一化:将加速度计数据归一化,以提取重力方向。

  4. 计算姿态误差:通过加速度计和四元数推导出的重力方向的差异,计算姿态误差。

  5. 互补滤波:将姿态误差反馈到四元数更新中,结合陀螺仪数据,通过互补滤波器修正四元数。

  6. 四元数更新:通过微分方程更新四元数,并进行归一化处理。

  7. 计算欧拉角:将四元数转换为欧拉角,得到俯仰角、横滚角和偏航角。

3.2.2四元数姿态解算算法

3.2.2.1初始化四元数

初始化四元数为单位四元数,表示初始姿态为零

//定义一个关于四元数的结构体变量
typedef struct 
{float q0, q1, q2, q3;
} Quaternion;Quaternion q = {1.0f, 0.0f, 0.0f, 0.0f};  // 初始化四元数
3.2.2.2读取传感器数据

从MPU6050读取加速度计和陀螺仪的原始数据,并将陀螺仪数据转换为弧度。

void MPU6050_ReadSensors(float *ax, float *ay, float *az, float *gx, float *gy, float *gz)
{int16_t raw_ax, raw_ay, raw_az;int16_t raw_gx, raw_gy, raw_gz;MPU6050_ReadAccel(&raw_ax, &raw_ay, &raw_az);MPU6050_ReadGyro(&raw_gx, &raw_gy, &raw_gz);*ax = MPU6050_ConvertAccel(raw_ax, 0x00);  // ±2g*ay = MPU6050_ConvertAccel(raw_ay, 0x00);*az = MPU6050_ConvertAccel(raw_az, 0x00);*gx = MPU6050_ConvertGyro(raw_gx, 0x00);  // ±250°/s*gy = MPU6050_ConvertGyro(raw_gy, 0x00);*gz = MPU6050_ConvertGyro(raw_gz, 0x00);
}
3.2.2.3 加速度计归一化

将加速度计数据归一化,提取重力方向

void NormalizeAccel(float *ax, float *ay, float *az) 
{float norm = sqrt(*ax * *ax + *ay * *ay + *az * *az);*ax /= norm;*ay /= norm;*az /= norm;
}
3.2.2.4计算姿态误差

通过加速度计和四元数推导出的重力方向的差异,计算姿态误差。

void ComputeError(float ax, float ay, float az, float *error_x, float *error_y, float *error_z) 
{float gravity_x = 2 * (q.q1 * q.q3 - q.q0 * q.q2);float gravity_y = 2 * (q.q0 * q.q1 + q.q2 * q.q3);float gravity_z = 1 - 2 * (q.q1 * q.q1 + q.q2 * q.q2);*error_x = (ay * gravity_z - az * gravity_y);*error_y = (az * gravity_x - ax * gravity_z);*error_z = (ax * gravity_y - ay * gravity_x);
}
3.2.2.5四元数更新

结合陀螺仪数据,通过微分方程更新四元数,并进行归一化处理,避免数值误差会越积越大。

void UpdateQuaternion(float gx, float gy, float gz, float error_x, float error_y, float error_z, float dt) 
{float Kp = 0.5f;  // 互补滤波系数float q0_dot = -q.q1 * gx - q.q2 * gy - q.q3 * gz;float q1_dot = q.q0 * gx - q.q3 * gy + q.q2 * gz;float q2_dot = q.q3 * gx + q.q0 * gy - q.q1 * gz;float q3_dot = -q.q2 * gx + q.q1 * gy + q.q0 * gz;q.q0 += (q0_dot + Kp * error_x) * dt;q.q1 += (q1_dot + Kp * error_y) * dt;q.q2 += (q2_dot + Kp * error_z) * dt;q.q3 += (q3_dot) * dt;// 归一化四元数,避免数值误差会越积越大float norm = sqrt(q.q0 * q.q0 + q.q1 * q.q1 + q.q2 * q.q2 + q.q3 * q.q3);q.q0 /= norm;q.q1 /= norm;q.q2 /= norm;q.q3 /= norm;
}
3.2.2.6计算欧拉角

将四元数转换为欧拉角,得到俯仰角、横滚角和偏航角。

void ComputeEulerAngles(float *pitch, float *roll, float *yaw) 
{*roll = atan2(2 * (q.q2 * q.q3 + q.q0 * q.q1), q.q0 * q.q0 - q.q1 * q.q1 - q.q2 * q.q2 + q.q3 * q.q3);*pitch = asin(-2 * (q.q1 * q.q3 - q.q0 * q.q2));*yaw = atan2(2 * (q.q1 * q.q2 + q.q0 * q.q3), q.q0 * q.q0 + q.q1 * q.q1 - q.q2 * q.q2 - q.q3 * q.q3);*roll *= RtA;  // 弧度转角度*pitch *= RtA;*yaw *= RtA;
}
3.2.2.7主函数

将上述步骤整合到主函数中,实现完整的姿态解算。

#include "mpu6050.h"
#include "math.h"#define PI 3.1415926535f
#define RtA 57.2957795f  // 弧度转角度
#define AtR 0.0174532925f  // 角度转弧度typedef struct 
{float q0, q1, q2, q3;
} Quaternion;Quaternion q = {1.0f, 0.0f, 0.0f, 0.0f};  // 初始化四元数void GetAngles(float *pitch, float *roll, float *yaw, float dt) 
{float ax, ay, az, gx, gy, gz;MPU6050_ReadSensors(&ax, &ay, &az, &gx, &gy, &gz);NormalizeAccel(&ax, &ay, &az);float error_x, error_y, error_z;ComputeError(ax, ay, az, &error_x, &error_y, &error_z);UpdateQuaternion(gx, gy, gz, error_x, error_y, error_z, dt);ComputeEulerAngles(pitch, roll, yaw);
}int main(void) 
{// 初始化MPU6050MPU6050_Init();while (1) {float pitch, roll, yaw;GetAngles(&pitch, &roll, &yaw, 0.01f);  // 假设采样间隔为10ms,保证一定的实时性// 打印姿态角printf("Pitch: %.2f, Roll: %.2f, Yaw: %.2f\r\n", pitch, roll, yaw);// 延时delay_ms(10);}
}

 3.3注意事项

小伙伴们,在动手实现姿态解算的时候,有几个要点得留心:

  1. 硬件连接要稳:MPU6050和STM32的IIC通信线路(SDA和SCL)一定要接对,不然通信可能会出问题。

  2. 传感器得校准:加速度计和陀螺仪用之前最好校准一下,不然数据可能会不准。静止状态下多读几次数据,算个平均值,这样能更准。

  3. 数据别急躁:加速度计和陀螺仪的数据可能会有点“毛躁”,用低通滤波器或者滑动平均滤波器给它“梳梳毛”,数据会更平滑。

  4. 算法参数要调:四元数解算里的互补滤波器增益(比如Kp)很重要,得根据你的实际需求调整,不然解算出来的姿态可能会“飘”。

  5. 代码要细心:四元数更新后一定要归一化,不然数值误差会越积越大。还有,采样频率别太低,不然实时性会受影响。

  6. 调试别慌:遇到数据跳变或者通信失败,别急,先检查硬件连接,再看看代码逻辑。慢慢来,总能找到问题所在。

总之,技术这事儿,耐心和细心是关键。希望这些小提示能帮到你,祝你顺利搞定姿态解算!

这里我只是带大家简单理解如何操作实现的,如果大家有不理解的可以来问,欢迎大家评论,后面我可能会为大家再出一篇关于姿态解算详细讲解😄

四. 应用与调试

4.1 数据可视化

  • 串口打印:通过串口打印姿态角(俯仰、横滚、偏航),方便实时观察。

  • 上位机软件:用LabVIEW、MATLAB等工具接收串口数据,绘制姿态变化曲线。

  • 显示屏:用LCD或LED显示姿态角,直观展示当前姿态。

4.2 常见问题与调试技巧

  • 数据跳变:可能是传感器干扰或算法问题。检查硬件连接,增加滤波算法。

  • 数据不准确:可能是传感器未校准或参数设置不当。校准传感器,调整算法参数。

  • 通信失败:可能是硬件连接或IIC配置错误。检查线路,确保通信参数正确。

4.3 应用实例

  • 姿态测量:用于无人机、机器人等设备的姿态控制。

  • 运动检测:实现计步器、手势识别等功能。

  • VR设备:跟踪头部运动,提供沉浸式体验。

五. 总结

不知不觉,我们已经走到了文章的尾声,说实话,这过程里,我也是边写边学,收获满满。通过这篇文章的介绍,你就可以掌握了如何使用STM32通过IIC接口驱动MPU6050,并实现基于四元数的姿态解算。姿态解算的结果可以通过多种方式可视化,并应用于实际项目中。在调试过程中,注意数据异常处理和精度优化,确保姿态解算的准确性和稳定性。

最后,姿态解算这事儿,还有很多可以深挖的。比如,要是再加个磁力计,精度说不定还能再上个台阶。或者,把解算结果用到无人机、机器人上,让它们动起来更稳当。这些,就留给大家去探索啦!希望这篇文章能给你带来些启发,要是觉得有用,别忘了点赞哦!下次见!

相关文章:

【STM32】玩转IIC之驱动MPU6050及姿态解算

目录 前言 一.MPU6050模块介绍 1.1MPU6050简介 1.2 MPU6050的引脚定义 1.3MPU6050寄存器解析 二.MPU6050驱动开发 2.1 配置寄存器 2.2对MPU6050寄存器进行读写 2.2.1 写入寄存器 2.2.2读取寄存器 2.3 初始化MPU6050 2.3.1 设置工作模式 2.3.2 配置采样率 2.3.3 启…...

《张一鸣,创业心路与算法思维》

张一鸣&#xff0c;多年如一日的阅读习惯。 爱读人物传记&#xff0c;称教科书式人类知识最浓缩的书&#xff0c;也爱看心理学&#xff0c;创业以及商业管理类的书。 冯仑&#xff0c;王石&#xff0c;联想&#xff0c;杰克韦尔奇&#xff0c;思科。 《乔布斯传》《埃隆马斯…...

深入浅出:UniApp 从入门到精通全指南

https://juejin.cn/post/7440119937644101684 uni-app官网 uniapp安卓离线打包流程_uniapp离线打包-CSDN博客 本文是关于 UniApp 从入门到精通的全指南&#xff0c;涵盖基础入门&#xff08;环境搭建、创建项目、项目结构、编写运行&#xff09;、核心概念与进阶知识&#x…...

低空经济-飞行数据平台 搭建可行方案

搭建一个飞行数据平台是低空经济中至关重要的一环,它能够实现对飞行器的实时监控、数据分析、路径优化以及安全管理。以下是搭建飞行数据平台的详细步骤和技术方案: 一、平台的核心功能 实时监控: 实时获取飞行器的位置、速度、高度、电池状态等数据。提供可视化界面,展示飞…...

【四.RAG技术与应用】【12.阿里云百炼应用(下):RAG的云端优化与扩展】

在上一篇文章中,我们聊了如何通过阿里云百炼平台快速搭建一个RAG(检索增强生成)应用,实现文档智能问答、知识库管理等基础能力。今天咱们继续深入,聚焦两个核心问题:如何通过云端技术优化RAG的效果,以及如何扩展RAG的应用边界。文章会穿插实战案例,手把手带你踩坑避雷。…...

3-7 WPS JS宏 工作表移动复制实例-2(多工作簿的多工作表合并)学习笔记

************************************************************************************************************** 点击进入 -我要自学网-国内领先的专业视频教程学习网站 *******************************************************************************************…...

【智能机器人开发全流程:硬件选型、软件架构与ROS实战,打造高效机器人系统】

文章目录 1. 硬件层设计(1) 传感器选型(2) 计算平台 2. 软件架构设计(1) 核心模块划分(2) 通信框架 3. 关键实现步骤(1) 硬件-软件接口开发(2) SLAM与导航实现(3) 仿真与测试 4. 典型框架示例基于ROS的移动机器人分层架构 5. 优化与扩展6. 开源项目参考 1. 硬件层设计 (1) 传感…...

【CSS—前端快速入门】CSS 选择器

CSS 1. CSS介绍 1.1 什么是CSS? CSS(Cascading Style Sheet)&#xff0c;层叠样式表&#xff0c;用于控制页面的样式&#xff1b; CSS 能够对网页中元素位置的排版进行像素级精确控制&#xff0c;实现美化页面的效果&#xff1b;能够做到页面的样式和 结构分离&#xff1b; 1…...

二、QT和驱动模块实现智能家居-----5、通过QT控制LED

在QT界面&#xff0c;我们要实现点击“LED”按钮就可以控制板子上的LED。LED接线图如下&#xff1a; 在Linux 系统里&#xff0c;我们可以使用2种方法去操作上面的LED&#xff1a; ① 使用GPIO SYSFS系统&#xff1a;这需要一定的硬件知识&#xff0c;需要设置引脚的方向、数值…...

在UI设计中使用自定义控件

生成&#xff1a; 自定义控件完成&#xff1a; 看控件的父类是谁&#xff1a; 在想使用的窗口中&#xff1a; 拖动一个与他父类相同类型的控件&#xff1a; 点击控件右键 这样就是你自定义的控件了 运行后&#xff1a; //点击QSpinBox&#xff0c;滑块跟着移动void (QSpinBox::…...

docker 离线安装redis(离线)

docker离线安装redis时&#xff0c;我找了挺多资料都是需要先在另一台能联网的机器上先下载镜像&#xff0c;然后移动内网中的docker服务器进行部署&#xff0c;我也是这么操作的&#xff0c;但是必须拥有能上网的docker环境才能下载&#xff0c;我在github上找到了一种可以直接…...

利用python实现对Excel文件中数据元组的自定义排序

问题引入&#xff1a; 假设你是一个浙江省水果超市的老板&#xff0c;统筹11个下辖地市的水果产量。假设11个地市生产的水果包括&#xff1a;苹果、香蕉和西瓜。你如何快速得到某种水果产量突出&#xff08;排名前几&#xff09;的地市&#xff1f;产量落后&#xff08;排名后…...

基于 Python 深度学习的电影评论情感分析可视化系统(2.0 全新升级)

基于 Python 深度学习的电影评论情感分析可视化系统&#xff0c;基于 Flask 深度学习&#xff0c;构建了一个 影评情感分析系统&#xff0c;能够 自动分析影评、计算情感趋势 并 可视化展示&#xff0c;对于电影行业具有重要参考价值&#xff01; 基于 Python 深度学习的电影评…...

【MYSQL数据库异常处理】执行SQL语句报超时异常

MYSQL执行SQL语句异常&#xff1a;The last packet successfully received from the server was 100,107 milliseconds ago. The last packet sent successfully to the server was 100,101 milliseconds ago. 这个错误表明 MySQL 服务器与 JDBC 连接之间的通信超时了。通常由…...

docker拉取失败

备份原始配置文件 sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 清理或修复 daemon.json 文件 sudo nano /etc/docker/daemon.json 删除 文件中的所有内容&#xff0c;确保文件为空。 cv下面这个文件内容 { "registry-mirrors": [ &…...

Linux的一些配置(网络建设与运维)

for i in 的指令使用集 传输内容指令 for i in {1..7};do ssh 10.4.220.10${i} "指令";done 传输文件指令 for i in {1..7};do scp 文件 root10.4.220.10${i}:文件位置;done DNS循环内容指令 for i in {1..7};do echo "linux$i A 10.4.220.10$i" >> …...

嵌入式L6计算机网络

Telnet不加密 socket是应用层和下面的内核...

大型语言模型演变之路:从Transformer到DeepSeek-R1

大型语言模型的崛起被认为是人工智能领域的一次革命&#xff0c;从2017年Transformer架构的引入开始&#xff0c;到2025年DeepSeek-R1的推出&#xff0c;每一步都在不断改变着人机交互的方式&#xff0c;推动着学术界与产业界的深度融合。 1. Transformer的引领&#xff08;201…...

Idea配置注释模板

一、配置类注释模板 打开IDEA&#xff0c;打开settings(快捷键&#xff1a;Ctrl Alt s)&#xff0c;选择Editor&#xff0c;找到File and Code Templates 这里以设置class文件为例&#xff0c;点击Class&#xff0c;在右侧配置以下内容 #if (${PACKAGE_NAME} && $…...

通过计费集成和警报监控 Elasticsearch Service 成本

作者&#xff1a;来自 Elastic Alexis Charveriat 使用 Elasticsearch 服务计费集成来跟踪、定制和提醒 Elasticsearch 服务费用。 监控和管理你的Elasticsearch服务&#xff08;ESS&#xff09;使用情况和成本对高效运营至关重要。 Elasticsearch服务计费集成提供了一种简化的…...

50.xilinx fir滤波器系数重加载如何控制

&#xff0c; 注意:matlab量化后的滤波器系数为有符号数&#xff0c;它是以补码形式存储的&#xff0c;手动计算验证时注意转换为负数对应数值进行计算。...

每日一题——接雨水

接雨水问题详解 视频学习推荐 建议先参考以下视频进行学习&#xff1a; 问题描述 给定一个非负整数数组 height&#xff0c;表示每个宽度为 1 的柱子的高度图。计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 示例 1&#xff1a; 输入&#xff1a;height …...

【Flink银行反欺诈系统设计方案】1.短时间内多次大额交易场景的flink与cep的实现

【flink应用系列】1.Flink银行反欺诈系统设计方案 1. 经典案例&#xff1a;短时间内多次大额交易1.1 场景描述1.2 风险判定逻辑 2. 使用Flink实现2.1 实现思路2.2 代码实现2.3 使用Flink流处理 3. 使用Flink CEP实现3.1 实现思路3.2 代码实现 4. 总结 1. 经典案例&#xff1a;短…...

【15】蚂蚁链产品与服务

15-1 蚂蚁链一体机 蚂蚁链一体机概述 蚂蚁链一体机是深度融合软硬件技术、针对区块链技术特色打造的软硬一体化服务器。面对区块链技术落地中的性能、安全和隐私等技术挑战&#xff0c;结合自主硬件技术&#xff0c;打造了高性能、强隐私和高安全的软硬件一体化服务器&#x…...

​DeepSeek:如何通过自然语言生成HTML文件与原型图?

在当今快节奏的开发与设计环境中&#xff0c;快速生成HTML文件或原型图是每个开发者与设计师的迫切需求。虽然DeepSeek无法直接生成图片&#xff0c;但它却能够通过自然语言生成流程图、原型图以及交互式页面&#xff0c;甚至可以直接输出HTML代码。本文将详细介绍如何与DeepSe…...

【JAVA架构师成长之路】【持久层】第2集:SQL常用优化手段

课程标题:SQL常用优化手段——15分钟快速提升数据库性能 目标:掌握10+核心SQL优化技巧,解决慢查询、高负载等生产问题 0-1分钟:优化核心原则——减少数据扫描量 本质逻辑:通过索引、分页、过滤条件等手段,最小化磁盘I/O和内存计算。 反例:SELECT * FROM orders(全表扫…...

文件上传和下载前后端交互逻辑

上传】 1、后端给前端一个上传接口&#xff1a;进行文件上传&#xff0c;上传成功后&#xff0c;该接口返回文件的路径&#xff0c;名称&#xff0c;id 2、表单提交接口&#xff0c;提交表单时&#xff0c;将文件的id和表单信息一块提交给后台&#xff0c;实现文件和表单的绑…...

全向广播扬声器在油气田中的关键应用 全方位守护安全

油气田作为高风险作业场所&#xff0c;安全生产始终是重中之重。在紧急情况下&#xff0c;如何快速、有效地传达信息&#xff0c;确保人员安全撤离&#xff0c;是油气田安全管理的关键环节。全向广播扬声器凭借其全方位覆盖、高音质输出和强大的环境适应性&#xff0c;成为油气…...

PHP之运算符

在你有别的编程语言的基础下&#xff0c;你想学习PHP&#xff0c;可能要了解的一些关于运算符的信息。 三元运算符&#xff0c;短径求值&#xff0c;为空判断 echo 1 ? "b" : "c";//第一个为真时返回第一个&#xff0c;为假时返回第二个 echo 1 ?: &qu…...

hive之lag函数

从博客上发现两个面试题&#xff0c;其中有个用到了lag函数。整理学习 LAG 函数是 Hive 中常用的窗口函数&#xff0c;用于访问同一分区内 前一行&#xff08;或前 N 行&#xff09;的数据。它在分析时间序列数据、计算相邻记录差异等场景中非常有用。 一、语法 LAG(column,…...

3427. 变长子数组求和

给你一个长度为 n 的整数数组 nums 。对于 每个 下标 i&#xff08;0 < i < n&#xff09;&#xff0c;定义对应的子数组 nums[start ... i]&#xff08;start max(0, i - nums[i])&#xff09;。 返回为数组中每个下标定义的子数组中所有元素的总和。 子数组 是数组中…...

RabbitMQ 2025/3/5

高性能异步通信组件。 同步调用 以支付为例&#xff1a; 可见容易发生雪崩。 异步调用 以支付为例&#xff1a; 支付服务当甩手掌柜了&#xff0c;不管后面的几个服务的结果。只管库库发&#xff0c;后面那几个服务想取的时候就取&#xff0c;因为消息代理里可以一直装&#x…...

C#+Halcon 检测稳定性提升的方式

前言 众所周知&#xff0c;C#是一个带垃圾回收机制的语言&#xff0c;开发过程中不需要考虑垃圾回收&#xff0c;你就可劲造吧。但我们在设计图像处理软件时&#xff0c;应时刻对图像等大内存资源进行管控&#xff0c;做到自行管控&#xff0c;及时释放&#xff0c;不应将其交…...

第一个Hadoop程序

编写和运行第一个 Hadoop 程序是学习 Hadoop 的重要步骤。以下是一个经典的“WordCount”程序示例&#xff0c;它统计文本文件中每个单词出现的次数。我们将使用 Java 编写 MapReduce 程序&#xff0c;并在 Hadoop 集群上运行它。 一、WordCount 程序概述 WordCount 是 Hadoo…...

Leetcode 378-有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix &#xff0c;其中每行和每列元素均按升序排序&#xff0c;找到矩阵中第 k 小的元素。 请注意&#xff0c;它是 排序后 的第 k 小元素&#xff0c;而不是第 k 个 不同 的元素。 你必须找到一个内存复杂度优于 O(n2) 的解决方案。 示例 1&#xff1…...

python之爬虫入门实例

链家二手房数据抓取与Excel存储 目录 开发环境准备爬虫流程分析核心代码实现关键命令详解进阶优化方案注意事项与扩展 一、开发环境准备 1.1 必要组件安装 # 安装核心库 pip install requests beautifulsoup4 openpyxl pandas# 各库作用说明&#xff1a; - requests&#x…...

第一个 C++ 程序

文章目录 “Hello, World!” 程序代码示例程序基本结构详细解释头文件&#xff08;#include <iostream>&#xff09;命名空间&#xff08;std&#xff09;主函数&#xff08;int main()&#xff09;输出语句&#xff08;std::cout << "Hello, World!" &l…...

深度学习篇---不同框架下的图像通道

文章目录 前言一、核心概念NCHWNHWCCHW 二、主流框架的通道顺序1.PyTorch默认格式特点调整方法 2.TensorFlow默认格式特点调整方法 3.Keras默认格式特点 4.PaddlePaddle默认格式特点调整方法 5.MXNet默认格式调整方法 6.ONNX默认格式特点 三、通道顺序的影响性能差异NCHWNHWC 框…...

在 C++ 中,通常会使用 `#define` 来定义宏,并通过这种方式发出警告或提示。

在 C++ 中,通常会使用 #define 来定义宏,并通过这种方式发出警告或提示。 如何实现 GBB_DEPRECATED_MSG 宏: 你可以通过以下方式定义一个宏,显示弃用警告: #include <iostream>// 定义一个宏,用来打印弃用警告 #define GBB_DEPRECATED_MSG(msg...

10x Research:Secured Finance 基于 FIL 的美元稳定币如何推动 Filecoin 生态系统发展

“众多与 Filecoin 数据进行互动的参与者&#xff0c;将从全新灵活性与金融化的体系中受益。” 在 10X Research 的最新研究内容中&#xff0c;揭示了在 Filecoin 生态系统内推出以美元计价的稳定币为用户带来的巨大增益。 Filecoin 是 Web3 中增长最快的平台之一&#xff0c…...

解决Spring Boot中LocalDateTime返回前端数据为数组结构的问题

在Spring Boot开发中&#xff0c;处理日期时间数据是一个常见的需求。Java 8 引入了新的日期时间API&#xff0c;如LocalDateTime&#xff0c;它提供了更强大的日期时间处理功能。然而&#xff0c;在将LocalDateTime对象序列化为JSON时&#xff0c;可能会遇到返回为数组结构的问…...

【Linux】进程间通信 续

目录 管道的原理&#xff08;匿名管道&#xff09; 核心原理 站在内核的角度看管道的本质 接口 创建管道文件 代码示例 管道的特征 管道读写端的四种情况 管道的应用场景 命令行的管道。 使用管道实现进程池 初始化 控制子进程 退出 命名管道 命名管道的理解 …...

心率提取,FFT

rPPG 信号提取&#xff1a; 从面部视频中提取 rPPG 信号&#xff0c;通常是通过对视频帧中的面部区域进行颜色通道分析&#xff0c;提取出反映血液容积变化的信号。 信号预处理&#xff1a; 对提取的 rPPG 信号进行滤波、归一化等预处理操作&#xff0c;以去除噪声和干扰。 心率…...

Data truncation: Out of range value for column ‘allow_invite‘ at row 1

由于前端传递的数值超过了mysql数据库中tinyint类型的取值范围&#xff0c;所以就会报错。 Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column allow_invite at row 1at com.mysql.cj.jdbc.exceptions.SQLExcept…...

ZYNQ-PL学习实践(二)按键和定时器控制LED闪烁灯

ZYNQ-PL学习实践&#xff08;二&#xff09;按键和定时器控制LED闪烁灯&#xff09; 1 创建工程2 verilog 代码3 约束4 综合5 生成bit总结 1 创建工程 2 verilog 代码 添加key_led.v 文件&#xff0c; module key_led(input sys_clk , //系统时钟50MHzinput …...

【CXX】4.4 其他构建系统

你需要至少完成以下三件事&#xff1a; 生成CXX生成的C绑定代码。 编译生成的C代码。 将生成的目标文件与你的其他C和Rust目标文件链接在一起。 并非所有构建系统都是平等的。如果你希望使用90年代的构建系统&#xff0c;尤其是如果你希望覆盖2个或更多构建系统&#xff08…...

windows 上删除 node_modules

在 Windows 11 上&#xff0c;你可以通过命令行来删除 node_modules 文件夹并清除 npm 缓存。以下是具体步骤&#xff1a; 删除 node_modules 打开命令提示符&#xff08;Command Prompt&#xff09;或终端&#xff08;PowerShell&#xff09;。 导航到项目目录。你可以使用 …...

mysql之如何获知版本

你可以通过在 MySQL 命令行客户端执行简单的 SQL 查询来获取 MySQL 的版本信息。以下是获取 MySQL 版本的常见方法&#xff1a; 使用 SELECT VERSION(); 查询&#xff1a; SELECT VERSION();执行这个查询后&#xff0c;MySQL 会返回一个字符串&#xff0c;其中包含了 MySQL 服…...

PHP 将图片url,写入到文件夹中,导出到zip下载到桌面

一&#xff1a;将图片写入到文件内 文件url&#xff1a;比如 【https://image.baidu.com/search/detail?ct503316480&z0&ipnfalse&word%E5%9B%BE%E7%89%87%E5%A4%B4%E5%83%8F&hs0&pn1&spn0&di7466852183703552001&pi0&rn1&tnbaiduima…...

系统架构设计师—计算机基础篇—文件管理

文章目录 文件结构逻辑结构物理结构文件的索引直接索引间接索引做题的方法 文件存储空间管理位示图 文件结构 文件的结构是指文件的组织形式。 逻辑结构 从用户角度看到的文件的组织形式。 用户知道文件名就可以存取文件中的信息。 物理结构 文件在文件存储器上的存放方式…...