拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32平衡车源码解析:从姿态解算到三环PID调参实战

简介这是一份基于STM32的智能平衡车完整工程源码适合嵌入式初学者与机器人控制开发者学习参考。项目围绕MPU6050姿态采集、卡尔曼滤波数据融合以及直立PID与速度PID控制展开可帮助读者理解平衡车如何维持直立并响应速度指令。压缩包共167个文件大小13.35MB以C源码与头文件为主同时包含Keil工程配置、编译生成的hex/axf文件、原理图PDF和安卓端控制APK便于对照源码、烧录调试与上位机联调。目前已有1165人学习下载。通过分析该工程可以掌握STM32实时控制框架、传感器驱动编写、PID参数整定思路以及卡尔曼滤波的工程应用对后续开展机器人或物联网项目有直接参考价值。 很多人拿到一份STM32平衡车源码第一反应是赶紧编译下载然后发现车子要么疯狂抖成筛子要么刚站起来就直接原地起飞翻车。我最初折腾平衡车时也经历过这个阶段网上找了一大堆开源工程真正能稳定站起来的不多能解释清楚为什么这样写代码的更是少之又少。这篇文章基于我实际调试过的一份STM32平衡车源码把代码背后的设计思路、每个模块在整辆车里扮演的角色、以及从硬件组装到整车站立调参的完整链路全部梳理一遍。无论你是准备做毕业设计、参加电赛还是纯粹自己DIY一台平衡车这份内容都会比单纯跑通一个Demo有用得多。1. 平衡车源码的整体架构先把整个工程拆开看写平衡车程序最难的不是某个算法而是把传感器读取、姿态解算、控制环计算、电机输出这些任务在有限的MCU资源里有条不紊地组织起来。源码里用的是STM32F103C8T6这颗经典芯片72MHz主频、64KB Flash、20KB RAM跑一个三环控制绰绰有余但资源算不上宽裕所以代码结构必须清晰。1.1 源码模块划分与功能定位这份源码的工程目录非常规整核心模块包括模块文件职责主控入口main.c初始化外设、启动控制循环姿态解算imu.c / mpu6050.c读取MPU6050原始数据计算角度控制算法control.c直立环、速度环、转向环计算电机驱动motor.cPWM输出、方向控制、死区补偿编码器测速encoder.c读取定时器正交解码值电源监测power.c电池电压采样与低电量提示调试通信debug.c串口打印、蓝牙透传调参这种按功能拆分的做法最大的好处是调参方便。我在现场调车时只需要改control.c里的PID参数完全不用碰其他文件。很多新手容易把一个工程写成一千行的main.c后面改一个参数要找半天排查问题更是灾难。1.2 任务调度主循环加定时器中断的组合平衡车的控制任务对实时性要求很高角度环的计算周期不能东颠西倒否则车子会表现出莫名的抖动。源码采用的是经典方案定时器中断固定频率触发控制中断1ms一个周期也就是1kHz的控制频率主循环处理串口收发、蓝牙遥控、电压检测这些对实时性要求不高的任务这里有一个容易忽略的点为什么控制频率选1kHz而不是500Hz或者2kHz1kHz能满足平衡车控制的带宽需求同时给STM32留出足够的空闲时间处理其他任务。如果控制频率提得太高MPU6050的数据噪声反而会加大过低的频率则无法及时纠正倾斜趋势。// 伪代码定时器中断里的控制流程 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); Get_Angle(); // 读取MPU6050并解算角度 Get_Speed(); // 读取编码器计算转速 Balance_Control(); // 直立环 Velocity_Control(); // 速度环 Turn_Control(); // 转向环 Set_Motor_Output(); // 输出PWM } }控制中断里不塞任何延时函数、串口打印这类阻塞操作这是做平衡车源码的铁律。我见过不少别人的工程在中断里加了延时导致控制周期漂移车子站立时明显有一种呼吸感那种情况很难从算法层面补救。2. 姿态解算不用DMP自己写互补滤波能跑得更稳MPU6050这颗六轴传感器是平衡车项目的老朋友。网上不少源码直接调用I2C读取加速度计和陀螺仪数据再跑一个DMP库输出四元数。这套方案不是不能用但我实际对比下来在平衡车这种场景里自己写互补滤波不仅代码量更小响应速度和控制手感反而更好。2.1 加速度计和陀螺仪的分工逻辑加速度计测量的是比力静止时能准确输出重力方向从而算出倾角但一旦车子震动或加速加速度计数据里就混入了大量高频噪声。陀螺仪测量角速度积分后能得到角度但积分会漂移时间越长误差越大。互补滤波的核心思想很简单低频段信任加速度计高频段信任陀螺仪用一个权重系数把两者融合起来。角度 0.98 * (上次角度 陀螺仪角速度 * dt) 0.02 * 加速度计角度这里0.98和0.02的比例就是互补系数。系数越偏向陀螺仪角度越平滑但滞后越明显越偏向加速度计响应越快但噪声越强。源码里给的是0.98这个经典值实际调车时如果发现角度波动偏大可以把系数调到0.96试一下代价是响应略微变慢。2.2 MPU6050初始化与数据读取细节初始化阶段有个非常关键的步骤等待传感器稳定。MPU6050上电后需要一段时间完成内部自检源码里在初始化后延时了100ms左右再读取数据这个延时不能省。更重要的是一次性把所有寄存器配置好量程设为±2g和±250°/s数字低通滤波设为42Hz这样角度数据既平滑又不会有过大的延迟。读取时用I2C连续读取模式一次读完加速度计和陀螺仪的6个寄存器数据。如果分开读取中间的时间差会导致角速度和角度在时间上不对齐控制效果会有微妙的影响。// MPU6050数据读取与角度解算核心逻辑 void Get_Angle(void) { MPU6050_Read_Accel(); MPU6050_Read_Gyro(); // 加速度计求角度注意单位换算和方向定义 acc_angle atan2(acc_y, acc_z) * 180.0f / PI; // 陀螺仪角速度单位转换为 degrees/s gyro_rate gyro_x / 16.4f; // ±250°/s 量程对应的灵敏度 // 互补滤波融合 angle 0.98f * (angle gyro_rate * dt) 0.02f * acc_angle; }2.3 零漂校准千万别跳过的步骤陀螺仪在静止时输出并不是零而是有一个固定的偏置值这个值俗称零漂。如果源码里不处理零漂角度会在陀螺仪的带动下缓慢偏移平衡车会表现为站立几分钟后逐渐朝一个方向加速跑。源码的处理方式是在开机时让车子保持静止采集100次陀螺仪数据取平均把这个平均值作为零漂补偿值。这个操作必须在车体完全水平静止的状态下进行。我实际遇到过一个情况在倾斜的桌面上开机校准之后车子怎么调都站不正总是往一个方向偏。3. 控制核心三环联动缺一不可平衡车源码的控制部分是最值得细读的地方。很多人以为平衡车只有直立环实际跑下来会发现车子确实能站着但一推就倒站一会儿还会自己跑起来这就是缺了速度环的表现。3.1 直立环PD控制让车体维持平衡直立环的目标是让车体维持在目标角度通常是0度。但光有比例控制是不够的因为车体的惯性和重力会让系统产生振荡。源码里用了PD控制微分项相当于一个阻尼能压制振荡让系统收敛。直立环输出 Kp * (目标角度 - 当前角度) Kd * (目标角速度 - 当前角速度)这里微分项用陀螺仪直接输出的角速度而不是角度差值的微分因为陀螺仪角速度更快、噪声更小。这个细节看起来简单实际效果差异巨大新手容易犯的错误是对角度求微分作为D项结果放大了加速度计的噪声车身抖动明显加剧。3.2 速度环PI控制让车子不溜坡不跑飞纯直立环控制下车子受到外力或者重心稍微偏移就会一直加速直到倒地。速度环的作用是通过调整目标角度来调节车速比如车子前倾加速了速度环就输出一个向后倾斜的目标角度来抵消加速趋势。速度环输出 Kp_speed * 速度误差 Ki_speed * 速度误差积分速度环的输出叠加到直立环的目标角度上形成串级控制结构。在源码里速度环只用PI控制不加微分因为速度本身由编码器测量微分会引入较大的噪声。3.3 转向环简单独立的PD控制转向环是三个环里最独立的因为它不影响直立和速度控制。转向环读取遥控器的转向期望值与当前车身转向角速度做差经过PD控制后输出一个差速值叠加到左右电机的PWM上。转向输出 Kp_turn * (目标转向速度 - 当前转向角速度)这个环的实现逻辑很直白但在调试时有一个注意点如果转向的P参数调得过大车子正常前行时会蛇形走位。因为路面震动会被转向环当成转向动作放大所以转向环的增益通常比较保守。3.4 三环融合与输出限幅三个控制环的输出最终会融合成一个总的PWM输出左电机PWM 直立环输出 速度环输出 转向环输出 右电机PWM 直立环输出 速度环输出 - 转向环输出这里必须对总和做限幅处理。STM32定时器输出PWM的占空比范围有限如果超限会导致PWM突然翻转电机抖动甚至烧毁驱动芯片。源码里的限幅逻辑是一个分段饱和函数输出超过阈值时钳制到最大值。4. 电机驱动与编码器测速从PWM输出到精准速度反馈控制环计算出来的是一堆数值最终要让电机真的转起来并知道电机实际转得多快这中间涉及PWM输出和编码器测速两个关键环节。4.1 PWM频率选择与死区补偿平衡车直流减速电机的PWM驱动频率源码里用的是10kHz。为什么是10kHz因为这个频率超出了人耳听觉的敏感范围能避开电机转动时产生的啸叫声同时10kHz的PWM周期0.1ms远小于电机机械响应时间电机运行平滑。PWM控制的另一个坑是死区。电机在PWM接近0的区间内由于电机内部的静摩擦力和驱动芯片的输出特性实际电机不会转动。也就是说程序输出2%的占空比电机纹丝不动但输出5%时电机突然跳转这种非线性会让平衡车在零点附近不停微震。源码的做法是在每次输出前检查PWM值如果小于最小启动阈值就直接把输出清零如果大于阈值再叠加一个固定的补偿值。这个阈值和补偿值跟电机的具体型号、电池电压都有关系需要现场实测。调试方法很简单在电机悬空状态下缓慢增加PWM输出记录电机刚好开始转动的占空比值那就是死区阈值。4.2 编码器正交解码与速度计算编码器测速用的是STM32的定时器正交解码模式。电机编码器的A相和B相分别接入定时器的两个通道定时器自动根据AB相的相位差判断旋转方向并计数。硬件正交解码的好处是不占用CPU资源主循环直接读取计数寄存器就能拿到速度和方向。速度计算时要特别注意单位换算。编码器输出的单位是脉冲数要换算成实际转速公式是实际转速(rpm) 脉冲数 / (编码器线数 * 减速比 * 定时器分频系数)举个例子我的电机编码器线数是13减速比30那么电机输出轴转一圈产生13 * 30 390个脉冲。如果定时器采集周期是10ms计数值是39那么转速就是10转/秒换算成角速度就是每秒628弧度。这个数值换算直接影响速度环PID参数的物理含义很多新手调速度环时调不出来就是因为这里量纲错了。4.3 速度采集周期与控制周期同步源码里编码器测速的采集频率和控制频率保持一致都是1kHz。每次控制中断里读取一次计数器值并立刻清零计数器这样得到的就是最近1ms内的脉冲增量。如果测速周期和控制周期不同步速度反馈会滞后速度环容易出现振荡。这一点在源码注释里特别提到过。我见过一些工程把测速放在另一个慢速定时器里然后用全局变量共享给控制中断结果速度环反应滞后了十几毫秒车子前推后倒的收敛速度明显变差。5. 从零调参的完整流程让车子从躺平到站稳源码写得再好参数不对车子一样站不起来。调参是整个平衡车项目里最磨人但最有成就感的一步。根据这份源码的架构我建议按固定顺序挨个调不要跨级调。5.1 调参前必须完成的硬件检查调参之前先确认硬件没有问题否则后面遇到的所有异常都很难判断原因。我的检查清单包括电机线和编码器线是否接反。接反的话直立环会变成正反馈车子一启动就快速倒地PWM和编码器是否共地。不共地的情况下编码器数据会跳变实际测下来就是速度环无法稳定电池电压是否充足。欠压状态下电机输出力矩不足调好的参数换块电池又要重调重心高度是否合适。电池尽量贴近车轮轴线重心得越高需要的控制增益越大系统越难稳定5.2 第一步先调直立环让车子能短暂站立调直立环时先屏蔽速度环和转向环只保留直立环。把车子拿在手里左右倾斜感受电机的出力方向是否正确。电机应该抵抗倾斜方向让车体恢复到水平位置。直立环Kp从小往大调每次增加后把车子放在地面上尝试松手站立。如果车子原地低幅高频抖动说明Kp偏大如果车子像面条一样软绵绵没力气说明Kp偏小。找到能站立但明显来回晃动的位置后再增大Kd来压制晃动。Kd过大的表现是车体在平衡点附近高频抖振电机发热严重。我调的时候会在Kp合适后Kd按Kp的十分之一起步慢慢加。这一阶段的目标是让车子能原地站立超过3秒虽然有点晃但不会立刻倒。5.3 第二步加入速度环锁定位置直立环稳定后车子通常会缓慢朝一个方向加速跑速度环要解决的就是这个问题。先给一个较小的Kp_speed观察车子是否会把速度收敛为零。如果车子前进后过冲倒退来回震荡说明Kp_speed太大。如果车子依然缓慢溜坡需要增加Ki_speed来消除稳态误差。速度环Ki加得太快的副作用是出现明显的呼吸感车子周期性前俯后仰。这是积分项在起作用需要把小幅度晃动和真正发散的振荡区分开。一个判断方法是把车子拿起来悬空观察轮子是否按照某一周期来回转轻微周期转动说明速度环增益偏大。5.4 第三步转向环最后调保持航向稳定转向环相对独立调试速度快很多。把车子放在地上站稳给它一个偏航方向的扰动观察它是否会主动修正回来。转向环调好之后车子在前进过程中不会无故跑偏。整个过程我实际调了差不多三天每天两到三个小时。调参最忌讳的就是没有章法地乱调一次改多个参数出问题了也不知道是哪个参数引起的。我的经验是每次只调整一个参数记录当前值、调整方向和车子的响应变化。6. 实战踩坑复盘源码在真实环境里的崩溃与修复最后分享几个我在实际调试这份源码时踩过的坑每一个都是花了不少时间才定位到根因的写在这里给大家提个醒。6.1 角度跳变引发的起飞第一次给车上电时车子刚站稳就猛地加速倒地感觉像突然失去了平衡控制。用串口看角度数据发现角度在某个瞬间从正常值跳变到180度左右。排查后发现问题出在加速度计求角度的公式上。当车体倾斜接近90度时atan2函数的分母趋近于零角度计算会出现跳变。平衡车的机械结构会限制车体在±30度左右活动但上电瞬间车体可能处于任意角度必须在初始化时读取初始角度作为中值而不是简单假设车体水平。6.2 电池电压下降导致PWM输出失真电池满电时调好的参数骑了十分钟后车子开始出现低频抖动再过一会儿直接趴窝。用万用表量电池电压发现已经跌到6.8V左右电机端电压不足导致同样的PWM占空比下实际输出力矩明显下降。这个问题有两个解决方案一是在电源模块里做电压补偿根据当前电池电压动态调整PWM的映射比例二是给控制电压设置下限当电压低于某阈值时直接报警停机防止电机在低电压下堵转烧毁。我最后两个方案都做了平时测试用电压补偿低于保护值就强制停机。6.3 蓝牙串口调参引发的死机为了无线调参我在源码里加了蓝牙透传没想到车子一开蓝牙就频繁死机。查了很久发现问题是串口中断里处理字符串解析用了阻塞式函数导致控制中断被延误。解决思路很直接把串口收到的一整帧数据缓存到环形缓冲区解析任务放到主循环里处理。蓝牙只负责传输参数数值控制中断里用标志位判断是否需要更新PID参数更新动作也放到主循环执行。6.4 代码移植到其他平台时的建议如果你拿到这份源码想移植到自己的板子上首先要关注的是引脚映射。STM32的定时器通道和编码器通道之间不是随便连的必须查数据手册确认定时器的引脚映射表。比如TIM2的通道1和通道2接编码器另一组电机可能就要换成TIM3。其次是MCU型号差异。同样写GPIO_PinAFConfig函数不同型号的GPIO和AF编号可能不同。源码里的I2C和定时器配置用的是标准外设库如果换成HAL库API风格完全不同建议先跑通一个LED点灯工程再逐步添加外设。6.5 关于源码的二次开发思路这份代码跑通之后我往上面加了不少功能包括手机App蓝牙遥控、超声波避障、以及通过串口实时绘制角度波形。这些扩展都建立在原来清晰的模块划分上改起来非常顺手。平衡车项目最迷人的地方在于它把传感器、信号处理、控制理论和电机驱动这些嵌入式核心知识点浓缩在一个巴掌大的机械结构里。把这些代码一行行读懂把参数一个个调稳你对嵌入式的理解会上升一个台阶。所以我还是建议源码要跑但更要读把它变成自己的东西。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门