基于STM32的自平衡自行车制作:从PID算法到硬件调试全解析

发布时间:2026/7/28 3:08:55
基于STM32的自平衡自行车制作:从PID算法到硬件调试全解析 1. 项目概述为什么选择自制自平衡自行车如果你对机器人控制、嵌入式系统或者PID算法感兴趣但又觉得那些复杂的理论离实际动手太远那么自制一辆迷你的自平衡自行车绝对是一个能让你把理论“玩”出来的绝佳项目。这玩意儿听起来很酷像是电影里的黑科技但其实它的核心原理并不神秘。简单来说它就是一个“不倒翁”的电子升级版通过传感器感知车身的倾斜角度然后驱动车轮朝倾斜的方向加速从而产生一个反向的力矩把车身“扶正”。整个过程就是一个经典的闭环控制系统。这个项目之所以迷人在于它完美融合了机械、电子和软件。你需要考虑车体的机械结构、电机的选型和安装、传感器的数据采集以及最核心的——控制算法的实现与调参。整个过程充满了挑战从车体一松手就倒到它能颤颤巍巍地立住几秒再到最终能稳定地自主平衡甚至行走每一个微小的进步都伴随着巨大的成就感。更重要的是它成本可控。利用Arduino、STM32这类开源硬件平台搭配廉价的直流电机、陀螺仪和加速度计模块几百块钱就能搭建起一个功能完整的实验平台。这比动辄上万的商业平衡车或机器人教具亲民太多了。它适合谁呢首先是电子、自动化、机器人相关专业的学生这是一个绝佳的毕业设计或课程实践选题能把你学的《自动控制原理》、《单片机原理》知识全部串起来。其次是广大创客和硬件爱好者这是一个能显著提升你系统设计能力和调试经验的“大玩具”。最后哪怕你只是个好奇的初学者跟着步骤一步步来也能窥见现代自动控制技术的门道体验到从零创造一台智能设备的乐趣。接下来我将以我实际制作过的一辆基于STM32的迷你自平衡自行车为例拆解从设计思路、硬件选型、软件编程到PID调参的全过程。我会重点分享那些教程里不会写的“坑”和“技巧”目标是让你看完后不仅能复现更能理解背后的每一个“为什么”。2. 整体设计与核心思路拆解在动手焊接第一根线之前我们必须把整个系统的逻辑想清楚。一辆自平衡自行车本质上是一个倒立摆系统。它的不稳定点垂直向上是我们希望维持的平衡点。2.1 系统工作原理与核心挑战它的工作流程是一个典型的“感知-决策-执行”循环感知通过惯性测量单元IMU通常包含三轴陀螺仪和三轴加速度计实时测量车身的俯仰角前后倾斜的角度和角速度。决策控制器我们的单片机根据当前的角度和角速度结合我们设定的目标角度通常是0度即竖直状态通过PID控制算法计算出一个控制量。这个控制量决定了电机应该输出多大的扭矩。执行电机驱动模块接收控制器的指令驱动车轮电机正转或反转产生相应的力试图将车身拉回平衡位置。这里的核心挑战在于响应速度车身一旦开始倾倒其角速度会迅速增加。如果我们的系统反应太慢等计算出结果再驱动电机时车身已经倒得无法挽回了。这就要求传感器采样、算法计算和电机驱动的整个环路延迟必须足够低。干扰处理电机转动本身的振动、地面不平、甚至一阵风都会对IMU读数造成干扰。如何从嘈杂的传感器数据中准确、稳定地估算出真实的车身倾角是第一个技术难点。控制精度PID算法中的三个参数比例、积分、微分需要精细调整。调好了车子稳如磐石调不好要么反应迟钝慢慢倒下要么剧烈振荡直接“跳”倒。2.2 方案选型为什么是STM32直流减速电机市面上常见的方案有Arduino和STM32。结合热搜词我们来分析一下选型考量。Arduino UnoATmega328P优点生态极其丰富库函数多上手快适合绝对新手快速验证想法。缺点主频低16MHz计算能力有限在进行高频率的传感器数据融合如互补滤波和浮点PID运算时可能会显得吃力影响控制频率。引脚和资源也相对紧张。STM32如STM32F103C8T6即“蓝色药丸”优点主频高72MHz性能强劲能轻松应对复杂的浮点运算和高频控制循环。外设丰富多定时器、高级PWM输出、硬件I2C/SPI能更稳定、高效地驱动电机和读取传感器。成本与Arduino Uno开发板相当甚至更低。缺点开发环境如Keil、STM32CubeIDE和编程方式库函数/HAL库对新手有一定门槛。我的选择与理由我选择了STM32F103C8T6。原因很简单性能冗余就是稳定性保障。在平衡控制中我们希望控制频率至少达到100Hz即每10ms完成一次“感知-决策-执行”循环。STM32可以轻松跑到200-500Hz为算法留下了充足的时间余量。更快的循环意味着系统能更及时地响应扰动平衡效果自然更好。虽然初期学习成本高一点但一旦掌握后续做更复杂的扩展比如加入速度环、转向控制会顺畅得多。执行机构选型电机主要有直流减速电机、步进电机和舵机。舵机位置控制但扭矩和速度通常不足且连续旋转舵机控制方式不同不首选。步进电机控制精确但需要复杂的驱动电路且在高速运行时可能丢步导致灾难性后果。直流减速电机搭配编码器后既能通过PWM调速又能通过编码器反馈转速是实现平衡和行走最主流、最灵活的选择。我选择了带AB相增量式编码器的N20直流减速电机减速比大扭矩足低速性能好。整体架构图文字描述主控STM32F103C8T6最小系统板。姿态感知MPU6050模块集成陀螺仪和加速度计通过I2C通信。执行驱动两个N20直流减速电机带编码器由两个TB6612FNG电机驱动模块驱动。电源一块7.4V 2S锂聚合物电池通过降压模块为STM32和MPU6050提供5V或3.3V。车体结构用亚克力板或3D打印件制作一个“T”字形车架将两个电机作为前轮实际上平衡车是左右轮我们这里是前后轮原理相通安装在车架两侧车架上方固定电池和主控板。这个架构平衡了成本、性能和复杂度是经过验证的可靠方案。3. 硬件搭建与核心细节解析硬件是系统稳定性的基石。很多调试时匪夷所思的问题根源往往在硬件。3.1 车体机械结构设计与注意事项车体结构的目标是低重心、高刚性、对称。材料我强烈推荐使用3D打印PLA材料。设计自由度高可以精确地做出电机座、电池仓和主控板安装位。如果没有3D打印机也可以用多层亚克力板激光切割后叠加粘合但强度和精度会差一些。重心电池是最重的部件一定要尽可能放在车体的最下方、靠近车轮轴心的位置。这能降低车体的转动惯量让控制系统更容易“扳动”它。你可以把电池仓设计在车架底部。刚性车架连接处必须牢固。电机轴与车架的连接如果存在哪怕微小的晃动都会被编码器放大为巨大的速度噪声进而影响平衡。使用联轴器连接电机轴和车轮时要确保同心度。对称性左右电机的安装高度、车轮直径必须一致。否则会导致两个轮子出力不均车子会自己转圈。安装好后可以手动推动车子看它是否走直线来粗略检验。实操心得在3D设计电机座时一定要为电机和编码器留出足够的散热空间和走线槽。电线要用扎带或热熔胶固定好防止在车子抖动时脱落或缠绕进车轮。第一次组装好后先不要急着上电用手轻轻晃动车体听听有没有松动的异响检查所有螺丝是否拧紧。3.2 核心模块连接与避坑指南这里给出STM32F103C8T6与各模块的关键连接示意具体引脚可根据你的板子调整MPU6050VCC - 3.3VGND - GNDSCL - PB6 (I2C1_SCL)SDA - PB7 (I2C1_SDA)INT - 悬空或接一个中断引脚用于数据就绪中断非必需但更高效TB6612FNG (驱动电机A为例)VM - 电池正极 (7.4V)VCC - 5V (逻辑供电可从STM32的5V引脚或降压模块取)GND - 共地AIN1 - PA1 (普通GPIO)AIN2 - PA2 (普通GPIO)PWMA - PA3 (定时器通道用于PWM调速)AO1, AO2 - 接电机A两根线STBY - 接高电平如3.3V或用一个GPIO控制使能电机编码器 (以电机A为例)Encoder_A_A - PB0 (定时器通道如TIM3_CH3)Encoder_A_B - PB1 (定时器通道如TIM3_CH4)编码器供电通常接3.3V或5V取决于编码器型号关键注意事项共地共地共地这是最重要的原则。STM32、MPU6050、TB6612的逻辑地、电机的电源地最终必须全部连接在一起。否则会产生巨大的噪声导致传感器数据跳变、电机控制异常。建议使用一个“电源地”汇流排。电源隔离电机在启动和急停时会产生很大的反向电动势可能通过电源线干扰微控制器。务必在电机驱动模块的电源输入端并联一个大容量如470uF的电解电容和一个小容量如0.1uF的瓷片电容用于滤波。最好能为STM32和传感器单独使用一路线性稳压电源如AMS1117-3.3与电机动力电源分离。I2C上拉电阻MPU6050模块通常自带4.7kΩ上拉电阻。如果通信不稳定读不出数据或数据全为0可以检查SCL和SDA线上是否确实有上拉电阻到3.3V。PWM频率选择对于TB6612PWM频率建议在1kHz到20kHz之间。频率太低电机会有啸叫频率太高驱动芯片开关损耗大。我通常选择10kHz。在STM32中通过配置定时器的ARR和PSC寄存器来设定。3.3 传感器数据处理姿态解算的奥秘MPU6050给我们提供的是原始的陀螺仪角速度单位度/秒和加速度计数据单位g。我们不能直接使用加速度计测得的角度因为车子运动时的加速度会严重干扰这个值也不能单纯积分陀螺仪数据因为它的零漂会随时间累积导致角度“飘走”。解决方案是传感器融合。1. 互补滤波这是最简单有效的方法思想直观用加速度计的数据来修正陀螺仪的长期漂移用陀螺仪的数据来抑制加速度计的短期振动。// 伪代码示例 float angle; // 估算的俯仰角 float gyro_rate; // 陀螺仪读取的角速度 float accel_angle; // 从加速度计atan2(ax, az)计算出的角度 float dt 0.01; // 采样时间假设控制周期是10ms (100Hz) float alpha 0.98; // 互补滤波系数通常0.98左右 // 核心公式 angle alpha * (angle gyro_rate * dt) (1 - alpha) * accel_angle;alpha是一个权衡参数。越接近1越信任陀螺仪动态响应好但会漂移越接近0越信任加速度计静态稳但动态干扰大。需要根据实际车体抖动情况调整。2. 卡尔曼滤波更优但更复杂的方法。它通过状态空间模型综合考虑了传感器噪声和系统模型的不确定性给出一个理论上最优的估计。对于自平衡车一维的卡尔曼滤波就够用。网上有很多开源的MPU6050卡尔曼滤波代码可以直接移植。它的效果通常比互补滤波更平滑、更准确。调试技巧在调试初期务必通过串口将原始加速度计角度、陀螺仪积分角度和融合后的角度实时发送到电脑上位机如VOFA、SerialPlot绘制成曲线。用手缓慢转动车体观察曲线是否跟随快速转动或震动车体看融合角度是否比加速度计角度更平稳。这是验证姿态解算是否正确的唯一可靠方法。4. 核心控制算法PID的深入理解与实现PID是项目的灵魂。很多人调不好PID是因为没有真正理解每个环节的物理意义。4.1 PID算法公式与离散化实现连续时间的PID公式是u(t) Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt在单片机中我们需要离散化。位置式PID是最直接的实现// 伪代码 float error target_angle - current_angle; // 当前角度偏差 float integral error * dt; // 误差积分 float derivative (error - last_error) / dt; // 误差微分 float output Kp * error Ki * integral Kd * derivative; last_error error; return output;这个output就是最终计算出的控制量我们将它映射为电机的PWM占空比。为什么选择位置式PID对于自平衡车我们控制的是电机的“力”扭矩目的是抵消倾斜。位置式PID的输出直接对应了需要施加的力的大小物理意义清晰。另一种“增量式PID”输出的是控制量的增量更适合控制电机“转速”或“位置”的场合。在我们的场景下位置式更直观。4.2 参数整定从零到稳的实战步骤调参是个手艺活遵循“先P再D最后I”的黄金法则并且一定要在软垫上进行防止车子摔坏。初始化将Ki和Kd设为0。target_angle设为0竖直。用手扶住车子让它保持竖直。调Kp比例给Kp一个很小的正值比如1.0。松手观察车子反应。如果它缓慢地向一个方向倒下说明Kp太小产生的恢复力不足以抵抗重力。逐渐增大Kp。当Kp增大到某个值时车子不再缓慢倒下而是开始围绕平衡点高频振荡快速来回抖动。记录下这个Kp值记为Kp_max。合适的Kp取Kp_max的30%~50%。例如Kp_max是20则Kp可以先设为8。此时车子应该能勉强立住但可能会缓慢地单向漂移因为还有积分项没加。调Kd微分微分项的作用是抑制振荡增加阻尼。它感知角度变化的趋势角速度。逐渐增加Kd。你会看到车子的高频抖动明显减弱变得“沉稳”起来。如果Kd过大车子会对微小的角度变化反应过度变得“僵硬”甚至引入新的低频抖动。Kd的合适值通常需要反复微调直到车子能稳定站立3-5秒。调Ki积分积分项用来消除静态误差。比如由于电机不对称、地面轻微倾斜导致车子需要一个持续的力才能站稳这个力就由积分项提供。在车子能基本站稳后加入一个很小的Ki比如0.01。观察车子是否还能站稳或者是否开始缓慢地向一个方向加速倒下积分饱和。积分限幅至关重要必须对integral变量设置一个绝对值上限如±100防止它无限累积“积分饱和”导致系统失控。这是PID调试中最容易忽略也最致命的一点。Ki通常很小一点点就能起作用。它的主要目标是解决车子站住后的缓慢漂移问题。我的调参实录我的车子参数最终大致为Kp12.0, Ki0.05, Kd0.8具体值因车而异。调参时我强烈建议将error,output等关键变量通过串口发送到上位机软件如VOFA进行可视化。观察当车子受到一个轻微推力时error和output的响应波形能极大地帮助你理解每个参数的作用。4.3 进阶串级PID与角度环、速度环当你的车子能稳定站立后可以尝试让它前进后退。这就需要引入速度环构成串级PID。内环角度环就是我们上面调好的PID。输入是角度偏差输出是电机的目标速度。意思是为了纠正这个角度偏差车轮需要以多快的速度转动。外环速度环输入是目标速度由遥控器给定比如0与实际速度由编码器测得的偏差输出是角度环的目标角度偏移。意思是为了让车子达到目标速度需要让车身向前或向后倾斜多少度。工作流程假设我们希望车子以0.2m/s的速度前进。速度环发现当前速度是0于是输出一个“2度”的指令给角度环。角度环发现当前角度是0度目标变成了2度于是产生一个-2度的偏差它计算出一个控制量让车轮向前转车身因此开始前倾。当前倾角度达到2度时角度环平衡车子就以一个固定的前倾角度匀速前进。编码器反馈的速度也逐渐增加到0.2m/s此时速度环的偏差为0它输出的角度偏移指令也归零车身恢复竖直但依靠惯性保持速度。实现串级PID后你的平衡车就真正“活”了可以通过遥控器控制其行走。这是从“平衡”到“运动”的关键一步。5. 软件架构与关键代码实现一个好的软件架构能让调试事半功倍。我采用基于定时器中断的“时间片”调度。5.1 主程序与定时器中断调度在main函数中完成硬件初始化后就进入空循环。所有实时任务都在定时器中断服务函数中完成。// 示例使用TIM2产生10ms中断 (100Hz) void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 1. 读取传感器数据 (MPU6050) MPU6050_Read_Data(accel, gyro); // 2. 姿态解算 (互补滤波或卡尔曼滤波) current_angle Complementary_Filter(accel, gyro); // 3. PID计算 motor_output PID_Calculate(current_angle, target_angle); // 4. 输出PWM到电机驱动 Set_Motor_PWM(motor_output); // 5. (可选) 每10个循环(100ms)执行一次速度环计算和编码器读取 static uint8_t cnt 0; if (cnt 10) { cnt 0; current_speed Read_Encoder_Speed(); target_angle_offset Speed_PID_Calculate(current_speed, target_speed); // 将角度偏移加到角度环的目标值上 target_angle 0 target_angle_offset; } } }这种结构确保了控制循环的严格周期性这是数字控制器稳定性的基础。5.2 编码器测速与电机驱动编码器测速STM32的定时器有编码器接口模式可以硬件自动计数非常方便。我们将编码器的A、B相接在定时器的两个通道上如TIM3_CH1, CH2配置为编码器模式。在中断中定期如100ms读取计数器值CNT并清零。速度 (CNT / 编码器线数 / 减速比) * (轮子周长 / 采样时间)。电机驱动使用PWM控制速度两个GPIO控制方向。例如void Set_Motor_PWM(float output) { int16_t pwm (int16_t)(output * MAX_PWM); // 将PID输出映射到PWM范围 if (pwm 0) { // 正转 HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htimX, TIM_CHANNEL_Y, pwm); } else { // 反转 HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htimX, TIM_CHANNEL_Y, -pwm); } }5.3 利用VOFA进行可视化调试串口打印数字不够直观。VOFA是一款非常强大的串口数据可视化工具。你只需要在单片机代码中将多个变量如current_angle,target_angle,error,output按照特定格式如angle:%f,%f,%f,%f\r\n发送到串口然后在VOFA中配置一个波形图控件就能实时看到这些变量的变化曲线。这对于PID调参和故障排查是神器级的工具。6. 调试全记录从“站不起来”到“行走自如”这一部分是最有价值的记录了我遇到的真问题。6.1 常见问题与排查清单现象可能原因排查步骤与解决方案上电后电机狂转或抖动1. PWM初始化或输出引脚配置错误。2. 电机驱动模块使能端STBY未接高电平。3. 电源功率不足或接触不良。1. 用示波器或逻辑分析仪检查PWM引脚是否有正常波形。2. 检查STBY引脚电平确保驱动芯片已使能。3. 单独测试电机驱动模块给AIN1高AIN2低PWMA给固定高电平看电机是否平稳转动。MPU6050读不出数据1. I2C接线错误或接触不良。2. 上拉电阻缺失。3. 电源电压不对MPU6050是3.3V器件。4. I2C地址错误AD0引脚电平决定地址。1. 用万用表检查SCL/SDA线电压空闲时应为高电平3.3V。2. 使用I2C扫描程序检查是否能发现设备地址通常是0x68或0x69。3. 确保VCC接3.3V并与STM32共地。车子往一边倒调PID没用1. 机械结构不对称重心偏。2. 两个电机性能不一致空载转速、减速比。3. 电机安装不水平。4. PID输出限幅不对称。1. 将车子放在绝对水平的桌面上用手机水平仪APP检查车架是否水平。2. 编写测试程序给两个电机相同的PWM看它们空转速度是否一致。不一致可在软件中对PWM输出乘以一个补偿系数。3. 检查PID计算后的输出限幅是否正负对称。车子高频剧烈振荡1. Kp过大。2. 传感器数据噪声大或融合算法不佳。3. 控制频率过高或过低与系统动态特性不匹配。4. 机械结构刚性不足有谐振。1. 大幅降低Kp。2. 通过上位机观察原始角度和融合后角度波形优化滤波算法或参数如互补滤波的alpha。3. 尝试调整控制周期通常在5ms到20ms之间200Hz到50Hz。4. 加固机械连接点特别是电机与车架的连接。车子能站住但缓慢漂移1. 积分项Ki太小或未起作用。2. 存在微小的机械不对称或零点漂移。3. 目标角度零位未校准准确。1. 适当增加Ki并确保积分项没有被限幅过早截断。2. 在车子静止且平衡时记录下此时的“平衡角度”将其作为target_angle的偏移量写入程序。3. 上电时让车子在水平面上静止2秒程序自动采集此时的传感器数据作为零位校准。加入速度环后车子跑飞1. 速度环PID参数过于激进特别是Kp。2. 编码器测速方向错误正负号反了。3. 角度环和速度环的控制周期设置不合理。1. 先将速度环的Kp设为非常小的值如0.1Ki和Kd为0慢慢增加。2. 用手转动一个轮子通过串口打印编码器读数和计算出的速度确认方向正确。3. 速度环的周期可以比角度环慢5-10倍因为车体速度变化比角度变化慢。6.2 我的踩坑实录与终极技巧坑一电源噪声导致MPU6050数据跳变。最初我用同一个降压模块给STM32和MPU6050供电电机一转角度数据就乱跳。后来为MPU6050单独增加了一级LC滤波一个10uH电感和一个100uF电容并在软件中增加了更严格的滑动平均滤波问题解决。坑二积分饱和导致“暴走”。第一次调Ki时没加积分限幅车子在某个方向倒下后积分项疯狂累积。当它被扶正时巨大的积分项输出导致电机全速反转车子直接“弹射”出去。务必给积分项设置一个合理的绝对值上限。坑三车轮打滑。在光滑地面上车轮加速时容易打滑编码器测速失效导致速度环失控。解决方法使用摩擦力更大的轮胎如硅胶轮胎或者在地面铺上粗糙的垫子。终极调试技巧分步测试不要所有代码写完一起调。先写个程序让电机能正反转再写个程序读MPU6050并打印角度然后让角度控制一个LED亮度用手倾斜车子LED变亮变暗最后才把电机和PID连起来。参数微调调参时每次只改变一个参数并且改变的幅度要小比如Kp每次增减20%。记录下每次改变后的现象。保护机制在代码中加入“倾角保护”。当检测到车身倾角超过某个安全范围如±30度时立即切断电机PWM输出防止车子在失控时高速旋转损坏硬件或伤及周围。从一堆散件到一辆能独立站稳、甚至听从指令行走的迷你自平衡自行车这个过程充满了挑战但最终的成就感是无与伦比的。它不仅仅是一个玩具更是一个完整的控制系统工程实践。当你亲手调教好PID参数看着它从东倒西歪到稳稳立住的那一刻你会对“反馈”、“稳定性”、“动态响应”这些课本上的词汇有刻骨铭心的理解。这个项目最大的收获不是一辆车而是解决复杂问题的系统化思维和动手能力。希望我的这些经验能帮你少走弯路更快地体验到这份乐趣。