十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F407软件I2C读取MPU6050 DMP库输出稳定yaw角详解

STM32F407软件I2C读取MPU6050 DMP库输出稳定yaw角详解 简介面向STM32F4系列开发者的MPU6050姿态解算资源基于HAL库与软件I2C方式集成InvenSense官方DMP运动驱动库重点解决偏航角漂移与姿态数据不稳定问题适合无人机、平衡车、机器人等需要实时获取航向角的应用场景。压缩包共16个文件包括9个头文件与7个C源文件整体仅52KB结构精简。头文件负责函数声明、寄存器地址与DMP关键配置的宏定义C源文件则完整实现软件I2C读写、MPU6050初始化、DMP姿态解算以及卡尔曼滤波等数据处理流程便于按模块阅读和裁剪。资源已有338人学习适用于具备一定STM32基础、希望绕开硬件I2C兼容性困扰或者需要掌握DMP移植方法的开发者。通过阅读代码可清晰了解DMP初始化流程、数据读取与yaw角解算方式并可直接将相关文件嵌入自身工程缩短调试时间提升姿态输出稳定性。 做姿态解算最烦的就是yaw飘。MPU6050陀螺仪积分出来的yaw十分钟能偏出去几十度加速度计又测不了yaw。所以过去在STM32上大家要么自己写互补滤波要么移植Mahony折腾一圈效果还看运气。直到用上DMP库才算是真正解脱了DMP在芯片内部直接完成姿态融合输出的是稳定的四元数MCU要做的只是读FIFO数据然后做坐标变换。这篇博文就围绕手上一个实际项目来写STM32F407HAL库软件模拟I2C跑InvenSense官方DMP库读取MPU6050姿态最终成功获得了稳定的yaw角。整个过程踩了不少坑下面会把硬件连接、I2C模拟层、DMP移植、姿态解算、常见问题全部拆开讲清楚。适合刚入门姿态解算的嵌入式开发者也适合用硬件I2C读MPU6050卡住的老手参考。1. 方案选型与整体设计思路1.1 为什么选用软件I2C这句话我先撂在前头MPU6050这颗芯片对I2C时序要求不苛刻但硬件I2C在STM32上的坑特别多尤其是和HAL库配合时。很多人在F1系列上刷过硬件I2C的“神坑”到了F4仍然心有余悸。HAL库的HAL_I2C_Mem_Read函数在连续读取时如果配合DMA或者中断使用不当很容易卡死在忙等待状态。而且MPU6050的寄存器读取有个特点先写寄存器地址再读数据。这中间的时序切换在硬件I2C上需要主模式设置、STOP/START条件控制稍不留神就出错。相比之下软件I2C用两个GPIO口模拟时序完全由代码控制时钟线和数据线的电平变化。它的优势不是快而是稳、可控、可调。你想让时序慢一点就慢一点想加延时调试就加延时。对于DMP这种需要高速连续读取FIFO数据的场景软件I2C在400kHz以下跑起来完全够用。再有就是引脚选择的自由度。硬件I2C要绑死在特定的PB6/PB7、PB8/PB9或者PB10/PB11上而软件I2C想接哪个GPIO都行。做板子的时候布线更自由调试时也可以随时换引脚。1.2 DMP库和自写滤波算法的取舍自写姿态解算算法比如Mahony互补滤波或者Madgwick思路是陀螺仪积分算出短期姿态加速度计修正俯仰和横滚的长期漂移磁力计修正yaw的漂移。但问题在于MPU6050内部没有磁力计yaw的长期漂移没法通过重力方向来修正。DMP是InvenSense提供的运动处理引擎它在芯片内部完成传感器融合、滤波、姿态计算直接输出四元数q30格式的成果。它内部融合了陀螺仪、加速度计的数据并且对加速度计噪声、陀螺仪零漂做了比较完善的校准补偿。用DMP之后MCU端的负载大大降低不需要再跑滤波算法只需要以100Hz左右的频率读取FIFO数据然后解析四元数并转换成欧拉角。实测下来的效果在没有外部磁力计修正的情况下yaw在静止状态长时间保持稳定不会像纯陀螺仪积分那样一直漂。这已经是软件滤波很难达到的效果。1.3 工程结构规划整个工程基于STM32CubeMX生成配置如下系统时钟168MHzPLL外部25MHz晶振调试串口USART1115200-8-N-1I2C引脚PB6作为SCLPB7作为SDA均为开漏输出定时器TIM31ms中断用于主循环调度和DMP数据读取节拍CubeMX只需要配置时钟、串口和GPIO不需要启用任何硬件I2C外设。软件I2C和DMP相关代码全部手动添加到工程里。这样还有一个好处HAL库版本升级也不影响I2C的稳定性因为根本不依赖HAL库的I2C驱动。2. 硬件连接与MPU6050核心寄存器解析2.1 传感器外围电路MPU6050本身是LGA封装的贴片元件很多朋友直接买模块用模块上已经画好了外围电路。如果是自己画PCB需要注意这些外围器件VDD供电2.375V-3.46V典型3.3V。要给VDD引脚接一个0.1uF去耦电容放在芯片电源引脚旁边。逻辑电平参考电压VLOGIC建议同样接3.3V。REGOUT引脚内部稳压器输出接0.1uF电容。电荷泵电容CPOUT接一个2.2nF的电容到GND这个电容关系到内部电荷泵的稳定。模块供电电流很小正常工作电流不到4mA所以3.3V LDO供电完全够用。但要注意MPU6050有两个I2C地址AD0接GND时地址是0x68接VCC时地址是0x69。一个I2C总线上最多挂两个MPU6050就是通过这个引脚区分的。2.2 上拉电阻的取值选择I2C总线需要上拉电阻这个细节很多人忽略。软件I2C模式下的输出配置建议SCL和SDA都配置为开漏输出外部接4.7k欧姆上拉电阻到3.3V。如果模块上已经有上拉电阻就不需要再加。如果是自己画的板子推荐4.7k。总线电容较小时可以用10k但总线速度想跑400kHz的话上拉电阻太大上升沿会变缓影响时序。2.3 必须掌握的寄存器清单DMP移植过程中不需要把整个寄存器手册读完但这几个寄存器的含义必须搞清楚。下面这张表是使用频率最高的一组寄存器名称地址作用WHO_AM_I0x75芯片ID固定0x68PWR_MGMT_10x6B电源管理复位后要写0x01唤醒SMPLRT_DIV0x19采样率分频器CONFIG0x1A数字低通滤波配置GYRO_CONFIG0x1B陀螺仪量程配置ACCEL_CONFIG0x1C加速度计量程配置INT_ENABLE0x38中断使能INT_STATUS0x3A中断状态标志USER_CTRL0x6AFIFO使能、I2C主模式控制FIFO_EN0x23FIFO通道使能FIFO_COUNT_H/L0x72/0x73FIFO中数据字节数FIFO_R_W0x74FIFO数据读取口读WHO_AM_I是验证I2C通信是否正常的第一步操作一般返回0x68。如果读出来是0xFF说明总线没通读出来是0x00或者别的值需要检查接线和地址配置。2.4 I2C探测的实际操作软件I2C写好之后第一件事不是跑DMP而是先写个I2C扫描程序把设备地址找出来。我的做法是遍历0x00到0x7F的每个地址尝试发一个起始条件发送地址加写位然后检查ACK。收到ACK的地址就是设备地址。这一步能快速区分问题如果是扫描不到设备大概率是上拉电阻缺失、引脚配置错误、供电异常如果扫到了0x68说明物理层没问题可以直接进入寄存器操作。3. 软件I2C协议层实现3.1 引脚初始化软件I2C的第一步是把SCL和SDA两个引脚配置成开漏输出。开漏输出的妙处在于输出高电平时引脚实际上是高阻态由外部上拉电阻把电平拉高输出低电平时引脚接地拉低。这样天然满足I2C的线与逻辑。初始化代码很简单void MPU6050_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); I2C_SCL_HIGH(); I2C_SDA_HIGH(); }注意这里没有使能内部上拉因为外部已经接了4.7k电阻。如果板子上没有外置上拉调试时可以把Pull改成GPIO_PULLUP先凑合跑但正规产品不能这么干。宏定义如下#define I2C_SCL_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define I2C_SCL_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define I2C_SDA_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET) #define I2C_SDA_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)开漏模式下向GPIO写SET引脚就会进入高阻态由外部上拉拉高。写RESET引脚就是拉低。逻辑上可以当作推挽输出使用但要记住读SDA电平用于ACK检查时需要先把SDA配置为输入模式读完之后再切回输出模式。高位在前高位在前。3.2 起始、停止、发送字节、接收字节软件I2C的核心就是四个时序函数起始条件、停止条件、发送一个字节、接收一个字节。起始条件的时序是SCL在高电平期间SDA从高拉低。void I2C_Start(void) { I2C_SDA_HIGH(); I2C_SCL_HIGH(); I2C_Delay(5); I2C_SDA_LOW(); I2C_Delay(5); I2C_SCL_LOW(); }停止条件正好相反SCL在高电平期间SDA从低拉高。void I2C_Stop(void) { I2C_SDA_LOW(); I2C_SCL_HIGH(); I2C_Delay(5); I2C_SDA_HIGH(); I2C_Delay(5); }发送一个字节的流程是把要发送的字节逐位放到SDA线上。在SCL为低电平期间改变SDASCL拉高后数据被对方采样。void I2C_SendByte(uint8_t data) { for (int i 0; i 8; i) { if (data 0x80) I2C_SDA_HIGH(); else I2C_SDA_LOW(); data 1; I2C_Delay(2); I2C_SCL_HIGH(); I2C_Delay(2); I2C_SCL_LOW(); } }发送完之后需要释放SDA并读取从机的ACK应答。这里有个细节要先把SDA切换成输入模式才能正确读到SDA上的电平。uint8_t I2C_WaitAck(void) { uint8_t ack 1; GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); I2C_Delay(2); I2C_SCL_HIGH(); I2C_Delay(2); if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) GPIO_PIN_RESET) ack 0; I2C_SCL_LOW(); GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); return ack; }这个不断切换GPIO模式的写法虽然啰嗦但可靠。在400kHz总线上完全够用。有人用寄存器直接改MODER位来切换输入输出速度更快但可读性差一些。二选一即可。接收一个字节的流程类似由主机产生时钟从机在SCL低电平时把数据放到SDA线上主机在SCL高电平时读取。uint8_t I2C_ReceiveByte(void) { uint8_t data 0; GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); for (int i 0; i 8; i) { I2C_SCL_HIGH(); I2C_Delay(2); data 1; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)) data | 0x01; I2C_SCL_LOW(); I2C_Delay(2); } GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); return data; }接收完一字节后主机要发送应答位。如果还要继续读下一字节发送低电平ACK如果这是最后一字节发送高电平NACK。void I2C_SendAck(void) { I2C_SDA_LOW(); I2C_Delay(2); I2C_SCL_HIGH(); I2C_Delay(2); I2C_SCL_LOW(); I2C_SDA_HIGH(); } void I2C_SendNack(void) { I2C_SDA_HIGH(); I2C_Delay(2); I2C_SCL_HIGH(); I2C_Delay(2); I2C_SCL_LOW(); I2C_SDA_LOW(); }I2C_Delay可以用简单的循环实现。经过实测在168MHz主频下空循环5次产生大约1us的延时。跑400kHz总线时每半周期大约1.25us延时循环10次左右比较合适。void I2C_Delay(uint32_t t) { while (t--) { __NOP(); } }3.3 连续读和单字节读的区别MPU6050的寄存器读取有两种场景一种是读单个寄存器比如WHO_AM_I、PWR_MGMT_1。流程是起始、发送设备地址加写位、发送寄存器地址、重新发送起始、发送设备地址加读位、读一字节、发送NACK、停止。另一种是连续读多个寄存器比如读FIFO数据时需要一次读出MPU6050_FIFO_COUNTH到FIFO_R_W之间的多个字节。MPU6050支持自动地址递增主机连续读即可不需要中途重新发送地址。中间字节发ACK最后一字节发NACK。DMP库内部大量使用连续读正确实现批量读取函数非常重要。写DMP移植代码前务必先把这两个读函数调试稳定。4. DMP库集成与初始化4.1 获取DMP代码DMP库的获取途径有两种。一种是InvenSense官方发布的运动传感器驱动包文件名通常叫embedded-motion-invensense版本有5.1.3、6.1.1等。另一种是从网络资源中获取的移植版本比如网上流传广泛的dmp_tap、dmp_capacity等。解压后需要从驱动包中拷贝到工程的文件有inv_mpu.cinv_mpu.hinv_mpu_dmp_motion_driver.cinv_mpu_dmp_motion_driver.hdmpKey.hdmpmap.h对应的MSP430或STM32平台文件提供底层I2C读写接口以inv_mpu.c为例移植的核心是补全三个底层接口函数int i2c_read(unsigned char addr, unsigned char reg, unsigned char len, unsigned char *buf) { MPU6050_I2C_ReadBytes(addr, reg, len, buf); return 0; } int i2c_write(unsigned char addr, unsigned char reg, unsigned char len, const unsigned char *buf) { MPU6050_I2C_WriteBytes(addr, reg, len, (uint8_t *)buf); return 0; } int delay_ms(unsigned long num) { HAL_Delay(num); return 0; }注意函数形参类型必须和库文件里的声明一致否则编译报错一堆头文件重定义问题。4.2 DMP初始化流程DMP初始化的标准流程是这样的void MPU6050_DMP_Init(void) { // 1. I2C物理层初始化 MPU6050_I2C_Init(); // 2. 复位MPU6050 MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x80); HAL_Delay(100); // 3. 唤醒MPU6050 MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x00); HAL_Delay(10); // 4. 芯片内部自检并校准 mpu_init(NULL); // 5. 设置传感器量程 mpu_set_gyro_fsr(2000); mpu_set_accel_fsr(2); // 6. 加载并启动DMP mpu_set_dmp_state(1); // dmp_load_motion_driver_firmware // mpu_set_sensors // mpu_configure_fifo // mpu_set_sample_rate // 7. 设置采样频率 mpu_set_sample_rate(100); // 100Hz }mpu_init返回0表示初始化成功。如果返回非零值大概率是I2C底层读写函数有问题或者是芯片自检时陀螺仪和加速度计的偏移量校准失败。DMP启动之后MPU6050会把解算好的四元数数据写入FIFO缓冲区。MCU需要定时读取FIFO解析四元数再转换成欧拉角。4.3 FIFO数据读取策略读取FIFO数据的标准姿势是先读FIFO_COUNTH和FIFO_COUNTL两个寄存器计算FIFO里有多少字节。一次完整的DMP输出包长度是定值由dmp_get_fifo_rate和包格式决定一般是256字节左右。FIFO中的字节数必须大于这个值才能读取。读取时一次性读出整个包。uint8_t mpu_dmp_get_data(float *pitch, float *roll, float *yaw) { short gyro[3]; short accel[3]; long quat[4]; unsigned long timestamp; unsigned char more; float q0, q1, q2, q3; if (dmp_read_fifo(gyro, accel, quat, timestamp, more, NULL)) return 1; if (quat) { // 四元数换算 q0 quat[0] / 1073741824.0f; q1 quat[1] / 1073741824.0f; q2 quat[2] / 1073741824.0f; q3 quat[3] / 1073741824.0f; // 计算欧拉角 *roll atan2f(2.0f * (q0 * q1 q2 * q3), 1.0f - 2.0f * (q1 * q1 q2 * q2)) * 57.29578f; *pitch asinf(2.0f * (q0 * q2 - q3 * q1)) * 57.29578f; *yaw atan2f(2.0f * (q0 * q3 q1 * q2), 1.0f - 2.0f * (q2 * q2 q3 * q3)) * 57.29578f; } return 0; }四元数除以1073741824是因为DMP输出的四元数是q30格式也就是乘以2的30次方。转换回浮点数的过程就是除以2的30次方。yaw角atan2f计算出来的结果是-pi到pi之间的弧度值乘以57.29578转成角度。这样得到的yaw初始范围是-180到180度。如果需要0到360度的范围加个判断if (*yaw 0) *yaw 360.0f;4.4 yaw数据处理和偏航角稳定性DMP输出的yaw在静止状态下非常稳定长时间运行漂移量很小。但在实际运动场景中比如四轴飞行器急加速、大幅倾斜时yaw可能会出现短时间的扰动。这是正常现象因为仅靠陀螺仪和加速度计无法感知绕重力轴的绝对旋转。如果项目要求更高的yaw稳定性加磁力计是绕不开的路。MPU6050的AUX I2C接口可以外接HMC5883L或AK8975磁力计由DMP自动完成校准和融合。不过这一步的硬件和软件复杂度都会增加先跑通无磁力计的方案再考虑扩展会稳妥得多。我实际测试的场景是把模块放在桌面上静止放置持续运行10分钟yaw的读数漂移不超过1度。这个表现在四轴飞控、平衡车、云台等应用中已经完全够用。5. 姿态解算结果分析5.1 四元数转欧拉角的细节上面代码里已经给出了转换公式这里补充几个容易出错的地方。第一DMP输出的四元数顺序是q0、q1、q2、q3分别对应w、x、y、z。别搞反了很多人栽在这上面。第二四元数转欧拉角用atan2f而不是atanf。atan2f能自动处理象限问题返回的角度范围是-pi到pi。如果使用atanfyaw的输出会丢失符号信息导致角度只在0到90度之间变化看起来很诡异。第三转换出来的欧拉角是机体坐标系相对于大地坐标系的姿态不是相对于初始状态的相对角度。也就是说当模块上电后yaw的初始值取决于当前朝向。如果想让yaw从0开始需要在初始化后记录初始yaw之后每次输出减去这个初始值。很多做云台的朋友会问为什么我转了一个方向yaw会从179突然跳到-179这不是DMP的问题而是欧拉角本身的天生缺陷万向锁。只要俯仰角接近正负90度yaw和roll会互相耦合角度显示就会出现跳变。如果对这个问题敏感建议直接用四元数做后续控制而不是转成欧拉角。5.2 定时读取节奏DMP建议的读取频率是100Hz也就是每10ms读一次FIFO。我工程里用TIM3产生1ms中断主循环里计数到10就触发一次DMP数据读取。读取时注意FIFO里的数据是FIFO队列的形式如果长时间不读数据会越积越多。读的时候需要一次性把FIFO中的数据全部取出来否则下一次读取时会读到旧数据导致姿态滞后。dmp_read_fifo函数内部会处理FIFO溢出问题。如果FIFO溢出库会清空FIFO并重新开始代价是丢失一段时间的数据。对于100Hz的读取频率DMP的FIFO容量是1024字节能存大约4个完整数据包只要主循环不卡死超过40ms就不会丢数据。5.3 实测数据波形用串口把yaw数据发到上位机手工让模块旋转90度、再转回来观察到的yaw变化非常跟手没有明显的过冲和振荡。静止时波形是一条直线只有偶尔0.1度级的噪声波动。这比之前用互补滤波在动态情况下的表现要好得多。需要说明的是DMP解算出来的姿态在动态快速运动时加速度计带来的误差会比纯陀螺仪积分大一些但yaw的短期稳定性完全没问题。如果你做的项目动作比较剧烈比如无人机特技飞行可以对加速度计的输出做低通滤波处理或者在DMP初始化时选择合适的低通滤波频率。6. 踩坑记录与问题排查6.1 I2C通信异常症状WHO_AM_I读取不到预期值。排查步骤和原因如下现象可能原因解决方案读返回0xFFSDA没上拉或SCL/SDA接反检查电路交换引脚测试读返回0x00芯片供电异常或地址错误检查VDD、VLOGIC确认AD0接线时序异常延时太短SCL频率过高增大I2C_Delay值电平一直为低引脚配置成推挽输出改成开漏输出配置软件I2C排查最实用的工具是逻辑分析仪。把SCL和SDA两根线接到逻辑分析仪上直接抓取波形一眼就能看出起始、停止条件和数据位是否正确。手头没有逻辑分析仪的可以写个循环向同一个寄存器写入固定数据再用万用表量SDA线的平均电压间接判断通信是否活跃。6.2 DMP初始化失败返回非零mpu_init或者mpu_set_dmp_state返回非零最常见的两个原因第一个是I2C读写函数没有正确实现。inv_mpu.c里的i2c_write和i2c_read接口形参类型要和头文件声明匹配。我在移植时就遇到过i2c_read的形参中多了const修饰符导致返回类型不匹配编译过了但运行直接硬件错误。第二个是自检校准失败。MPU6050上电后如果陀螺仪和加速度计的偏移量检查不过mpu_init会返回错误码。解决方法是在调用mpu_init之前先跑一遍reg dump确认芯片寄存器读写正常并且芯片处于唤醒状态。6.3 FIFO溢出导致数据中断现象是姿态数据偶尔会卡住几秒钟然后跳变。检查FIFO_COUNT寄存器发现FIFO中的数据量一直在积累说明读取频率跟不上写入频率。排查方法确认mpu_set_sample_rate设置的值和主循环读取频率匹配。DMP在内部会将四元数输出频率固定为某个值一般是100Hz。主循环的读取节奏必须大于等于这个频率。如果用了中断方式读取检查中断线是否接对了MPU6050的INT引脚并且中断处理函数里不要做太多耗时操作只做标志位置位。6.4 yaw角度在长时间运行后漂移DMP芯片内部的零偏校准在上电时执行。如果上电后芯片一直处于运动状态初始校准就会不准导致后续yaw漂移。解决方法是上电后保持模块静止2到3秒待初始化完成后再开始使用。另一个原因是温度变化。MEMS陀螺仪的零偏对温度敏感温度变化几十度会引起几度每秒的零偏变化。对温度稳定性要求高的场合DMP内部有温度补偿机制但效果有限需要外置温度传感器做二次补偿。6.5 角度在正负180度跳变这不是故障而是欧拉角的固有问题。处理方式是先确认工程对角度范围的需求。云台、机械臂这类场景360度范围内的连续控制更合适用四元数或者旋转矩阵只显示角度数值的场景接受180度跳变即可。如果必须在正负180度之间连续跳变可以把yaw值转换为0到360度范围但这只能解决显示层面的问题不能解决控制层面的坐标系跳变。7. 一些实操心得这个方案跑通之后我在几个不同项目上都做了复用。总结下来稳定性最好的组合是软件I2C加官方DMP库主循环100Hz读取不用中断。中断方式虽然实时性更好但在HAL库工程中中断回调里处理数据会带来优先级问题调试成本更高。还有一点要提醒DMP库只能使用InvenSense发布的内置固件无法修改解算参数。如果你需要特殊的滤波频率、特定的融合算法DMP库这条路走不通需要回到自己写滤波算法。DMP的优势是快速、省心、效果有保障劣势是黑盒、不可定制。两种方案没有绝对的好坏看项目需求选型。软件I2C的代码在工程里没有做线程安全保护如果你的程序里有多个地方同时调用MPU6050的读写函数记得加互斥锁或者保证调用逻辑串行化。否则SCL和SDA的时序交织在一起轻则读出的数据错误重则导致总线卡死。做嵌入式就是这样芯片手册不翻不知道翻了才知道水有多深。MPU6050这颗老芯片这么多年还能活跃在各类DIY项目中一方面是因为性能足够另一方面也是DMP这个配套生态立了大功。希望这篇博文能帮你少走几个弯路一次打通yaw读取这条路。本文还有配套的精品资源点击获取
返回列表