资讯详情

资讯详情

基于STM32的MPU6050姿态解算实战:I2C驱动到Mahony滤波

简介完整Keil工程整合STM32F103C8T6与MPU6050提供可直接编译下载的姿态解算源码面向无人机、机器人、智能穿戴等场景兼顾初学者入门与专业开发者的快速原型需求。资源包共188个文件涵盖标准外设库C源文件与头文件、Keil工程配置、编译中间文件及最终生成的hex/axf可执行文件压缩包仅6.16MB解压即可打开工程并一键编译。工程集成了定时器、ADC、I2C、USART等常用外设驱动并附使用说明和示例代码便于理解数据融合与姿态计算流程。已有2616人浏览学习这套整理好的源码与模板能显著缩短环境搭建时间无论用于飞控调试还是健康监测类产品开发都能让开发者把精力集中在算法与业务逻辑上。1. 这是什么工程专治MPU6050数据读不准的完整姿态解算框架如果你接过MPU6050大概率经历过这种场景模块照着教程接了SCL、SDA代码也没报错但串口里打印出来的加速度值像随机数pitch和roll跟着手一抖就满屏飞。市面上讲STM32驱动MPU6050的教程很多但成套的、能直接编译烧录的完整工程反而难找。这个工程把I2C驱动、寄存器初始化、原始数据读取、Mahony姿态解算、串口输出全部串好芯片用STM32F103C8T6烧录后打开串口助手就能实时看到欧拉角。适合做二轮平衡车、低成本云台、四轴入门验证姿态算法的人也适合刚学STM32想拿一个真实传感器项目练手的开发者。下载后不用改任何代码Keil里编译一遍直接烧。2. 硬件连接与工程结构先接对线再打开工程2.1 硬件接线3.3V供电、上拉电阻与I2C地址工程最基础的物理连接是一根I2C总线MPU6050作为从机挂在上面。多数模块引出的引脚是VCC、GND、SCL、SDA、AD0、INT。接线表如下MPU6050引脚STM32F103C8T6引脚说明VCC3.3V模块没有板载稳压接5V会发烫且数据异常GNDGND与开发板共地SCLPB6模拟I2C时钟线SDAPB7模拟I2C数据线AD0GND从机地址为0x68INT悬空数据就绪中断本工程轮询不接这里有一个重要的设计选择工程没有使用STM32F103的硬件I2C外设而是在普通GPIO上模拟I2C时序。我在硬件I2C上踩过不止一次坑——外设挂在总线上偶发性死锁、主从时钟同步异常排查起来比写驱动本身还费时间。模拟I2C只用两个GPIO脚逻辑完全由自己控制出问题的概率明显低。更重要的是模拟I2C不绑定固定引脚后面想换IO时改宏定义就行。供电方面MPU6050的核心工作电压是2.375V到3.46V所以模块的VCC必须接3.3V。有些开发者会把VCC接到5V认为模块有稳压实际上多数模块板上只有去耦电容和上拉电阻没有稳压芯片5V供电会让芯片严重发热读数变成固定值。我第一次上电时也犯了同样的错模块烫手之后读数全废换回3.3V才恢复正常。上拉电阻的问题容易被忽略。I2C协议要求SCL、SDA在空闲时是高电平设备通过拉低进行通信。GPIO配置成开漏输出后高电平需要外部上拉电阻提供。很多成品模块在PCB上自带了两个4.7kΩ上拉电阻直接接就行但如果是裸传感器或自己画板要在SCL、SDA上各加一个4.7kΩ或10kΩ电阻到3.3V。判断模块有没有上拉很简单用万用表量SDA对VCC的阻值几kΩ说明有无穷大说明没有。AD0脚决定I2C从机地址。AD0接GND时地址是0x68接3.3V时是0x69。代码里默认按0x68寻址如果你的模块正好把AD0接到了高电平会发现WHO_AM_I寄存器读出来的还是0x68但正常数据读取全部失败。因为WHO_AM_I是只读寄存器地址永远固定只有普通数据寄存器会受地址位影响。这类问题非常隐蔽我第一次遇到时绕了很久才发现是AD0接错了。2.2 工程结构与源文件职责哪些文件是核心打开工程后的目录结构如下不同版本文件名会有差异但功能分层基本一致。STM32F103_MPU6050_Demo/ ├─ Core/ │ ├─ inc/ │ │ ├─ main.h │ │ ├─ stm32f1xx_hal_conf.h │ │ └─ stm32f1xx_it.h │ └─ src/ │ ├─ main.c │ └─ stm32f1xx_hal_msp.c ├─ Drivers/ │ ├─ inc/ │ │ └─ stm32f1xx_hal.h │ └─ src/ │ └─ stm32f1xx_hal.c ├─ Hardware/ │ ├─ myiic.c / myiic.h │ ├─ mpu6050.c / mpu6050.h │ ├─ attitude.c / attitude.h │ └─ usart_com.c / usart_com.h ├─ MDK-ARM/ │ └─ Project.uvprojx └─ Output/ ├─ Project.hex └─ ...这个工程基于STM32CubeMX生成的HAL库工程文件名是Project.uvprojx直接用Keil MDK打开。不管用的是标准外设库还是HAL库跟业务相关的文件只有三组Hardware目录下的myiic负责模拟I2C引脚级驱动mpu6050负责寄存器读写和初始化attitude负责四元数姿态解算再加上main.c把三组串起来。usart_com是串口驱动如果只需要打印调试信息看它就够了。要提一下DMP这个概念。MPU6050内部有个名为DMP的数字运动处理器可以让它自己输出四元数从而省掉在MCU上跑姿态解算。本工程没有启用DMP而是直接读取原始的加速度和角速度数据在STM32上用Mahony互补滤波完成解算。这样做的原因是DMP的输出频率和数据结构相对固定调试起来不如自己掌控解算过程直观而且对想学习姿态算法的同学来说看懂寄存器模式是怎么算出来的比直接调库有价值得多。2.3 编译与烧录三步跑通完整流程打开MDK-ARM目录下的工程文件第一次编译前做两件事。第一点击魔术棒中的Device选项卡确认当前选择的芯片是STM32F103C8T6而不是别的型号选择错误会导致启动文件不匹配下载后运行不起来。第二点击Debug选项卡把调试器改成你手上的下载器常见的是ST-Link Debugger再进入Settings确认能识别到目标芯片。如果面板上是空的检查驱动安装或换一根USB线。连接好ST-Link点击Build编译。第一次编译要生成所有中间文件时间会长一些之后每次增量编译就快很多。编译通过后再点Download如果在小窗口里勾选了Reset and Run芯片会下载完自动复位运行。如果用的是串口ISP下载需要把BOOT0拉高进入下载模式烧完拉低再复位比较麻烦所以我一般直接ST-Link。代码里串口默认是USART1对应PA9TX和PA10RX波特率1152008位数据位、1位停止位、无校验。用USB转串口工具把PA9接RXD、PA10接TXD共地后打开串口助手选择对应COM口就能看到持续输出的角度数据。这里有个比较隐蔽的问题市面上很多CH340模块的TXD和RXD丝印是从模块本身视角标的接到开发板时要交叉接反了没有任何数据。查这个问题的通用办法是让开发板发看CH340模块的TX指示灯是否闪烁。输出格式是roll%.2f pitch%.2f yaw%.2f例如roll-0.03 pitch0.12 yaw45.67 roll0.05 pitch0.08 yaw45.89看到这样的数据说明整个链路已经通了。3. 核心代码拆解从寄存器到欧拉角的完整链路3.1 模拟I2C底层开漏输出与时序细节模拟I2C的物理载体是GPIO所以第一步是配置引脚模式。初始化函数如下void I2C_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStructure.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStructure.Mode GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStructure.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStructure); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_SET); }开漏输出是I2C的关键配置不是推挽输出。开漏模式下引脚只能主动拉低电平高电平依靠外部上拉电阻。如果配置成推挽两个设备同时驱动总线时可能产生短路电流长时间运行会损坏IO而且设备也无法拉低总线来表达握手。HAL库枚举是GPIO_MODE_OUTPUT_OD标准库则对应GPIO_Mode_Out_OD如果看的是标准库工程写法略有区别但含义一致。引脚速度设置为高速是为了让边沿尽量陡峭。I2C通信速率一般只有100kHz或400kHz理论上来得及但STM32的GPIO输出也有压摆率限制速度太低会导致矩形波变成梯形波长线传输时更容易产生误码。5MHz低速也行不过高速更稳妥。接着通过宏定义控制引脚#define IIC_SCL_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define IIC_SCL_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define IIC_SDA_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET) #define IIC_SDA_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET) #define IIC_SDA_READ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)写数据时SDA在SCL高电平期间必须保持稳定只有SCL低电平时才允许改变SDA。这个协议细节直接决定了起始信号和停止信号的写法void IIC_Start(void) { IIC_SDA_H; IIC_SCL_H; delay_us(5); IIC_SDA_L; // SCL高时SDA拉低 起始 delay_us(5); IIC_SCL_L; } void IIC_Stop(void) { IIC_SDA_L; IIC_SCL_H; delay_us(5); IIC_SDA_H; // SCL高时SDA拉高 停止 delay_us(5); }起始和停止信号的判定条件都是SCL为高电平时SDA的变化这是I2C协议最基础也最容易写错的地方。我见过同学的代码把SDA先变化、SCL后变化从逻辑波形上看起来完全不是I2C设备自然没有响应。每个电平跳变后加delay_us(5)大约对应100kHz到200kHz的时钟频率。延时太短从机跟不上延时太长通信速率太慢读完整个缓冲区需要更多时间。发送一个字节的函数是类似的思路void IIC_Send_Byte(uint8_t txd) { for (uint8_t i 0; i 8; i) { if (txd 0x80) IIC_SDA_H; // 先放数据 else IIC_SDA_L; txd 1; IIC_SCL_H; // 拉高SCL让从机采样 delay_us(2); IIC_SCL_L; // 拉低准备下一位 delay_us(2); } }每发送一位数据都要在SCL低电平时把SDA设置好然后拉高SCL让从机采样再拉低SCL准备下一位。主机的信号变化顺序是SDA先变、SCL后变和起始/停止的判断正好互为对照。接收字节类似只是把读引脚放在SCL高电平期间采样。3.2 寄存器初始化与量程换算参数决定你看到的数据MPU6050上电后处于休眠状态必须先配置电源管理寄存器解除休眠否则能读到WHO_AM_I但读不到有效运动数据。初始化代码#define MPU6050_SMPLRT_DIV 0x19 #define MPU6050_CONFIG 0x1A #define MPU6050_GYRO_CONFIG 0x1B #define MPU6050_ACCEL_CONFIG 0x1C #define MPU6050_PWR_MGMT_1 0x6B void MPU6050_Init(void) { MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); // 解除休眠 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07); // 125Hz采样 MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // DLPF 5Hz MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计±16g }各参数的含义PWR_MGMT_1写入0x00让芯片退出休眠并使用内部振荡器。如果写0x01只是把时钟源切到陀螺仪X轴并没有解除休眠数据依然读不出来这是最常见的“初始化无效”原因之一。SMPLRT_DIV7采样率按1kHz/(17)125Hz计算对于静止测倾角、慢速云台完全够用。如果要提高到250Hz改成3500Hz改成1。采样率越高姿态解算的动态响应越好但主循环也必须用同样频率调用解算函数否则数据会在读取端堆积。本工程主循环用10ms周期对应100Hz低于125Hz所以实际使用中部分采样会被跳过姿态输出依然平滑。CONFIG寄存器的低通滤波带宽设置为5Hz主要滤掉高频机械振动。做云台一般选0x03即44Hz带宽动态响应更快但噪声也会更明显。做平衡车建议先5Hz起步如果感觉响应迟钝再逐步放开不要一上来就给几百Hz的带宽。GYRO_CONFIG写入0x18陀螺仪满量程±2000°/s灵敏度16.4 LSB/°/sACCEL_CONFIG写入0x18加速度计满量程±16g灵敏度2048 LSB/g。量程选择是一个取舍大量程不容易饱和适合剧烈运动小量程精度高适合静态倾角测量。常用组合和灵敏度列在一起方便直接查对照寄存器值陀螺仪量程灵敏度(LSB/°/s)加速度量程灵敏度(LSB/g)0x00±250°/s131±2g163840x08±500°/s65.5±4g81920x10±1000°/s32.8±8g40960x18±2000°/s16.4±16g2048读取原始数据的代码void MPU6050_Read_Accel(int16_t *ax, int16_t *ay, int16_t *az) { uint8_t buf[6]; MPU6050_ReadRegs(MPU6050_ACCEL_XOUT_H, buf, 6); *ax (int16_t)((buf[0] 8) | buf[1]); *ay (int16_t)((buf[2] 8) | buf[3]); *az (int16_t)((buf[4] 8) | buf[5]); }MPU6050寄存器的高字节在前低字节在后所以要先读高字节再读低字节组合成一个有符号数。组合时必须强制转换为int16_t否则符号位会被当成无符号数展开。比如原始值是-32768不做转换就会变成65536姿态角直接错乱。我当年在这翻过车波形上表现为某个角度反向跳变排查了很久。3.3 Mahony姿态解算比例积分互补滤波的实现陀螺仪积分短期准、长期飘加速度计长期准、短期噪声大。Mahony算法用加速度计去修正陀螺仪积分是这类传感器在嵌入式设备上的常见选择。相比卡尔曼滤波它不需要矩阵求逆占用资源小在72MHz主频的STM32F103上跑几百Hz完全无压力。核心代码#define SAMPLE_RATE 125.0f #define RAD2DEG 57.2957795f #define Kp 2.0f #define Ki 0.01f static float q0 1.0f, q1 0.0f, q2 0.0f, q3 0.0f; static float exInt 0.0f, eyInt 0.0f, ezInt 0.0f; void Mahony_Update(float gx, float gy, float gz, float ax, float ay, float az) { float norm, vx, vy, vz, ex, ey, ez; if (ax * ay * az 0.0f) { return; } norm sqrtf(ax * ax ay * ay az * az); ax / norm; ay / norm; az / norm; vx 2.0f * (q1 * q3 - q0 * q2); vy 2.0f * (q0 * q1 q2 * q3); vz q0 * q0 - q1 * q1 - q2 * q2 q3 * q3; ex ay * vz - az * vy; ey az * vx - ax * vz; ez ax * vy - ay * vx; exInt ex * Ki; eyInt ey * Ki; ezInt ez * Ki; gx Kp * ex exInt; gy Kp * ey eyInt; gz Kp * ez ezInt; q0 (-q1 * gx - q2 * gy - q3 * gz) * 0.5f / SAMPLE_RATE; q1 ( q0 * gx q2 * gz - q3 * gy) * 0.5f / SAMPLE_RATE; q2 ( q0 * gy - q1 * gz q3 * gx) * 0.5f / SAMPLE_RATE; q3 ( q0 * gz q1 * gy - q2 * gx) * 0.5f / SAMPLE_RATE; norm sqrtf(q0 * q0 q1 * q1 q2 * q2 q3 * q3); q0 / norm; q1 / norm; q2 / norm; q3 / norm; }这个函数分几步理解先把加速度计向量归一化让它与四元数估算出来的重力向量在一个尺度上比较。如果某轴读到0且另外两轴也为0说明传感器数据异常跳过修正避免把错误向量带进积分。然后用当前四元数推算重力在机体坐标系下的表示得到vx、vy、vz。叉积是测量值与估算值之间的误差叉积向量同时反映姿态误差的大小和方向把它加到角速度上相当于对陀螺仪积分做闭环修正。PI参数中Kp决定修正速度Ki决定长期漂移的抑制力度。Kp太大会让角度高频抖动Ki太大会让解算结果出现低频振荡。我一般先用Kp1.5、Ki0.01起步看静态波形再根据噪声和响应速度微调。SAMPLE_RATE必须与MPU6050实际采样率一致如果这里写100而实际是125转动时角度就会出现明显滞后和畸变。四元数每步更新后必须归一化否则误差累积会让角度变成NaN或反向跳变。四元数转欧拉角void Quaternion_To_Euler(float *pitch, float *roll, float *yaw) { *pitch asinf(2.0f * (q0 * q2 - q1 * q3)) * RAD2DEG; *roll atan2f(2.0f * (q0 * q1 q2 * q3), q0 * q0 - q1 * q1 - q2 * q2 q3 * q3) * RAD2DEG; *yaw atan2f(2.0f * (q1 * q2 q0 * q3), q0 * q0 q1 * q1 - q2 * q2 - q3 * q3) * RAD2DEG; }注意pitch用asin它的定义域是[-1,1]如果传入的数值因为浮点误差变成1.0000001返回值就是NaN。解决办法是在调用前做一次限幅float sinp 2.0f * (q0 * q2 - q1 * q3); if (sinp 1.0f) sinp 1.0f; if (sinp -1.0f) sinp -1.0f; *pitch asinf(sinp) * RAD2DEG;这个NaN问题非常隐蔽静止时不会出现旋转速度快时偶尔复现。我第一次遇到时串口输出突然变成-1.#IND还以为是电源干扰实际上就是asin参数越界。3.4 主循环与串口输出如何把角度打印到上位机主循环是典型的轮询结构先读传感器再解算再输出。int16_t ax, ay, az, gx, gy, gz; float roll, pitch, yaw; int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(115200); I2C_GPIO_Config(); MPU6050_Init(); while (1) { MPU6050_Read_Accel(ax, ay, az); MPU6050_Read_Gyro(gx, gy, gz); gx gx / 16.4f * 0.01745f; // LSB - °/s - rad/s gy gy / 16.4f * 0.01745f; gz gz / 16.4f * 0.01745f; ax ax / 2048.0f; // LSB - g ay ay / 2048.0f; az az / 2048.0f; Mahony_Update(gx, gy, gz, ax, ay, az); Quaternion_To_Euler(pitch, roll, yaw); printf(roll%.2f pitch%.2f yaw%.2f\r\n, roll, pitch, yaw); delay_ms(10); } }单位换算必须与寄存器量程一致。如果GYRO_CONFIG改成±500°/s灵敏度就要换成65.5否则所有角速度都会被放大姿态快速发散。0.01745是把°/s换算成rad/s的系数因为Mahony_Update内部使用弧度制。加速度计除以2048后得到以g为单位的加速度。printf重定向到串口的标准写法int fputc(int ch, FILE *f) { (void)f; HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 10); return ch; }在HAL库工程里重定向fputc之后printf的输出就会走串口。注意如果工程里用的是USART2就要换成huart2。另外部分Keil版本需要勾选MicroLIB否则printf会占用大量空间或重定向无效STM32F103C8T6的Flash容量通常足够但MicroLIB更省空间。3.5 陀螺仪读取和FIFO这个工程为什么不依赖DMP陀螺仪的读取方式和加速度计一致只是起始寄存器不同void MPU6050_Read_Gyro(int16_t *gx, int16_t *gy, int16_t *gz) { uint8_t buf[6]; MPU6050_ReadRegs(MPU6050_GYRO_XOUT_H, buf, 6); *gx (int16_t)((buf[0] 8) | buf[1]); *gy (int16_t)((buf[2] 8) | buf[3]); *gz (int16_t)((buf[4] 8) | buf[5]); }很多教程提到MPU6050的FIFO和DMP功能这里说明为什么本工程不使用。DMP可以把解算好的四元数写入FIFOMCU批量读取省去自己跑滤波算法。但DMP固件初始化代码较长输出频率和数据格式相对固定对想理解姿态解算原理的开发者来说反而像一个黑匣子。寄存器轮询模式代码直观每125ms读一次数据CPU占用极低完全够平衡车和云台这种场景用。4. 避坑指南MPU6050调试中的常见问题排查4.1 读不到设备WHO_AM_I返回0xFF现象初始化函数里的自检返回失败回读WHO_AM_I寄存器结果是0xFF或0x00。原因物理链路出问题的概率比代码还大。VCC接5V会让模块内部过热SCL、SDA上没有上拉电阻导致总线空闲时被拉低杜邦线接触不良甚至USB转串口和ST-Link共用一个地线时产生了电位差都可能让I2C通信直接失败。解决先用万用表量模块VCC电压是否为3.3V再看SCL、SDA上是否有正常波形。如果没有工具就写一个单寄存器读函数读WHO_AM_I。这个寄存器的值永远返回0x68不管AD0接高接低。能读到说明底层I2C已经通问题在后续寄存器配置读不到就回到接线和上拉电阻排查。4.2 数据全部为0或静止时角度仍然漂移现象加速度、陀螺仪数据都是0或者把板子平放在桌上roll和pitch从0慢慢爬到几度yaw更是一直在飘。原因数据全0多半是芯片还在休眠。PWR_MGMT_1寄存器写入0x00才能解除休眠很多初始化代码写的是0x01以为只是改时钟源实际上芯片依然处于低功耗状态。角度漂移则是零偏补偿缺失每个陀螺仪出厂时都有零偏温度变化后还会改变上电后不校准直接积分必然漂。解决数据全0的话回读PWR_MGMT_1确认是不是0x00。角度漂移的话在main里加一个陀螺仪零偏校准函数上电后等待传感器稳定连续读取100次角速度求平均把平均值保存为全局变量之后每次读取先减掉这个零偏再转换单位。static float gyro_offset_x 0.0f; void MPU6050_Calib_Gyro(void) { int32_t sum 0; int16_t gx, gy, gz; for (int i 0; i 100; i) { MPU6050_Read_Gyro(gx, gy, gz); sum gx; delay_ms(2); } gyro_offset_x (float)sum / 100.0f; }4.3 角度变NaN或反向跳变现象快速翻转板子时串口偶尔出现-1.#IND或者某个角度瞬间跳变180度。原因四元数转欧拉角的asin入参超出[-1,1]或者组合寄存器数据时符号位被错误解析。NaN问题几乎总是由于四元数归一化不彻底或浮点累积误差造成。跳变则常见于int16_t类型转换问题。解决在调用asin之前做限幅保证入参在[-1,1]之间。同时把组合原始数据的类型强制为int16_t。如果数据还跳检查是否有代码在中断里用了浮点运算C8T6没有FPU浮点运算在中断里会消耗大量时间导致主循环对数据读取不及时。4.4 串口乱码或没有输出现象串口助手显示乱码或者完全没有任何输出。原因波特率不匹配常见的是代码里115200而串口助手选了9600。另一个原因是printf重定向用的串口句柄写错比如初始化了huart2但fputc里写huart1这时数据会发送到没有初始化的串口上电脑自然收不到。解决统一波特率打开串口助手设置为1152008N1。检查fputc和串口初始化是否指向同一个句柄。如果用了带DTR/RTS自动复位功能的USB转串口模块串口助手可能会在打开串口时拉低RTS导致开发板反复复位输出看起来断断续续把DTR和RTS的勾去掉再试。4.5 编译报错工程打开后一堆Undefined symbol现象下载别人的工程编译报几百个错误全是Undefined symbol main或找不到xxx.c文件。原因一般不是代码本身的问题而是编译路径或源文件列表不完整。Keil工程如果换过电脑可能产生相对路径失效有些代码需要添加目录到头文件包含路径否则找不到头文件。解决右键工程名在Manage Project Items里把Hardware目录源文件加进工程在Options的C/C选项卡里把Include Paths添加Core/inc、Drivers/inc等路径。这类工程下载下来不要放在带中文或空格的路径里换到纯英文目录问题会少很多。5. 二次开发把工程改成你要的传感器应用5.1 改引脚模拟I2C让GPIO不再受限把SCL、SDA换到任意GPIO。最省事的做法是保留模拟I2C驱动只改宏定义里的端口和引脚。换引脚时要注意目标引脚是否被复用占用例如PB3、PB4、PA15这些引脚默认可能是JTAG调试口需要关闭SWJ调试功能后才能当普通GPIO用。到这一步你会发现当初没有用硬件I2C外设是明智的因为硬件I2C的引脚被固定死了换MCU后整个驱动都可能要重写。5.2 换解算算法从Mahony到卡尔曼或者更轻量如果你做静态倾角感觉Mahony的噪声还是偏大可以升级成一维卡尔曼滤波。状态量取角度和角速度零偏观测量是加速度计算出的倾角代码量不大但需要调测量噪声R和过程噪声Q。如果做四轴飞控方向建议直接学惯性导航里的EKF不要把时间花在调互补滤波参数上。5.3 加无线传输串口数据变成手机波形在现有串口printf基础上把物理层换成蓝牙透传模块。接线是STM32的TX接蓝牙RXSTM32的RX接蓝牙TX共地。波特率记得配对蓝牙模块出厂常见9600直接在代码里把USART1初始化改成9600也行或者用AT指令把蓝牙改到115200。上位机可以用VOFA或匿名上位机按协议格式发送pitch:xx.xx,roll:xx.xx,yaw:xx.xx就能看到实时波形。这样调试平衡车时就不用拖着线了。现在我做任何姿态传感器工程烧录前必做两个动作一是检查零偏校准函数有没有执行过二是上电后拿串口绘图工具录一段静态波形等波形平了再谈调Kp和Ki。从那以后我每次调MPU6050都强制先跑一遍静态数据再说不然就只能靠肉眼猜猜来猜去最后还是要回来看波形。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →