ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零实现两轮自平衡小车:STM32+MPU6050+双环PID全解析

2026/9/3 8:31:16 拓冰建站 浏览量
从零实现两轮自平衡小车:STM32+MPU6050+双环PID全解析 简介本资源是一套面向高校嵌入式系统课程设计、毕业设计及期末大作业的完整实践方案基于WHEELTEC B570两轮平衡小车平台聚焦STM32F1系列主控的闭环控制实现适用于具备C语言基础与单片机入门经验的学习者。压缩包共315个文件涵盖44个C源文件含HAL库驱动与MPU6050姿态解算核心逻辑、71个头文件定义传感器接口、PID参数及系统状态机、44个编译中间文件.o/.d及配套Makefile、链接脚本.ld、工程配置.ioc/.mxproject与固件输出.hex/.elf整体仅1.13MB轻量易部署。已有130人学习下载所有代码均经本地Keil/STM32CubeIDE编译验证实测可稳定运行项目评审得分98分文档详述硬件连接、DMP姿态融合原理、PID调参过程与故障排查要点结构清晰、注释充分助读者深入理解平衡控制算法实现与嵌入式软硬协同调试全流程。1. 项目背景与核心价值最近在整理过去的项目资料翻到了大学时期一个让我印象深刻的课程设计——基于WHEELTEC B570套件的两轮自平衡小车。这个项目当时拿了高分更重要的是它几乎涵盖了嵌入式开发从硬件到软件、从理论到实践的所有核心环节。今天我想把这个项目的完整实现思路、源码解析以及那些在官方文档里不会写的“踩坑”经验系统地分享出来。无论你是正在寻找课程设计灵感的在校学生还是希望深入理解PID控制、姿态解算等经典算法的嵌入式爱好者这篇文章都能提供一个从零到一的完整参考。WHEELTEC B570是一个经典的平衡小车入门套件它集成了STM32主控、MPU6050六轴传感器、直流电机与编码器构成了一个完整的闭环控制系统。这个项目的核心价值在于它不是一个简单的“点灯”或“串口通信”实验而是一个动态的、实时的、多任务协同的复杂系统。你需要处理传感器数据融合、设计控制算法、驱动执行机构并让整个系统稳定运行。这个过程正是嵌入式开发的精髓所在。接下来我将从硬件框架拆解开始带你一步步走进平衡小车的世界。2. B570平衡小车硬件系统深度解析一套平衡小车本质上是一个倒立摆系统。要让这个“摆”不倒我们需要精确感知它的倾斜角度并快速驱动轮子做出反应。B570套件为我们搭建好了这个物理平台理解每个部件的作用是写好程序的第一步。2.1 核心控制器STM32F103C8T6项目主控采用的是经典的“蓝桥杯”核心板MCU——STM32F103C8T6。选择它有几个很实际的原因首先它基于ARM Cortex-M3内核72MHz的主频对于处理传感器数据和运行PID算法绰绰有余其次它拥有丰富的外设特别是我们需要的定时器用于产生PWM波驱动电机和编码器测速、ADC虽然本项目未使用但为扩展留有余地以及多个USART用于调试和无线通信最后它的社区资源极其丰富任何问题几乎都能找到解决方案这对于课程设计这种有时间限制的项目来说至关重要。在实际开发中我强烈建议你一开始就规划好引脚分配。比如电机PWM输出需要用到高级定时器TIM1的通道1和通道2来控制一个电机的正反转另一个电机则可能使用TIM8。编码器接口模式则需要配置为定时器的编码器模式通常使用TIM2和TIM3。把这些对应关系在一张表格里列清楚能避免后续接线和代码配置时的混乱。2.2 姿态感知核心MPU6050六轴传感器姿态感知是整个系统的“眼睛”。MPU6050同时集成了三轴加速度计和三轴陀螺仪。加速度计测量的是载体在三个轴向上的加速度在静止时其数据可以反映出重力加速度的方向从而计算出载体相对于水平面的倾斜角俯仰角和横滚角。但加速度计对运动非常敏感小车一旦移动电机振动和线性加速度就会引入巨大噪声。陀螺仪测量的是角速度通过对角速度积分可以得到角度变化但它存在漂移误差时间一长积分累积的误差会越来越大。因此单独使用任何一种传感器都无法获得稳定、准确的角度。这就需要用到传感器融合算法。在B570的源码中最常见的是互补滤波或卡尔曼滤波。互补滤波实现简单计算量小其核心思想是利用高通滤波器提取陀螺仪数据中的高频可信部分快速变化的角度利用低通滤波器提取加速度计数据中的低频可信部分长期稳定的角度再将两者按一定权重融合。卡尔曼滤波则是一种最优估计算法能更精确地估计出系统的真实状态但数学模型和参数调整更为复杂。对于课程设计从互补滤波入手是更稳妥的选择。2.3 执行与反馈机构直流电机与编码器小车通过两个直流减速电机来执行控制指令。STM32通过定时器产生PWM波来控制电机驱动芯片如TB6612FNG的输入从而调节电机的转速和方向。这里有一个关键点电机驱动需要隔离。单片机IO口的驱动能力很弱无法直接驱动电机TB6612这类驱动芯片起到了功率放大的作用同时将控制电路与动力电路隔离开保护了MCU。仅有开环控制是不够的。我们给电机一个PWM值期望它转多快但它实际转了多少、速度是否达标我们需要知道。这就是编码器的作用。B570小车通常配备的是增量式光电编码器。电机转动时编码器会输出两路相位差90度的方波A相和B相。STM32的定时器可以配置为“编码器接口模式”自动根据A、B相的脉冲数和相位关系计算出计数器的增减从而得到电机的转速和方向。速度环PID控制正是基于这个反馈值来工作的。3. 软件架构与多任务调度设计面对传感器数据读取、姿态解算、PID计算、电机控制等多个需要周期性执行的任务如何安排它们的执行顺序和时机是保证系统实时性和稳定性的关键。在资源有限的STM32上我们通常不会上实时操作系统RTOS而是采用前后台超级循环配合定时器中断的架构。3.1 定时器中断作为系统心跳我通常设置一个基础定时器如TIM4产生1ms或2ms的定时中断。这个中断服务函数ISR就像系统的心脏以固定的频率跳动。在这个ISR里我们只做最必要、最紧急的事情读取MPU6050的原始数据通过I2C总线读取加速度计和陀螺仪的原始值。这里要注意I2C通信相对较慢读取6轴数据可能需要几百微秒要确保整个ISR的执行时间远小于中断周期。执行姿态解算互补滤波将读取到的原始数据进行单位转换、校准偏移后立即进行互补滤波计算得到当前估计的俯仰角Pitch和角速度。这一步必须在ISR内完成因为角度值是所有控制的基础需要最高的更新频率和确定性。设置任务标志位例如设置一个angle_update_flag 1告诉主循环“新的角度数据已就绪”。注意在ISR里千万避免进行浮点数运算如果MCU没有FPU、复杂的数学函数调用或任何可能阻塞的操作如等待式延时。保持ISR短小精悍是铁律。3.2 主循环中的任务协调主循环while(1)则负责处理那些实时性要求稍低或者计算量较大的任务它通过查询在ISR中设置的各种标志位来触发执行while(1) { if (angle_update_flag) { angle_update_flag 0; // 1. 进行角度环PID计算 pid_angle.Calculate(目标角度, 当前角度); // 角度环的输出作为速度环的目标值 target_speed pid_angle.output; // 2. 读取编码器值计算实际速度 actual_speed Read_Encoder_Speed(); // 3. 进行速度环PID计算 pid_speed.Calculate(target_speed, actual_speed); // 4. 将速度环PID输出转换为电机PWM值并执行输出 Set_Motor_Pwm(pid_speed.output); } // 其他任务如蓝牙串口指令解析、状态指示灯刷新等 Uart_Command_Parse(); LED_Blink(); }这种架构清晰地将高频、硬实时的任务数据采集、滤波放在中断中将稍低频的控制算法和输出放在主循环有效利用了CPU资源保证了控制的及时性。4. 双环PID控制算法的实现与调参实战平衡小车的稳定性依赖于一个经典的双环PID控制结构内环是速度环外环是角度环。这是整个项目的算法核心也是调参的“重灾区”。4.1 角度环外环立起来的根本角度环的输入是期望角度通常为0度即直立状态与MPU6050解算出的当前实际角度之间的偏差。它的输出是一个速度值这个速度值将作为速度环的目标。可以这样理解当小车向前倾倒时角度为正角度环会计算出一个正向的速度目标意思是“需要让轮子向前转动以追赶重心使车身回正”。角度环通常使用PD控制器。比例项P提供与角度偏差成比例的恢复力P值越大小车回正的力量越强但过大会引起剧烈抖动。微分项D则与角度变化的速率角速度相关它起到阻尼作用抑制小车在平衡位置附近的振荡。D参数是角度环稳定的关键它能预见小车的运动趋势提前施加反方向的力来“刹车”。调参心得先调P后调D。将小车用手扶住慢慢增大P值直到小车有明显的回正力但开始高频振荡。然后加入D从小值开始慢慢增大你会发现振荡逐渐减弱直至消失小车能稳定在你手扶的任意角度。此时角度环基本调好。特别注意MPU6050解算出的角度和角速度的符号方向必须与电机转动方向对应否则正反馈会瞬间让小车飞出去。4.2 速度环内环抑制发散的守护者如果只有角度环小车理论上可以平衡但就像一个独轮车它的重心位置是不确定的会缓慢地向一个方向加速移动直到倒下。速度环的作用就是消除这种“静差”和抑制发散。它以角度环输出的速度作为目标以编码器测得的实际电机转速作为反馈计算出控制量。速度环通常使用PI控制器。积分项I在这里至关重要它能累计长时间的速度偏差从而产生一个控制量来抵消使小车持续移动的力如地面轻微不平、电机差异等。没有I项小车会缓慢地越走越快但I项太强又会引起低频振荡。4.3 参数整定流程与“黑盒”测试法调参是一个系统工程我总结了一个可复现的流程归零将所有PID参数设为0。确保小车在平坦地面上电后角度解算稳定可以通过串口打印观察。角度环P用手扶住小车给一个较小的角度逐渐增大角度环P让小车有回正趋势。调到轻推小车后它能自己回到平衡点附近但会来回摆动不止。角度环D逐渐增大角度环D抑制摆动。目标是轻推后小车能在1-2个周期内稳定下来无明显振荡。此时手扶小车它应该能在各个角度保持。速度环P将小车放在地上用手轻轻推动它。逐渐增大速度环P目标是让小车在受到推力后能较慢地移动一小段距离然后停下而不是立刻静止或加速跑开。此时角度环的稳定性可能会被破坏需要微调角度环D。速度环I观察小车是否会在无人碰触的情况下自己缓慢地向一个方向移动。如果是则缓慢加入速度环I。I值要非常小每次调整后观察几分钟。目标是消除缓慢的漂移但绝不能引起周期性的前后“点头”振荡。联调与微调进行综合测试比如快速拨动小车、在轻微斜坡上测试。微调各个参数在响应速度和稳定性之间取得平衡。一个实用的“黑盒”测试技巧调参时用手机慢动作视频录制小车的轮子。通过回放你可以清晰地看到电机在平衡过程中的细微抖动、加速和减速过程这比单纯凭感觉观察要精确得多。5. 源码关键模块剖析与编程细节光有理论不够我们直接深入源码看看关键功能是如何实现的。这里我结合B570常见的代码结构讲解几个最容易出问题的模块。5.1 MPU6050数据读取与DMP库的使用MPU6050有两种使用方式一是直接读取原始数据自己写融合算法二是使用其内置的DMP数字运动处理器。DMP是芯片内部的一个微处理器可以直接运行官方的运动处理库输出解算好的四元数大大减轻了MCU的负担。在源码中初始化MPU6050后如果使用DMP通常会调用dmp_load_motion_driver_firmware()等函数来加载固件。之后在中断里不再需要进行复杂的滤波计算只需读取FIFO先入先出缓冲区中的四元数数据然后将其转换为欧拉角即可。// 示例在定时器中断中读取DMP数据 if (mpu_dmp_get_data(pitch, roll, yaw) 0) { // pitch, roll, yaw 即为解算出的姿态角 angle_update_flag 1; }避坑指南DMP虽然方便但初始化流程繁琐且对I2C时序要求严格。一个常见的问题是初始化失败。务必确保I2C的GPIO引脚已正确配置为上拉模式或外部接上拉电阻。在MPU6050_Init()函数后给予足够的延时如100ms让芯片稳定。仔细检查每一步初始化函数的返回值很多源码里缺少错误判断一旦某步失败后续全部失效导致角度始终为0。5.2 编码器速度计算与滤波编码器值通过定时器自动计数获得我们需要将其转化为速度。最直接的方法是“M法测速”在固定的定时周期如10ms内读取编码器计数值的变化量delta_cnt。速度speed delta_cnt / 时间。但这里有两个问题一是电机转速很低时delta_cnt可能为0导致速度计算为零二是单个周期的计数可能存在偶然误差。因此通常采用滑动平均滤波。例如维护一个包含最近5个速度值的数组每次计算的新速度存入数组并剔除最旧的值然后对数组内所有值求平均作为最终输出速度。这能有效平滑数据提高速度环的稳定性。#define SPEED_FILTER_NUM 5 int32_t speed_filter_buf[SPEED_FILTER_NUM] {0}; uint8_t speed_filter_index 0; int32_t Get_Filtered_Speed(int32_t raw_speed) { speed_filter_buf[speed_filter_index] raw_speed; speed_filter_index (speed_filter_index 1) % SPEED_FILTER_NUM; int32_t sum 0; for(int i0; iSPEED_FILTER_NUM; i) { sum speed_filter_buf[i]; } return sum / SPEED_FILTER_NUM; }5.3 PID控制器的代码实现一个健壮且易于调试的PID实现应该包含抗积分饱和和输出限幅。以下是位置式PID的一个简洁实现typedef struct { float Target; // 目标值 float Current; // 当前值 float Err; // 当前误差 float Err_Last; // 上次误差 float Err_Sum; // 误差积分 float Kp, Ki, Kd; // PID参数 float Output; // 输出值 float OutputMax; // 输出上限 float OutputMin; // 输出下限 float IntegralMax; // 积分限幅 } PID_TypeDef; void PID_Calculate(PID_TypeDef *pid) { pid-Err pid-Target - pid-Current; // 比例项 float p_out pid-Kp * pid-Err; // 积分项带抗饱和 pid-Err_Sum pid-Err; // 积分限幅防止深度饱和 if (pid-Err_Sum pid-IntegralMax) pid-Err_Sum pid-IntegralMax; if (pid-Err_Sum -pid-IntegralMax) pid-Err_Sum -pid-IntegralMax; float i_out pid-Ki * pid-Err_Sum; // 微分项使用不完全微分可减少高频干扰 float d_out pid-Kd * (pid-Err - pid-Err_Last); pid-Err_Last pid-Err; // 计算总输出并限幅 pid-Output p_out i_out d_out; if (pid-Output pid-OutputMax) pid-Output pid-OutputMax; if (pid-Output pid-OutputMin) pid-Output pid-OutputMin; }关键点IntegralMax积分限幅非常重要。当小车长时间偏离平衡点比如被拿在手里误差积分会变得非常大一旦放下这个巨大的积分量会瞬间导致电机全速转动造成“积分饱和”现象。限幅可以有效避免这个问题。6. 项目调试技巧与常见问题排查调试嵌入式系统尤其是动态控制系统需要方法和工具。以下是我在完成这个项目过程中积累的一些实用技巧。6.1 利用串口绘图进行可视化调试单纯打印数字很难直观感受角度、速度、PWM输出的变化趋势。利用串口上位机软件如SerialPlot、匿名上位机、VOFA等的绘图功能可以将多个变量实时绘制成曲线图。例如你可以同时输出以下数据到串口用逗号分隔当前角度,目标角度(0),角度环输出,实际速度,目标速度,速度环输出在软件中配置好通道就能看到角度曲线是否平滑有无毛刺检查MPU6050滤波效果角度环PD输出波形是否合理P和D的作用是否明显速度环的响应是否及时能否跟上角度环输出的目标速度变化整个系统受到扰动后各曲线是如何恢复稳定的这是调参和排查问题的利器能让你“看见”算法的运行状态。6.2 典型问题与解决方案问题一小车一上电就朝一个方向猛冲完全不受控。可能原因1电机接线或驱动芯片控制逻辑反了。检查Set_Motor_Pwm()函数中正负PWM值对应的电机转动方向是否符合物理预期。一个快速测试方法是将小车悬空用手轻轻转动轮子观察编码器计数值是增加还是减少确保方向正确。可能原因2角度或角速度的符号定义与控制逻辑不匹配。这是最常见也最隐蔽的坑。确保小车前倾时MPU6050解算出的角度为正值此时为了回正需要让轮子向前转产生一个向前的加速度。如果符号反了就会形成正反馈瞬间加速跑飞。务必在调参前用手扶住小车通过串口打印确认角度符号并手动给一个小的PWM值观察小车运动方向是否符合回正逻辑。问题二小车能站住但抖动非常厉害伴有高频“滋滋”声。可能原因角度环P值过大或D值过小。过大的P值会让系统“反应过敏”不断过度修正。首先尝试减小P值。如果抖动是高频的通常是D值阻尼不足无法抑制振荡适当增大D值。深层排查检查PWM频率。电机驱动芯片TB6612的推荐PWM频率在1kHz到几十kHz。频率太低如几百Hz电机会有明显的啸叫声频率太高开关损耗会增大。通常设置在5kHz-20kHz之间比较合适。检查定时器初始化代码中PWM频率的配置。问题三小车站得很稳但会缓慢地、不受控制地向一个方向移动。可能原因1速度环I参数需要调整。这是速度环I项的典型作用场景。缓慢地加入一个很小的Ki值观察漂移是否改善。可能原因2机械结构不对称或电机性能有差异。即使PID参数相同两个轮子也可能因为摩擦力、齿轮间隙不同而导致力不平衡。可以尝试为左右电机设置微小的PWM偏移量进行补偿。可能原因3MPU6050模块未水平安装。如果传感器本身相对车体有轻微倾斜它认为的“零角度”就不是真正的物理垂直位置。小车会为了维持这个错误的零角度而持续移动。需要校准或物理调平。问题四通过蓝牙或串口发送指令小车无反应或反应异常。可能原因串口中断服务函数编写不当影响了主循环或定时器中断的时序。在串口接收中断USARTx_IRQHandler中只做最简单的事情将数据存入缓冲区并设置一个数据到达标志。复杂的数据解析工作应放到主循环中根据标志位来处理。绝对避免在中断中使用printf或任何可能引起阻塞的延时函数。7. 从课程设计到能力提升项目扩展思路完成基本的平衡站立和速度控制只是一个开始。这个平台有巨大的扩展潜力可以深化你对嵌入式系统各个层面的理解。扩展一加入无线遥控与姿态控制通过蓝牙模块如HC-05/06或2.4G无线模块如NRF24L01为小车增加遥控功能。你可以实现前进、后退、转向。更有挑战性的是实现“姿态遥控”手机APP通过自身的IMU传感器获取姿态转换为控制指令发送给小车实现“身体前倾小车就前进”的直观控制。这涉及到手机端编程、无线通信协议设计、指令解析与融合等多个知识点。扩展二实现定点和循迹功能在车体上加装超声波模块或TOF激光测距传感器实现距离墙壁特定距离的定点平衡。或者加装红外对管或摄像头模块实现循线行驶。这引入了新的传感器数据类型需要你设计更高层的决策逻辑比如在平衡控制的基础上叠加一个位置环PID。扩展三探索更先进的控制算法PID是经典但并非唯一。你可以尝试模糊PID将PID参数根据误差和误差变化率动态调整以适应更复杂的工况。线性二次型调节器LQR这是一种基于状态空间模型的最优控制方法需要建立小车的精确数学模型并进行状态反馈矩阵的设计。虽然实现复杂但对理解现代控制理论极有帮助。自抗扰控制ADRC一种不依赖于精确模型能主动估计并补偿系统内外“总扰动”的强鲁棒性控制方法。在平衡小车上实现ADRC是一个非常有深度的挑战。扩展四系统优化与工程化实践低功耗设计尝试在保持平衡的前提下降低CPU主频优化代码测量系统功耗。状态机设计将小车的行为如初始化、等待、平衡、遥控、摔倒保护用状态机来管理使程序逻辑更清晰。使用RTOS尝试将传感器读取、控制算法、通信等任务移植到FreeRTOS等实时操作系统上体验多任务编程和系统资源管理。这个基于WHEELTEC B570的平衡小车项目就像一把钥匙打开了一扇通往嵌入式控制领域的大门。从最开始的硬件接线、软件配置到中间的算法调试、问题排查再到最后的稳定运行和功能扩展每一步都充满了挑战和收获。它强迫你去理解硬件时序、软件架构、数学算法和物理模型是如何协同工作的。我个人的体会是调参调到最后已经不只是在调几个数字而是在和这个物理系统进行“对话”你通过参数告诉它该如何响应这个世界它则通过运动状态反馈给你它的“感受”。这个过程远比最终那个能稳稳站立的小车本身更有价值。希望这份结合了源码分析与实战经验的总结能帮助你少走弯路更深入地享受嵌入式开发的乐趣。如果在复现过程中遇到具体问题不妨多看看数据曲线多从物理原理层面思考答案往往就在其中。本文还有配套的精品资源点击获取