1. 平衡球项目的整体设计思路与方案选型1.1 为什么选择平衡球作为PID实战载体平衡球这个项目在控制类实践里算是经典中的经典我第一次接触它是在做一个桌面级演示装置的时候。它的物理模型非常直观一根横梁或者平板上放一颗球通过电机驱动横梁倾斜让球稳定在指定位置或者沿着预定轨迹运动。听起来简单但它几乎涵盖了自动控制的所有核心难点——非线性、时滞、摩擦扰动、传感器噪声一个都不少。我试过用很多不同的被控对象来练PID比如温控、电机调速、倒立摆但平衡球有个独特的优势状态肉眼可见。球往哪滚、滚多快、有没有振荡你一眼就能看出来不需要盯着示波器猜。这对调试PID参数来说太重要了尤其是新手能直接建立“参数变化→系统行为变化”的直觉。从控制理论角度看平衡球系统本质上是一个欠驱动系统——你只有一个控制输入横梁倾角却要控制球的位置而且球的位置和横梁角度之间还存在耦合。这种特性让它比单纯的电机调速更有挑战性也更接近实际工程中遇到的多变量耦合问题。1.2 系统架构的选型考量整个项目我把它拆成三个层次感知层、控制层、执行层。这个分层方式是我踩过几次坑之后总结出来的早期我把所有代码堆在一个文件里后来发现调试时根本分不清是传感器问题还是算法问题。感知层负责获取球的位置信息。常见方案有电阻触摸屏、摄像头视觉、红外对管阵列。我最终选了摄像头OpenCV的方案原因很简单非接触、精度可调、成本可控。电阻触摸屏虽然便宜但球在上面滚动会有磨损而且响应速度受限于扫描频率。红外对管阵列精度取决于对管密度想做到毫米级需要大量对管布线会疯掉。控制层是核心跑PID算法。我用的是一块STM32F407主频168MHz带FPU跑浮点PID绰绰有余。选它不是因为性能不够用而是考虑到后续可能要上串级PID或者模糊PID留足余量。如果你只是做位置式PIDSTM32F103也完全够用。执行层是舵机或者步进电机驱动横梁。我选的是数字舵机扭矩大、响应快而且内置位置反馈省去了额外的编码器。步进电机虽然精度高但低速时容易共振对平衡球这种需要平滑微调的场景不太友好。注意舵机的供电一定要独立不要从STM32的板载稳压取电。舵机启动瞬间的电流冲击会导致单片机复位这个坑我踩过排查了一整天才发现是电源问题。1.3 控制策略的取舍位置式还是增量式PID算法有两种基本形式位置式PID和增量式PID。很多教程一上来就讲位置式因为公式直观但我实际用下来平衡球这个场景更适合增量式PID。位置式PID的输出直接对应执行器的绝对位置公式是u(k) Kp*e(k) Ki*Σe(i) Kd*(e(k)-e(k-1))增量式PID输出的是控制量的增量Δu(k) Kp*(e(k)-e(k-1)) Ki*e(k) Kd*(e(k)-2e(k-1)e(k-2))为什么平衡球更适合增量式因为舵机的控制信号是脉宽每次调整都是在上一次的基础上增减。增量式天然适合这种“累加”式的执行器而且它没有积分饱和问题——位置式的积分项会一直累加当球被卡住或者目标位置突变时积分项会累积到很大导致超调严重。增量式只关心增量不会出现这种累积效应。但增量式也有缺点它对噪声更敏感因为微分项放大了两次差分。所以我在代码里加了低通滤波后面会详细讲。2. 核心细节解析与实操要点2.1 视觉感知球位置检测的精度与延迟平衡摄像头方案的核心问题是帧率与精度的权衡。我用的是普通USB摄像头720p30fps。理论上分辨率越高定位越准但处理一帧的时间也越长。实测下来640x48060fps比1280x72030fps更适合这个项目因为控制周期越短PID的响应越快。球的位置检测我用的是颜色阈值轮廓查找。具体步骤把图像从BGR转到HSV空间因为HSV对光照变化更鲁棒用cv2.inRange()提取球的颜色区域用cv2.findContours()找到轮廓计算轮廓的最小外接圆圆心就是球的位置这里有个细节球的位置需要映射到物理坐标。摄像头拍到的像素坐标和横梁上的实际位置不是线性关系因为存在透视畸变。我的做法是在横梁上贴几个已知位置的标记点用cv2.getPerspectiveTransform()做透视校正。这一步不做的话球在横梁两端和中间的PID参数会表现不一致。实操心得颜色阈值不要写死我加了一个按键触发自动校准的功能。按下按键后程序会采集当前画面中球的颜色分布自动计算HSV范围。这样换环境或者换球之后按一下就能重新校准不用手动调参数。延迟方面从摄像头采集到算出球位置整个链路大概有30-50ms的延迟。这个延迟在PID里相当于一个纯滞后环节会降低系统的相位裕度。我的应对策略是降低微分增益因为微分项对延迟最敏感。同时适当提高比例增益来补偿响应速度。2.2 PID参数整定的实操方法PID调参是很多人头疼的地方我见过有人用粒子群算法或者模糊PID来自动整定但对于平衡球这个项目手动整定反而更快。我的步骤是第一步只开比例项。把Ki和Kd设为0Kp从0开始慢慢加。你会看到球开始向目标位置移动但会在目标位置附近振荡。继续加Kp直到振荡刚好不衰减也不发散记下这个临界Kp。第二步加入微分项。Kd的作用是“刹车”抑制振荡。从Kp的1/10开始加观察球的运动是否变得平滑。如果球在接近目标时出现“抖动”说明Kd太大了因为微分项放大了传感器噪声。第三步最后加积分项。Ki用来消除稳态误差。平衡球系统里由于横梁不可能绝对水平球总会有一个方向偏移。Ki从很小的值开始加比如Kp的1/100。加太快会导致球在目标位置附近来回“画圈”。我实测下来的一组参数供参考具体值取决于你的机械结构参数值说明Kp0.8比例增益决定响应速度Ki0.005积分增益消除稳态误差Kd0.15微分增益抑制振荡控制周期10ms定时器中断周期注意这组参数是在我的机械结构下调出来的你的舵机扭矩、横梁长度、球的质量不同参数肯定不一样。但整定顺序是通用的先P后D最后I。2.3 抗噪声处理滤波器的选择与实现视觉检测的噪声主要来自两方面光照变化和运动模糊。球在快速滚动时摄像头拍到的图像会有拖影导致轮廓变形位置检测出现跳变。我用了两级滤波第一级中值滤波。对连续5帧的球位置取中值去掉突变的野值。中值滤波对“脉冲噪声”特别有效比如某一帧因为反光导致检测到错误位置。第二级一阶低通滤波。公式是y(k) α*x(k) (1-α)*y(k-1)α取0.3左右。这个滤波器会引入相位延迟但能显著平滑位置信号。延迟和噪声是一对矛盾你需要根据实际效果调整α。在PID的微分项上我还额外加了一个不完全微分。标准PID的微分项是Kd*(e(k)-e(k-1))当误差突变时微分项会输出一个很大的尖峰。不完全微分的做法是给微分项加一个一阶惯性环节D(k) Kd*(1-β)*(e(k)-e(k-1)) β*D(k-1)β取0.7左右。这样微分项的输出会平滑很多舵机不会因为噪声而抖动。3. 实操过程与核心环节实现3.1 硬件搭建从零到能跑的完整清单先列一下我用的硬件清单都是容易买到的STM32F407开发板一块USB摄像头一个支持UVC协议免驱数字舵机两个扭矩至少15kg·cm铝合金横梁一根长度约40cm亚克力底板一块用于固定舵机和摄像头12V/5A电源一个降压模块12V转5V给STM32和舵机供电杜邦线、螺丝、热熔胶若干机械结构的设计要点横梁的转轴要在中心这样舵机转动相同角度横梁两端的高度变化是对称的。如果转轴不在中心PID控制会变得不对称调参难度翻倍。舵机和横梁的连接我用的是舵盘连杆的方式。舵机转动时通过连杆推动横梁倾斜。这里要注意连杆的长度太长会导致行程不够太短会导致舵机受力过大。我试了几次连杆长度取舵机臂长度的1.5倍左右比较合适。摄像头的安装位置很关键要正对横梁且镜头光轴垂直于横梁平面。如果摄像头有倾斜角度透视畸变会很严重即使做了透视校正也会有残余误差。我的做法是在亚克力底板上开一个槽摄像头卡进去保证位置固定。3.2 软件框架STM32端的代码结构STM32端的代码我分成三个模块串口通信、PID计算、舵机控制。串口通信负责接收上位机PC发来的球位置数据。我定义了一个简单的协议帧头(0xAA) 位置高字节 位置低字节 校验和 帧尾(0x55)位置数据用int16表示范围-32768到32767对应横梁上的物理位置。校验和是前面所有字节的累加和取低8位。PID计算放在定时器中断里周期10ms。为什么用中断而不是主循环因为主循环里如果有串口接收、数据处理等任务PID的执行周期会不稳定。定时器中断能保证严格的时间间隔这对PID很重要——PID的参数是基于固定采样周期整定的如果周期抖动参数就失效了。舵机控制用PWM输出。STM32的定时器可以产生50Hz的PWM波脉宽0.5ms到2.5ms对应舵机0到180度。我把PID的输出映射到脉宽上映射关系是pulse_width 1500 PID_output * 500 / 最大输出值1500us是舵机中位PID_output为正时舵机正转为负时反转。3.3 上位机Python视觉处理与串口发送上位机我用Python写主要用OpenCV和pyserial。核心流程import cv2 import serial import numpy as np ser serial.Serial(COM3, 115200, timeout0.01) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 60) # HSV阈值需要根据实际球色调整 lower np.array([0, 100, 100]) upper np.array([10, 255, 255]) while True: ret, frame cap.read() if not ret: continue hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, lower, upper) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: c max(contours, keycv2.contourArea) ((x, y), radius) cv2.minEnclosingCircle(c) if radius 5: # 过滤小噪点 # 透视校正后映射到物理坐标 pos int((x - 320) * 100 / 320) # 简化映射实际用透视矩阵 # 发送数据 data bytearray([0xAA, (pos 8) 0xFF, pos 0xFF, 0, 0x55]) data[3] sum(data[:3]) 0xFF ser.write(data)这段代码里有个细节radius 5这个过滤条件很重要。如果画面里有其他红色物体或者球被遮挡轮廓面积会很小这时候不应该发送位置数据否则PID会收到错误的位置信息导致舵机乱动。实操心得串口波特率我用的115200实测下来60fps下每帧发送5个字节带宽完全够用。如果你用更高的帧率或者更复杂的数据协议可以考虑提高到460800甚至921600。3.4 联调过程从开环到闭环的逐步验证联调我分了四步每一步都确认无误后再进行下一步第一步验证视觉检测。先不接STM32只在PC上显示球的位置。用手移动球看屏幕上的坐标是否跟随。这一步要确认检测是否稳定、坐标映射是否正确、延迟是否可接受。第二步验证串口通信。接上STM32但舵机不接。用串口助手看STM32收到的数据是否正确。这一步要确认帧头帧尾是否正确、校验和是否通过、数据解析是否准确。第三步开环测试舵机。不跑PID直接给舵机发送固定脉宽看横梁是否按预期倾斜。这一步要确认舵机方向是否正确、行程范围是否合适、电源是否稳定。第四步闭环调试。把PID加上从很小的Kp开始慢慢加。这一步是最耗时的但前面三步都确认无误后闭环调试会顺利很多。我见过很多人跳过前三步直接上闭环结果球一放上去舵机就疯狂摆动根本分不清是视觉问题、通信问题还是PID问题。分步验证是节省时间的捷径不是浪费时间。4. 常见问题与排查技巧实录4.1 球在目标位置附近持续振荡这是最常见的问题表现是球在目标位置左右来回滚动幅度不大但停不下来。原因一Kp太大。比例增益过大导致系统超调球冲过目标位置后又往回冲。解决方法是降低Kp或者增加Kd。原因二Kd太小。微分项是“阻尼”太小的话系统欠阻尼振荡会持续。适当增加Kd但注意不要加太多否则球会抖动。原因三控制周期太长。如果控制周期大于50ms系统的相位裕度会很低容易振荡。检查定时器中断的周期确保在10-20ms之间。原因四传感器噪声。如果球位置信号有跳变微分项会输出尖峰导致舵机抖动。检查滤波是否到位可以尝试增大中值滤波的窗口或者降低低通滤波的α值。4.2 球总是偏向一侧无法回到中心原因一横梁不水平。机械安装时横梁没有调平导致球有一个固定的重力分量。解决方法是调整舵机中位或者在PID输出上加一个前馈补偿。前馈补偿的做法是在PID输出上叠加一个固定的偏置量抵消横梁的不水平。这个偏置量可以通过实验确定把球放在中心观察它往哪边滚然后手动调整舵机中位直到球基本不动。原因二积分项饱和。如果Ki太大积分项会累积到很大导致舵机一直偏向一侧。解决方法是加积分限幅限制积分项的最大值。原因三摄像头安装偏斜。如果摄像头没有正对横梁透视校正后仍然会有残余误差。检查摄像头的安装角度确保光轴垂直于横梁平面。4.3 舵机发热严重或发出异响原因一舵机持续堵转。如果PID输出一直在极限值舵机会试图转到极限位置但被机械结构挡住导致堵转。解决方法是加输出限幅限制PID输出的最大值。原因二PWM频率不对。舵机需要50Hz的PWM如果频率太高或太低舵机会工作不正常。检查定时器的配置确保PWM频率是50Hz。原因三电源功率不足。舵机启动瞬间电流很大如果电源功率不够电压会跌落舵机可能复位或者抖动。用万用表测一下舵机供电电压在舵机动作时不应该低于额定电压的90%。4.4 常见问题速查表现象可能原因排查方法解决方案球持续振荡Kp太大/Kd太小降低Kp增加Kd重新整定参数球偏向一侧横梁不水平观察球静止时的滚动方向加前馈补偿或调中位舵机抖动传感器噪声看位置信号是否跳变加强滤波舵机发热输出限幅未设看PID输出是否饱和加输出限幅球不动串口通信失败用串口助手看数据检查接线和协议响应迟钝控制周期太长测中断周期缩短控制周期避坑技巧每次修改参数后先用手把球放在目标位置附近观察舵机的反应。如果舵机动作方向反了说明PID输出的符号搞错了把Kp改成负数即可。这个错误很常见但排查起来很快。4.5 进阶优化串级PID与前馈控制如果你已经调通了基本的位置PID可以尝试串级PID。串级PID有两个环外环是位置环内环是速度环。外环的输出作为内环的输入。为什么要加内环因为位置环的响应速度受限于球的运动惯性而速度环可以更快地响应。串级PID能让系统对扰动更鲁棒比如当球受到外力推动时速度环能更快地做出反应。串级PID的整定顺序是先调内环再调外环。内环的Kp从大到小调直到速度响应快速且无超调。然后固定内环参数调外环的位置PID。另一个优化是前馈控制。前馈的思路是根据目标位置的变化提前给舵机一个控制量而不是等误差出现后再反应。前馈的公式是u_ff Kf * (目标位置 - 上一时刻目标位置)Kf是前馈增益需要实验确定。前馈能显著减小跟踪误差尤其是在目标位置快速变化时。我实测下来加了前馈之后球跟踪正弦轨迹的误差从±15mm降到了±5mm以内。效果还是很明显的。5. 项目扩展与个人经验分享5.1 从平衡球到更复杂的控制对象平衡球调通之后你会发现很多控制问题都是相通的。我后来用同样的框架做了倒立摆和板球系统两个舵机控制平板球在平板上任意移动。倒立摆的难点在于系统本身是不稳定的需要更快的控制周期和更精细的参数整定。板球系统则是多变量耦合需要解耦控制。如果你想把平衡球项目继续深入我建议尝试以下几个方向方向一轨迹跟踪。不只是让球停在某一点而是让球沿着预定轨迹运动比如画圆、画八字。这需要前馈反馈的复合控制。方向二自适应PID。用模糊PID或者强化学习来自动调整参数。我试过用Q-learning做自适应PID效果还行但训练时间比较长而且需要设计合适的状态空间和奖励函数。方向三多球控制。如果横梁上放多个球就需要考虑球之间的碰撞和协调。这个难度就上了一个台阶属于多智能体控制的范畴了。5.2 我踩过的那些坑第一个坑是电源。前面提过舵机供电一定要独立。我一开始图省事直接从STM32的5V引脚取电结果舵机一动单片机就复位。后来加了一个LM2596降压模块从12V降到5V给舵机供电问题解决。第二个坑是摄像头延迟。我一开始用的摄像头是30fps的延迟大概60ms。PID调来调去都不稳定后来换了一个60fps的摄像头延迟降到30ms同样的参数立刻就稳了。所以如果你怎么调都不稳先检查一下摄像头的帧率和延迟。第三个坑是机械间隙。舵机和横梁之间的连杆如果有间隙会导致控制精度下降。球在目标位置附近时舵机微小的转动被间隙“吃掉”了球就会漂移。解决方法是尽量减小连杆的间隙或者用齿轮传动代替连杆。第四个坑是光照变化。白天和晚上房间里的光线不同HSV阈值需要重新调整。后来我加了自动校准功能每次开始前按一下按键程序自动采集球的颜色。这个功能虽然简单但省了很多事。5.3 给新手的建议如果你刚开始接触PID和平衡球我的建议是不要一上来就追求完美。先把系统跑起来哪怕球只是勉强能停在中心附近也比一直停留在理论阶段强。跑起来之后你才能看到实际的问题才能有针对性地改进。另外记录每一次调参的结果。我习惯用表格记录每次修改的参数和对应的系统表现这样能快速定位到哪组参数最好。不要凭记忆记忆是靠不住的。最后多看数据少猜。如果球振荡不要猜是Kp大了还是Kd小了把误差曲线打印出来看。误差曲线的形状会告诉你答案如果误差曲线是等幅振荡说明Kp太大如果误差曲线是衰减振荡但衰减很慢说明Kd太小如果误差曲线有稳态误差说明Ki太小。数据不会骗人。这个项目我从开始到基本满意大概花了两周时间其中大部分时间花在调试和排查问题上。但调通之后那种“球稳稳停在目标位置”的成就感值得所有的折腾。
阅读完成 · 觉得有帮助?