1. 从零开始:认识你的硬件伙伴
大家好,我是老张,一个在嵌入式圈子里摸爬滚打了十来年的“老电工”。今天想和大家聊聊一个特别经典又实用的项目:用STM32单片机读取KY-9250(或者MPU9250)传感器的数据,然后实时解算出姿态角。这玩意儿听起来挺高大上,什么姿态解算、四元数,但说白了,就是让单片机知道它自己(或者它带着的传感器)在空间里“歪”了多少度。这个技术是四轴飞行器、平衡小车、云台稳定器这些酷炫玩意儿的基石。如果你正想自己动手做一个能感知姿态的小设备,那这篇文章就是为你准备的。
你可能在网上搜到过一些代码片段,比如直接读取传感器模块通过串口吐出来的、已经算好的角度。这确实能快速看到数据,但就像吃别人嚼过的饭,知其然不知其所以然。一旦数据跳得厉害,或者你想换个算法优化一下,就完全无从下手了。咱们这次要玩点更“底层”的:直接读取传感器的原始数据——也就是陀螺仪、加速度计、磁力计的原始读数,然后自己用C语言写算法来算姿态。这个过程能让你真正理解传感器融合是怎么回事,以后调参、排错心里都有底。
先来认识一下两位主角。STM32,这个不用多说了,单片机界的“瑞士军刀”,性价比高,资源丰富,社区支持强大,是嵌入式开发入门和进阶的首选。我这次用的是最常见的STM32F103C8T6核心板,也就是大家常说的“蓝板”或者“最小系统板”,价格便宜,功能足够。
另一位主角是KY-9250模块。这里有个关键点:KY-9250本质上是一个集成了MPU9250芯片并自带姿态解算固件的模块。MPU9250本身是一个9轴运动传感器(3轴陀螺仪+3轴加速度计+3轴磁力计),而KY-9250模块在它的基础上,增加了一个单片机,预先跑了一套姿态解算算法(通常是DMP或类似的),然后通过串口直接输出解算好的姿态角。所以,你可以把KY-9250理解为一个“智能版”的MPU9250。我们的玩法有两种:一是偷个懒,直接读取它算好的角度(就像原始文章里那样);二是追求极致控制和理解,通过I2C接口直接操控内部的MPU9250芯片,读取原始数据。为了把原理吃透,我们这篇文章会聚焦在第二种方法上,这也是大多数专业飞控和平衡车项目的标准做法。
那么,姿态解算到底要算什么?我们最终想要的是三个角:滚转角(Roll)、俯仰角(Pitch)、偏航角(Yaw)。想象一下飞机的姿态:滚转就是左右倾斜,俯仰就是抬头低头,偏航就是左右转向。单独靠加速度计可以算滚转和俯仰,但动态下误差大;单独靠陀螺仪积分可以得到角度,但误差会随着时间累积(漂移);磁力计可以用来校正偏航角,但容易受干扰。所以,我们需要一个“融合”算法,把这三者的优点结合起来,互相修正,得到稳定、准确的实时角度。这就是我们后面要实现的互补滤波或者Mahony算法干的事情。
2. 硬件连接与工程搭建:别在第一步就踩坑
万事开头难,硬件连接和软件环境搭建是第一个门槛。这里我会把容易出错的点都给你指出来,保证你能一次点亮。
2.1 接线图与电源管理
首先看接线。如果我们使用KY-9250模块的“智能串口模式”,接线就像原始文章里那么简单,VCC、GND、TX、RX四根线对接STM32的串口。但我们要玩“原始数据模式”,就需要连接I2C接口。MPU9250支持I2C和SPI,我们选用更常见的I2C。
接线清单如下:
- KY-9250/MPU9250模块 <--> STM32F103C8T6
- VCC -> 3.3V (极其重要:务必接3.3V!很多模块虽然标称兼容5V,但接5V时I2C电平可能不对,导致通信失败或损坏)
- GND -> GND
- SCL -> PB6 (STM32的I2C1时钟线)
- SDA -> PB7 (STM32的I2C1数据线)
- 如果你模块上有AD0引脚:接地(地址为0x68)或接VCC(地址为0x69),我们默认接地。
这里有个大坑我踩过:市面上有些KY-9250模块,为了兼容Arduino,板载了电平转换电路,即使你接5V,I2C引脚输出也是3.3V。但有些廉价模块没有这个电路。最保险的做法就是统一使用3.3V供电。同时,确保STM32和传感器共地,这是通信的基础。
2.2 开发环境与基础驱动
软件方面,我习惯用Keil MDK(也就是Keil uVision)加STM32标准外设库。标准库虽然老,但寄存器操作清晰,对于学习理解非常有帮助。当然,你用HAL库或者LL库也完全没问题,原理是相通的。
在Keil里新建一个工程后,第一件大事就是配置I2C引脚和初始化。我们以标准库为例,下面这段初始化代码我建议你存成模板:
#include "stm32f10x.h"
#include "i2c.h" // 假设你已将标准库中相关文件加入工程
void MPU9250_I2C_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENA

427

被折叠的 条评论
为什么被折叠?



