资讯动态

STM32平衡小车代码资源:从姿态解算到三环PID实战调试

发布时间:2026/10/15 12:45:14 来源:尧图企业网站定制
简介面向STM32开发者的两轮平衡小车完整代码资源覆盖从传感器采集到电机驱动的全链路实现适合学习嵌入式实时控制与PID调参的入门及进阶者。压缩包共857个文件、约47.51MB以C源文件、H头文件和MDK工程文件.uvprojx为主同时包含编译生成的.o/.d中间文件、.hex/.axf烧录文件、IOC图形化配置、MPU6050相关代码以及txt/md说明文档方便直接打开工程阅读或移植。核心内容包括STM32 HAL库外设初始化、MPU6050陀螺仪与加速度计数据读取、PID控制器代码、PWM电机驱动和蓝牙/ADC扩展模块开发者可对照例程理解平衡车直立环、速度环的调试思路。已有5960人学习下载工程可直接运行并二次开发能节省环境搭建与底层驱动编写时间适合在Keil或STM32CubeIDE中复现平衡小车项目。1. stm32平衡小车代码资源先分清你要的是能站起来还是能调好搜「stm32平衡小车代码资源」的人多数是同一个诉求手里有块板子、两个电机、一个姿态传感器想把小车调站住。这套资源本质上是一份把姿态解算、三环 PID、PWM 驱动串起来的嵌入式工程模板解决的是从电机能转到小车能直立不倒这段最磨人的链路。适合三类人做课程设计的学生、刚转嵌入式控制方向的开发者、备赛但不想从零写驱动的人。先说一个反直觉结论这套资源里最不值钱的是代码本身最值钱的是参数背后的取舍——同一份代码换个电池电压、换张桌子可能从站 3 分钟变成秒倒。所以下文不打算替你复述代码而是带你把资源拆开每部分该改哪里、参数按什么逻辑调、翻车了先查哪一环。2. 看懂平衡车代码的控制链路姿态解算与三环 PID 的分工2.1 从 MPU6050 到倾角姿态解算在代码里的数据流所有平衡车代码的第一段数据流都一样姿态传感器通过 I2C 输出加速度计和陀螺仪的原始数据代码把它们换算成车身的俯仰角这个角度再喂给 PID。注意这里只需要俯仰角一个量横滚角用不上偏航角也只留给转向环。因为平衡车本质上是单自由度稳定问题代码里 90% 的滤波计算都围绕 pitch 展开。加速度计测的是重力向量在三个轴上的投影静止时用反正切就能算出倾角但它怕振动电机一转全是噪声陀螺仪测角速度积分出来的角度平滑可每秒都在漂。所以真正干活的是中间那层融合滤波常见做法三选一传感器内部 DMP 直接输出四元数代码读 FIFO 再转欧拉角在 MCU 上跑互补滤波或者上卡尔曼滤波。三者的选择直接决定你后面调试的体验。方案MCU 占用代码量数据延迟适合场景DMP 硬件融合低少中输出率一般到不了 1kHz不想碰滤波原理先跑通再说互补滤波低很少低两次乘加绝大多数开源平衡车工程卡尔曼滤波高多略高学习滤波原理或对平滑度要求极端主环我不推荐 DMP。平衡车直立环跑 500Hz 以上很常见DMP 的数据输出率跟不上这个节奏而且它的初始化序列很长出问题不好定位。互补滤波是平衡车代码资源里的绝对主力后面第 4 章会给出可直接抄的实现。但无论用哪种拿到资源后的第一个动作都一样把解算出的角度通过串口发出来。板子放平角度应稳定在 0°±0.5°用手倾斜 20°打印值应在 0.2 秒内跟到 20°±1°快速转 360°角度不应出现不可恢复的偏移。三点同时满足姿态链路才算干净否则后面调 PID 全是对着脏数据调怎么调都是白费。单位约定也在这里一次定死角度统一用度角速度用度每秒PWM 用定时器计数。一套代码里混了度和弧度是最隐蔽的 bug编译不报错表现就是别人的 180 能站我的 3000 还抖。MPU6050 出厂常见配置是 ±2000dps、±2g对应灵敏度 16.4 LSB/(°/s) 和 16384 LSB/g代码里这两个常量错一个角度就错一个固定倍数而且只会在某个参数区间里露出马脚。2.2 直立环、速度环、转向环三环各自在代码里管什么一套完整的平衡车代码里有三个独立的 PID 计算但它们是串级关系不是并列关系。直立环是内环每 1~5ms 跑一次输入是目标倾角减当前倾角输出直接进 PWM管的是别倒下速度环是外环每 20~50ms 跑一次输入是速度偏差输出是给直立环的目标角修正管的是别漂走转向环独立处理偏航角速度输出左右轮差分管方向。控制环周期输入输出缺失表现直立环1~5ms倾角偏差PWM根本站不住速度环20~50ms速度偏差直立环目标角修正站住但越跑越快转向环5~20ms偏航角速度左右轮差分站得住但方向失控三环的时间尺度分离是刻意为之。倾斜是发散量直立环响应慢 10ms车身已经倒出好几度速度是位置的一阶积分本身就是慢变量速度环跑快了反而和直立环打架。所以判断一份资源完整不完整别数文件数直接搜速度环和编码器两个关键词同时出现才算三环齐全。新手最容易忽略的是目标倾角通常不是 0而是 1°~2° 前倾。物理含义是让车身轻微往前进方向倒给速度环留出用前倾纠速度的调节空间。如果目标角写死 0速度环只剩半边行程车一给速度指令就站不住。代码里找这个偏置常量通常叫 ANGLE_TARGET 或 BALANCE_OFFSET。动手确认控制周期的做法在控制中断函数里翻转一个 GPIO用示波器或逻辑分析仪量频率。代码里写的周期和实测周期对不上是这类资源最常见的隐藏问题后面每次调参都建立在这个时间基准上基准错了参数再准也没用。举一个数量级参考假设直立环 Kp2005° 倾角误差时输出 1000 个 PWM 计数在 10kHz、ARR7199 的配置下约等于 13.9% 占空比刚好驱动常见直流减速电机缓慢转动。这个对应关系能帮你快速判断一套资源的参数是否离谱。3. 对照最小工程读懂代码资源.ioc 配置与四个关键文件3.1 工程文件清单先认人再办事典型的资源压缩包解压后哪怕几十个文件核心就四类main 负责初始化和主循环imu 驱动负责传感器读写和姿态接口pid/control 负责三环计算motor 负责 PWM 输出和编码器读取。带 .ioc 文件的资源是加分项用图形化初始化工具打开就能直接看到完整外设配置比读代码快得多。文件/模块职责入手检查点main.c时钟、外设初始化、主循环控制函数在中断里还是 while 里调用imu 驱动I2C 读写、零偏校准角度单位是度还是弧度符号是否与电机方向一致pid/control直立、速度、转向计算目标角是 0 还是带前倾偏置motorPWM 输出、编码器计数PWM 极性方向编码器倍频与轮周长换算给新手的建议是拿到资源先做纸面走读把初始化顺序、哪个定时器触发中断、中断里调了哪个函数这三件事写下来。我见过最坑的一种资源主循环里放了个 while(1) 死等按键控制函数却放在另一个定时器中断里两个逻辑相互抢时间改哪里都感觉车不理我。走读时重点问三个问题。第一控制函数是在中断里还是主循环里调用的平衡车必须走定时器中断这条路任何把控制放主循环里的资源即使能站也站不稳因为主循环周期不可控。第二中断里有没有 printf、延时函数这类阻塞调用有的话要么改掉要么换资源串口打印一次在低主频芯片上可能吃掉几百微秒。第三浮点运算有没有在中断里大量出现不带 FPU 的芯片做浮点除法很贵2ms 中断里跑几十次浮点除法时间片就没了。3.2 用初始化工具核对三大配置I2C、PWM 与控制定时器别急着编译先核对三组配置I2C 速率设为 400kHz太慢会让姿态数据到达率不足PWM 频率建议 10~20kHz低于 5kHz 电机会有可闻噪音占空比抖动还会直接串进直立环控制定时器中断优先级必须高于串口否则串口打印会随机拉长控制周期。这三项在 .ioc 或初始化代码里各占几行却是后面所有玄学问题的物理根源。PWM 频率的计算式是 fPWM 定时器时钟 / (PSC1) / (ARR1)。以常见 72MHz 主频的芯片为例// 目标 10kHz PWM主频 72MHzPSC0ARR7199 // 72,000,000 / (01) / (71991) 10,000 Hz TIM_HandleTypeDef htim2; htim2.Init.Prescaler 0; htim2.Init.Period 7199; htim2.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_PWM_Init(htim2);Prescaler 和 Period 就是要对着代码资源检查的两个参数。如果资源里写的是 PSC71、ARR99算出来同样是 10kHz但占空比分辨率从 1/7200 变成 1/100细调死区补偿时完全不是一个手感。统一这个基准再动手调参是避免别人参数能用我的一用就抖的第一步。配置项推荐值异常症状I2C 速率400kHz角度跳变、偶发读不到传感器PWM 频率10~20kHz低于 5kHz 电机噪音大、高频敏感直立环周期1~5ms更慢则必须整体降参数控制中断优先级高于串口打印时车身抽动编码器模式4 倍频速度数值系统性偏小 3/4注意最后一行很多资源在初始化工具里默认把编码器配成 1 倍频代码里却按 4 倍频换算结果速度环看到的数值永远是真实值的四分之一症状就是速度环怎么调都没反应。这个表格里的每一项都是这类资源的高发问题建议逐行核对完再烧录。3.3 让小车点头开环验证电机方向与 PWM 极性在烧任何 PID 之前先做开环方向验证这是所有平衡车调试里最划算的十分钟。把车架起来让轮子悬空在代码里把直立环输出临时改成固定占空比观察轮子转向和命令方向是否一致。步骤很简单先给左右轮一个相同的正占空比确认都正转再给左轮正、右轮负确认差速方向最后手动倾斜车身对比角度符号和应该响应的方向。// 开环验证代码临时绕过 PID固定输出固定占空比 void openloop_test(void) { set_motor_left(1000); // 占空比 1000/7200 ≈ 13.9% set_motor_right(1000); // 两个轮子都应向前转 HAL_Delay(500); set_motor_left(-1000); // 左轮反转 set_motor_right(-1000); // 右轮反转应同步 HAL_Delay(500); set_motor_left(0); set_motor_right(0); }注意 set_motor 的参数含义必须查资源有些代码里正值是前进有些是后退有些直接写 CCR 寄存器的值有些先转成百分比。方向搞反的典型症状是上电就倒而且往同一个方向加速倒。我在这个环节的建议是方向错了优先改代码里的符号不要改电机接线。杜邦线和焊接点在震动环境下很容易接触不良把方向逻辑固定进代码排查面至少少一半。顺手加一个读传感器 ID的动作imu 驱动初始化时读设备地址寄存器返回值不对就直接停在这里排查接线和上拉电阻而不是继续往 PID 里钻。这个寄存器把 I2C 接线、供电、传感器是否虚焊三件事一次验完是所有资源里最便宜的自检。4. 核心代码模块互补滤波与串级 PID 的可抄实现4.1 互补滤波陀螺仪积分与加速度计修正附零偏校准平衡车主环不用 DMP 的原因前面说过延迟偏高相位裕度受损表现为更容易抖。互补滤波在 MCU 上只做两次乘加延迟可以忽略这是它统治平衡车代码资源的原因。最常见的实现长这样// 互补滤波一阶融合dt 必须与调用周期一致 #define FILTER_K (0.95f) // 陀螺仪权重 0.95加速度计权重 0.05 static float angle_pitch 0.0f; float complementary_filter(float gyro_rate, float accel_angle, float dt) { // 角度 上次角度积分 加速度计缓慢修正 angle_pitch (angle_pitch gyro_rate * dt) * FILTER_K accel_angle * (1.0f - FILTER_K); return angle_pitch; } // accel_angle 由原始值换算 // accel_angle atan2f(accel_x, accel_z) * 57.29578f;逻辑是陀螺仪负责短期角度更新积分无延迟但会漂加速度计负责长期拉回噪声大但绝对准。两个信号按 0.95 和 0.05 加权等于把短期信陀螺、长期信加速度写成了两行代码。参数只有两个FILTER_K 和 dt。dt 必须等于控制周期控制 2ms 触发一次就传 0.002f否则角度要么偏软要么带锯齿。FILTER_K 我一般从 0.90 起步越小越跟手但越吵越大越平滑但越飘调到 0.95 以上后把车快速转 180° 再回正角度应该基本不产生永久偏移。零偏校准是姿态链路里最容易漏的一环绝大多数资源都有但位置放得不对// 陀螺仪零偏上电静止采样 200 次取平均 // 必须在车身水平静止时执行执行期间不能碰车 int32_t bias_sum 0; for (int i 0; i 200; i) { bias_sum read_gyro_raw(); // 绕 pitch 轴的角速度原始值 delay_1ms(); } gyro_bias bias_sum / 200; // 之后每个控制周期gyro_rate (read_gyro_raw() - gyro_bias) / 16.4f;这段必须放在直立环启动之前而且执行期间车身静止。很多人把零偏采样放在主循环里反复跑每次上电的校准值都不一样表现就是这次能站下次上电就倒而且毫无规律可循。校准值稳定之后再回到串口看角度这个方法才算闭环。4.2 串级 PID直立环内环与速度环外环的 C 实现串级 PID 的正确打开方式不是把两个 PID 串行调用而是让内环跑得快、外环跑得慢外环输出叠加到内环的设定值上。下面这段是平衡车代码资源里最常见、也最容易被改坏的部分// 直立环PD 控制微分直接用陀螺仪角速度不用角度差分 float balance_pid(float angle_target, float angle_pitch) { float err angle_target - angle_pitch; float out BALANCE_KP * err - BALANCE_KD * gyro_rate; if (out PWM_MAX) out PWM_MAX; if (out -PWM_MAX) out -PWM_MAX; return out; } // 速度环PI 控制输出是目标角的修正量 float speed_pid(float speed_measure, float speed_target) { static float speed_integral 0.0f; float err speed_target - speed_measure; speed_integral err * SPEED_KI; if (speed_integral SPEED_INT_LIMIT) speed_integral SPEED_INT_LIMIT; if (speed_integral -SPEED_INT_LIMIT) speed_integral -SPEED_INT_LIMIT; return SPEED_KP * err speed_integral; }两个细节值得强调。第一直立环的微分项直接用陀螺仪角速度而不是把角度做差分——角度差分会把加速度计的噪声放大几十倍这是很多资源抖得停不下来的根源。第二速度环的积分必须限幅否则车被外力按住几秒积分攒到上限一松手就是发射。常见做法是把积分限幅设在 3°~5° 的目标角修正上限物理含义是速度环最多让车身前倾几度去纠速度这个量不足以让车失控。最终输出是直立、转向叠加再加电机死区补偿// 最终输出直立在先转向差分在后最后做死区补偿 int16_t pwm_left (int16_t)(balance_out steering_out); int16_t pwm_right (int16_t)(balance_out - steering_out); // 死区补偿电机低占空比不转给固定偏置跨越死区 #define MOTOR_DEADZONE 300 if (pwm_left 0) pwm_left MOTOR_DEADZONE; if (pwm_left 0) pwm_left - MOTOR_DEADZONE; if (pwm_right 0) pwm_right MOTOR_DEADZONE; if (pwm_right 0) pwm_right - MOTOR_DEADZONE; // 最后 clamp 到安全范围再写 CCR 寄存器死区补偿这一行救过很多人的车。直流减速电机在占空比低于 3%~5% 时根本不转不加补偿的话直立环在小误差区的输出全部落在死区里车表现出站住一瞬间又倒下去的顿挫感。补偿量的确定方法开环逐步加占空比记录车轮刚好开始转的那个值左右轮分别标定两个电机的死区往往差 50~100 个计数不能共用一个值。4.3 编码器读数速度环的眼睛要接对速度环必须有速度反馈常见资源用定时器编码器模式读 AB 相信号。CubeMX 里把引脚配成编码器模式后计数由硬件完成代码只需周期性读取计数寄存器并清零。这里最容易错的是倍频理解1 倍频模式下一个电机转一周的脉冲数等于编码器线数4 倍频模式要乘 4。换算常量写错速度环怎么调都没反应或反着推。// 读编码器4 倍频模式假设编码器 13 线减速比 30 // 轮子转一圈13 * 4 * 30 1560 个脉冲 int32_t speed_raw (int32_t)__HAL_TIM_GET_COUNTER(htim3); __HAL_TIM_SET_COUNTER(htim3, 0); // 线速度 speed_raw * 轮周长(mm) / 1560 / 速度环周期(s) // 三个常量线数、倍频、减速比一处错就全错调速度环之前把轮子抬起来用手转一圈串口打印的 speed_raw 应接近 1560 左右。这一步能同时验证接线、倍频和方向避免把速度符号反了误判成PID 参数不对。还有一个隐蔽坑编码器接反时的代码修复。多数资源里会有一个方向宏改这里比改接线可靠——震动环境下焊接点很容易出问题代码符号固定下来排查面就小一半。5. 平衡车代码调试避坑抖动、抽风与上电倒地的排查清单5.1 高频抖动与低频荡秋千直立环参数的两种失控形态现象车身以 8~15Hz 的高频颤振电机发出尖锐嗡嗡声手摸车架能感到细密震动。原因最常见是直立环 Kp 过大叠加控制周期被其他中断拖长相位裕度不足产生振荡。解决先砍 Kp 40% 观察是否收敛如果还抖查控制中断是否被串口打印抢占再查互补滤波的 dt 是否等于实际控制周期。高频震颤的调试顺序永远是先稳定周期再降 Kp顺序反了等于盲试。还有一个容易误判的变体手扶车反而加剧抖动那是直立环输出已经饱和外力成了激励源而不是约束这时要查的不是 Kp 而是 PWM 限幅和机械重心。现象车身前后缓慢摆动像荡秋千一样幅度越来越大最终倒下。原因直立环 Kd 不足或速度环方向接反形成正反馈。解决先加 Kd每次加 10% 看摆动是否衰减若衰减到一定程度又出现新摆动把速度环输出临时置 0秋千现象消失就说明问题锁定在速度环方向或积分限幅上。这个临时置 0是排查串级系统最实用的单点变量法一次只动一个环节比同时改三个参数靠谱得多。现象车能站住几十秒但缓慢朝一个方向加速最后冲出去。原因速度环根本没闭环典型是编码器计数方向接反或速度环周期没跑起来。解决先看串口回传的速度值确认方向与电机转向一致再确认速度环周期比直立环慢 5~10 倍且代码确实按这个周期调用了速度环函数。这一类不是参数问题是外环根本没接上调参永远调不好一个断开的环。5.2 上电猛倒与参数随电压漂移初始化和标定基准的坑现象每次上电小车都朝同一个方向猛倒加速度很大像被人推了一把。原因上电瞬间传感器尚未稳定陀螺仪零偏没校准融合角度输出一个很大的初始值PID 一启动就按错误角度猛打方向。解决代码里加上电等待逻辑初始化后延时 500ms~1s静止采样 200 次取平均作为零偏最后才允许控制中断启动。凡是带屏幕显示的资源把显示初始化放在控制启动前面也能顺带起到延时作用。现象满电 8.4V 调好的车玩十分钟电压降到 7.2V 后开始频繁抖动最后站不住。原因电压下降后电机在同样占空比下输出扭矩变小直立环的增益裕度被吃掉如果资源里 PWM 是开环直驱这个问题靠调参解决不了。解决把参数标定基准定在最低工作电压或者给代码加电压归一化——把输出除以当前电池电压再乘标定电压。多数人选择前者按 7.4V 标定满电时参数略保守但全程稳定。这个现象也解释了为什么抄来的参数永远只能做起点不能做终点你的电池、车架重心、电机减速比都跟资源作者不完全一样。6. 让代码资源变成你自己的车三个验证技巧与收尾习惯6.1 用串口回传把控制链路可视化把车架起来让轮子悬空串口以 20ms 周期输出一行数据目标角、当前角、直立环输出、速度值。用手缓慢倾斜车身观察四点角度是否平滑跟随输出是否先反向补偿再回中速度值方向是否正确数值量级是否和预期一致。数据格式建议用文本加逗号分隔任何串口助手都能看。嵌入式里别用 printf 的浮点格式化又慢又占栈改成整数输出或定点放大 100 倍。这一步做完控制过程就不再是黑匣子每个参数改动都能在数据上看到响应。6.2 三个让调试效率翻倍的现场技巧第一个技巧是用手持站立测试代替直接放手双手扶住车架逐渐降低扶力感受推力方向和大小有没有在正确对抗倾斜。这个动作 30 秒内能分辨方向反了和参数偏小两种手感——前者推力越来越猛后者推力绵软无力完全不同的调参方向。第二个技巧是手机慢动作录像把车放地上试站慢动作看抖动频率8Hz 左右的高频颤振对应 Kp 过大0.5~2Hz 的低频摆动对应 Kd 不足录像一眼就能区分两种振荡。第三个技巧是单变量记录表每次只改一个参数记下改了什么、现象往哪个方向变两列半小时就能画出参数与响应的对应关系远胜拍脑袋乱调。我自己的习惯是每调好一个阶段就把参数和现象写进代码文件头的注释里Kp180、Kd900、2S 满电站 2 分钟10Hz 轻微颤这种记录下次换电池电压翻车时翻出来对照能省掉一整晚。调平衡车没有银弹只有把每次改动变成可追溯的证据链一套 stm32平衡小车代码资源才能真正变成你自己的能力。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑