
1. 项目缘起与整体设计思路1.1 为什么选择平衡球作为PID实战载体做控制算法的人都有一个共识看一百遍公式不如亲手让一个真实系统稳下来。平衡球系统就是这样一个绝佳的实战载体——一根横梁一颗小球目标是通过控制横梁的倾角让小球稳定在指定位置。听起来简单但它几乎涵盖了控制工程中所有核心问题非线性动力学、传感器噪声、执行器延迟、重力扰动、摩擦不确定性。我选择这个项目是因为它比倒立摆简单比温控系统直观比电机调速有观赏性。你能亲眼看到球在梁上滚动能用手拨一下球看系统如何恢复这种即时反馈对理解PID三个参数的作用是无价的。而且整个系统的硬件成本可以压到很低——一个舵机、一个超声波或红外测距模块、一块STM32或者Arduino、一根铝型材梁加起来不到两百块。这个项目适合谁如果你已经看过PID公式但不知道Kp、Ki、Kd到底怎么影响实际系统如果你做过仿真但没碰过真实硬件如果你想让自己的控制理论知识落地那这个项目就是为你准备的。我会从算法原理讲到硬件选型从代码实现讲到调参实录把整个流程拆开揉碎。1.2 系统总体架构与信号流整个系统的信号流是这样的测距传感器实时采集小球在梁上的位置这个位置信号经过滤波后送入PID控制器控制器计算出舵机应该转动的角度舵机驱动横梁倾斜小球在重力分量作用下产生加速度位置发生变化形成闭环。这里有一个关键设计决策位置式PID还是增量式PID。位置式PID输出的是舵机角度的绝对值增量式输出的是角度的变化量。对于舵机控制我最终选择了位置式PID原因是舵机本身就是一个位置伺服系统给它绝对角度指令更自然而且位置式PID在积分饱和处理上更直观。增量式PID更适合直接驱动电机转速的场景比如速度环控制。另一个决策是单环还是串级。理论上可以加一个角度环作为内环位置环作为外环形成串级PID。但实测下来对于这种慢速系统单环PID已经足够串级反而增加了调参复杂度。除非你的舵机响应特别慢或者梁的惯量特别大否则我建议从单环开始。注意不要一上来就追求复杂方案。单环PID调好了效果已经能满足大部分场景。串级PID是锦上添花不是雪中送炭。1.3 硬件选型背后的取舍逻辑硬件选型直接决定了控制效果的上限。我踩过的坑是一开始用SG90微型舵机扭矩太小梁稍微长一点就带不动球在梁上抖得像筛糠。后来换成MG996R金属齿轮舵机扭矩够了但响应速度又成了瓶颈。最终我选的是DS3218 20kg数字舵机扭矩和速度都够用价格也在可接受范围内。测距方案我试过三种红外测距、超声波测距、激光ToF。红外测距响应快但量程短、受环境光影响大超声波量程够但刷新率低、波束角大容易误检激光ToF综合表现最好刷新率可以到100Hz以上精度毫米级。如果你预算有限红外测距也能用但要在梁上贴一条白色反光带提高信噪比。主控我用的STM32F103C8T6也就是常说的“蓝板”。原因很简单定时器资源丰富PWM输出稳定ADC采样快而且社区资料多遇到问题好查。Arduino也能做但中断响应和PWM精度会差一些调参时你会明显感觉到“手不够快”。部件型号关键参数选择理由主控STM32F103C8T672MHz, 3个通用定时器PWM精度高中断响应快舵机DS321820kg·cm, 0.16s/60°扭矩足金属齿轮耐用测距VL53L0X量程2m, 精度±3mm刷新率高抗干扰强梁铝型材长度40cm, 轻质刚性好惯量小球钢球直径2cm, 光滑滚动摩擦小运动可预测2. 核心细节解析与实操要点2.1 PID算法在平衡球上的物理意义PID三个参数在平衡球系统里各自扮演什么角色我用最直白的话解释。Kp比例项决定系统对位置偏差的“敏感程度”。球偏离目标位置越远舵机倾斜角度越大球回来的速度越快。Kp太小球慢悠悠地晃回来甚至回不来Kp太大球会在目标位置附近来回振荡像喝醉了酒。Ki积分项消除稳态误差。如果梁不是完全水平或者球的重心不在正中间光靠Kp球永远停不到目标位置总会差那么一点。Ki的作用就是“记住”这个偏差慢慢累积直到把球推到正确位置。但Ki太大会导致积分饱和球冲过头后要等很久才能回来。Kd微分项提供阻尼。球运动越快Kd产生的“反向力”越大防止球冲过头。Kd相当于给系统加了刹车让球接近目标位置时减速。没有Kd系统会振荡Kd太大系统会变得迟钝甚至因为噪声放大而抖动。用数学表达位置式PID的输出是u(t) Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt其中e(t)是目标位置与当前位置的偏差。在代码里积分项用累加实现微分项用差分实现。实操心得调参时先把Ki和Kd设为0只调Kp。当球能在目标位置附近等幅振荡时记录此时的Kp值然后取它的60%作为最终Kp。接着加Kd从Kp的1/10开始往上加直到振荡消失。最后加Ki从Kp的1/100开始只加到能消除稳态误差就停。2.2 传感器数据处理与滤波策略测距传感器输出的原始数据一定带噪声。VL53L0X虽然精度不错但在球快速滚动时单次测量会有几毫米的跳变。如果直接把这个数据喂给PID微分项会把噪声放大成剧烈的舵机抖动。我的做法是先做中值滤波再做一阶低通滤波。中值滤波取最近5次采样的中位数能有效剔除偶发的野值。一阶低通滤波的公式是filtered alpha * raw (1 - alpha) * filtered_prevalpha取0.3到0.5之间。alpha越大响应越快但噪声越大alpha越小越平滑但延迟越大。对于平衡球我最终取0.35在响应速度和噪声抑制之间找到了平衡。还有一个细节微分项不要直接用滤波后的位置差分。因为滤波本身有延迟差分出来的速度会滞后。更好的做法是用一个单独的差分滤波器或者直接用位置式PID的微分项对误差做差分但加上一个低通滤波。我试过用卡尔曼滤波估计球的速度效果确实好但计算量大STM32F103跑起来有点吃力最后放弃了。注意滤波不是越多越好。每加一级滤波就多一份相位延迟。延迟大了Kd的效果会大打折扣系统容易振荡。我的原则是能用硬件解决的不要用软件滤波能用简单滤波解决的不要上卡尔曼。2.3 舵机控制与死区补偿舵机有一个特性死区。也就是说PWM占空比变化很小的时候舵机根本不转。DS3218的死区大约在5微秒左右。如果PID输出的角度变化小于死区舵机不动球就会一直偏下去积分项越积越大最后突然冲过头。我的解决方案是在PID输出后加一个死区补偿。如果输出变化量小于死区阈值就把它放大到刚好能驱动舵机的最小值。这样虽然牺牲了一点精度但避免了积分饱和和突然冲过头的问题。另外舵机的响应延迟也要考虑。DS3218从收到指令到转到指定角度大约需要100到150毫秒。这个延迟在PID里相当于一个纯滞后环节会降低系统的相位裕度。我的做法是在调参时把Kd适当调大一点补偿这个延迟带来的相位损失。舵机的供电也要注意。千万不要用STM32的5V输出直接给舵机供电舵机启动瞬间的电流冲击会导致主控复位。我用的是一个独立的5V 3A开关电源给舵机供电主控和舵机共地但不共电源。3. 实操过程与核心环节实现3.1 硬件搭建与接线要点硬件搭建的顺序很重要。我的建议是先搭机械结构再连电路最后写代码。机械结构不稳后面调参全是白费。梁的安装方式是舵机固定在底座上舵机臂连接梁的一端梁的另一端用一个轴承支撑保证梁可以绕轴自由转动。梁的转轴要尽量靠近梁的重心这样舵机负载最小。球放在梁上测距传感器固定在梁的正上方垂直向下照射。接线方面VL53L0X用I2C接口SCL接PB6SDA接PB7注意要加上拉电阻4.7kΩ。舵机信号线接PA0这个引脚对应TIM2_CH1可以输出PWM。舵机电源单独走线不要和主控共用一路。实操心得I2C总线上拉电阻不能省。我一开始没加上拉电阻VL53L0X时好时坏查了半天以为是传感器坏了后来加上4.7kΩ上拉电阻就稳定了。这个坑很典型新手很容易忽略。3.2 PID代码实现与参数整定代码部分我用的是位置式PID核心结构体如下typedef struct { float Kp; float Ki; float Kd; float integral; float prev_error; float integral_limit; float output_limit; } PID_Controller; float PID_Compute(PID_Controller *pid, float setpoint, float measured) { float error setpoint - measured; // 积分项累加带限幅 pid-integral error; if (pid-integral pid-integral_limit) pid-integral pid-integral_limit; else if (pid-integral -pid-integral_limit) pid-integral -pid-integral_limit; // 微分项 float derivative error - pid-prev_error; pid-prev_error error; // 计算输出 float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if (output pid-output_limit) output pid-output_limit; else if (output -pid-output_limit) output -pid-output_limit; return output; }积分限幅非常重要。如果不加限幅球长时间到不了目标位置积分项会一直累加最后输出饱和球冲过头后要等很久积分才能降下来。我的积分限幅设的是输出限幅的1/3左右。调参过程我记录如下步骤KpKiKd现象调整10.500球缓慢滚动回不到目标Kp太小加倍21.000球能回来但振荡Kp合适记录30.600振荡幅度减小取60% Kp40.600.05振荡明显减弱加Kd50.600.08基本稳定有微小稳态误差Kd合适60.60.010.08稳态误差消除加Ki70.60.020.08球稳定在目标位置最终参数最终参数Kp0.6, Ki0.02, Kd0.08。这个参数下球从任意位置出发大约2秒内能稳定到目标位置超调量小于5%。3.3 调试工具与实时监控调参时如果只能靠眼睛看效率极低。我的做法是用STM32的串口把实时数据发到电脑上用VOFA或者SerialPlot画曲线。需要监控的变量有目标位置、实际位置、PID输出、积分项。串口发送的格式我用的是简单的文本协议每行一帧数据用逗号分隔target,position,output,integral\n波特率设115200每20毫秒发一帧。VOFA可以直接解析这种格式并实时绘图。看着曲线调参比盲调快十倍。注意串口发送数据会占用CPU时间如果发送频率太高会影响控制循环的实时性。我的做法是把控制循环放在定时器中断里串口发送放在主循环里两者互不干扰。控制周期我设的是10毫秒也就是100Hz足够快了。4. 常见问题与排查技巧实录4.1 球振荡不止怎么办球在目标位置附近持续振荡是最常见的问题。原因通常有三个Kp太大、Kd太小、或者系统延迟太大。排查顺序先把Ki设为0排除积分项干扰。然后逐步减小Kp直到振荡幅度明显减小。如果减小Kp后球回不到目标位置说明Kp不能再小了此时应该增大Kd。Kd从Kp的1/10开始加每次加0.01直到振荡消失。如果加了Kd还是振荡那就要检查系统延迟。用示波器看舵机信号和球位置的变化如果舵机响应明显滞后于位置变化说明延迟太大。解决办法是换更快的舵机或者降低控制周期或者用前馈补偿。4.2 球停在目标位置附近但回不到正中间这是典型的稳态误差原因是Ki太小或者根本没有Ki。但加Ki之前要先确认机械结构没有卡涩梁的转轴没有摩擦死点。如果机械没问题逐步增大Ki每次加0.005直到球能回到正中间。如果加了Ki后球开始低频振荡说明Ki太大了积分项在“过冲-回调”之间来回摆动。此时应该减小Ki或者加一个积分分离当误差大于某个阈值时暂时关闭积分项避免积分饱和。4.3 舵机抖动严重舵机抖动通常是因为PID输出变化太剧烈尤其是微分项放大了传感器噪声。解决办法在微分项后面加一个低通滤波截止频率设在20Hz左右。或者直接减小Kd但这样会牺牲阻尼效果。另一个原因是电源功率不足。舵机在快速转动时电流需求很大如果电源带不动电压会跌落舵机就会抖动。用万用表测一下舵机供电电压如果在舵机转动时电压跌落超过0.5V就要换更大功率的电源。4.4 常见问题速查表现象可能原因排查方法解决措施球振荡不止Kp太大/Kd太小先关Ki减小Kp增大Kd或减小Kp稳态误差Ki太小检查机械是否卡涩增大Ki或加积分分离舵机抖动微分噪声/电源不足测电源电压看微分项加低通滤波/换电源球冲过头积分饱和看积分项是否限幅加积分限幅响应迟钝Kp太小/Kd太大逐步增大Kp调整Kp和Kd比例球不动死区/接线错误测舵机信号加死区补偿/检查接线4.5 进阶优化方向如果基础PID已经调好了还想进一步提升性能可以尝试这几个方向。前馈补偿根据目标位置的变化率提前给舵机一个角度补偿。比如球需要从A点移动到B点前馈项会在球还没动之前就把梁倾斜一个角度让球更快启动。前馈不改变闭环稳定性但能显著提升响应速度。模糊PID根据误差和误差变化率动态调整PID参数。误差大时用大Kp快速响应误差小时用小Kp避免振荡。模糊规则表需要根据经验制定调试工作量比普通PID大但适应性强。粒子群优化整定把PID参数作为粒子群算法的优化变量以超调量、调节时间、稳态误差的加权和作为适应度函数让算法自动搜索最优参数。这个方法适合有MATLAB基础的人可以在仿真里先跑再把结果用到实物上。实操心得不要一上来就上模糊PID或者粒子群。普通PID调好了平衡球已经能稳稳停在目标位置。进阶算法是在基础扎实之后才考虑的。我见过太多人基础PID都没调明白就急着上神经网络最后连球都稳不住。5. 项目复盘与个人体会这个项目我从零开始做了大约三周中间踩了不少坑。最大的坑是电源问题——舵机和主控共用电源导致主控频繁复位查了两天才找到原因。第二个坑是I2C上拉电阻这个属于硬件常识但新手很容易忽略。第三个坑是积分饱和球冲过头后要等好几秒才能回来加了积分限幅后问题解决。调参那几天我几乎把VOFA的曲线看成了心电图。Kp从0.1试到2.0Kd从0试到0.5Ki从0试到0.1。最后发现真正好用的参数区间其实很窄Kp在0.5到0.7之间Kd在0.05到0.1之间Ki在0.01到0.03之间。超出这个范围要么振荡要么迟钝。如果让我重新做一遍我会先花半天时间把机械结构做扎实确保梁的转轴顺滑、舵机臂连接牢固、传感器安装稳固。机械上的每一丝松动都会在PID调参时被放大成剧烈的振荡。然后我会先用开环测试手动给舵机不同角度记录球的位置变化大致了解系统的静态增益和响应时间。这个开环数据对后面调参很有参考价值。最后分享一个小技巧调参时如果实在找不到合适的Kd可以先把Kd设为一个较大的值然后逐步减小直到系统刚好开始振荡再往回加一点。这个方法比从零往上加更快找到临界点。当然前提是Kp已经调好了。这个项目后续还可以扩展加一个摄像头做视觉反馈实现二维平面上的平衡控制或者加一个无线模块用手机APP实时调整PID参数或者把控制算法移植到更快的MCU上把控制周期降到1毫秒看看性能能提升多少。控制工程的乐趣就在于你永远可以做得更好。