简介:面向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,用示波器或逻辑分析仪量频率。代码里写的周期和实测周期对不上,是这类资源最常见的隐藏问题,后面每次调参都建立在这个时间基准上,基准错了参数再准也没用。举一个数量级参考:假设直立环 Kp=200,5° 倾角误差时输出 1000 个 PWM 计数,在 10kHz、ARR=7199 的配置下约等于 13.9% 占空比,刚好驱动常见直流减速电机缓慢转动。这个对应关系能帮你快速判断一套资源的参数是否离谱。
3. 对照最小工程读懂代码资源:.ioc 配置与四个关键文件
3.1 工程文件清单:先认人再办事
典型的资源压缩包解压后,哪怕几十个文件,核心就四类:main 负责初始化和主循环;imu 驱动负责传感器读写和姿态接口;pid/control 负责三环计算;motor 负责 PWM 输出和编码器读取。带 .ioc 文件的资源是加分项,用图形化初始化工具打开就能直接看到完整外设配置,比读代码快得多。
| 文件/模块 | 职责 | 入手检查点 |
|---|---|---|
| main.c | 时钟、外设初始化、主循环 | 控制函数在中断里还是 while 里调用 |
| imu 驱动 | I2C 读写、零偏校准 | 角度单位是度还是弧度,符号是否与电机方向一致 |
| pid/control | 直立、速度、转向计算 | 目标角是 0 还是带前倾偏置 |
| motor | PWM 输出、编码器计数 | PWM 极性方向,编码器倍频与轮周长换算 |
给新手的建议是拿到资源先做"纸面走读":把初始化顺序、哪个定时器触发中断、中断里调了哪个函数这三件事写下来。我见过最坑的一种资源,主循环里放了个 while(1) 死等按键,控制函数却放在另一个定时器中断里,两个逻辑相互抢时间,改哪里都感觉车不理我。
走读时重点问三个问题。第一,控制函数是在中断里还是主循环里调用的?平衡车必须走定时器中断这条路,任何把控制放主循环里的资源,即使能站也站不稳,因为主循环周期不可控。第二,中断里有没有 printf、延时函数这类阻塞调用?有的话要么改掉要么换资源,串口打印一次在低主频芯片上可能吃掉几百微秒。第三,浮点运算有没有在中断里大量出现?不带 FPU 的芯片做浮点除法很贵,2ms 中断里跑几十次浮点除法,时间片就没了。
3.2 用初始化工具核对三大配置:I2C、PWM 与控制定时器
别急着编译,先核对三组配置:I2C 速率设为 400kHz,太慢会让姿态数据到达率不足;PWM 频率建议 10~20kHz,低于 5kHz 电机会有可闻噪音,占空比抖动还会直接串进直立环;控制定时器中断优先级必须高于串口,否则串口打印会随机拉长控制周期。这三项在 .ioc 或初始化代码里各占几行,却是后面所有玄学问题的物理根源。
PWM 频率的计算式是 fPWM = 定时器时钟 / (PSC+1) / (ARR+1)。以常见 72MHz 主频的芯片为例:
// 目标 10kHz PWM:主频 72MHz,PSC=0,ARR=7199 // 72,000,000 / (0+1) / (7199+1) = 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 就是要对着代码资源检查的两个参数。如果资源里写的是 PSC=71、ARR=99,算出来同样是 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 不足,录像一眼就能区分两种振荡。第三个技巧是单变量记录表:每次只改一个参数,记下"改了什么、现象往哪个方向变"两列,半小时就能画出参数与响应的对应关系,远胜拍脑袋乱调。
我自己的习惯是每调好一个阶段,就把参数和现象写进代码文件头的注释里:"Kp=180、Kd=900、2S 满电,站 2 分钟,10Hz 轻微颤",这种记录下次换电池电压翻车时翻出来对照,能省掉一整晚。调平衡车没有银弹,只有把每次改动变成可追溯的证据链,一套 stm32平衡小车代码资源才能真正变成你自己的能力。希望帮到你。
本文还有配套的精品资源,点击获取