ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零理解PID控制算法:原理、Arduino实现与调参实战指南

从零理解PID控制算法:原理、Arduino实现与调参实战指南 我记得自己第一次真正被PID搞到夜不能寐是在调一个Arduino平衡小车的时候。车子放在地上轮子疯狂左右摆动像是喝醉了酒电机嗡嗡响电池发烫而串口监视器里的数据像心电图一样冲上冲下。那时候我已经搜遍了网络上关于“Arduino PID”的教程下载了一堆库照着示例代码粘贴可车就是站不起来。后来一个玩飞控的朋友看不过去问我“你知道P、I、D这三个参数分别在你的代码里干了什么吗”我沉默了因为我不知道。那一刻起我才意识到搞懂原理和会调库是两回事。这个经历让我后来无论做什么项目——温度控制、智能小车循迹、舵机定姿、四轴悬停——都先老老实实把PID吃透再去碰代码。所以这篇文章是写给那些和我当年一样的初学者看的。我会用最朴素的方式把PID控制算法掰开揉碎讲清楚也会附上完整可用的Arduino代码以及我从一次次翻车中总结出来的调参经验和排查方法。读完你不需要成为控制理论专家但一定能在自己的项目里把PID用起来而且知道它为什么这么干活。1. 为什么需要PID从开环到闭环的思维转变1.1 先从开环控制的“天真”说起很多刚接触Arduino的朋友写电机控制代码时往往是这样思考的我给电机一个固定占空比比如analogWrite(pin, 150)电机就会以某个速度转。如果想让风扇转快一点就把数值调大想转慢一点就调小。这种“我说什么它做什么做完就不管了”的方式在控制领域叫开环控制。开环控制最大的问题在于你给出的指令和实际输出之间可能存在一个不可预测的偏差。以直流电机为例同样的PWM占空比电机空载和带负载时的转速完全不一样电池电量高时转速会快些电量低时又会慢下来轴承磨擦大了、轮子卡了、地面阻力变了转速都会变。你设了analogWrite(pin, 150)但你根本不知道电机现在实际转了多快也没法在它“没达到预期”的时候主动纠正。用生活里的例子就好比你让一个外卖骑手“以每小时30公里的速度骑行”但没有给他速度表也没有任何人告诉他是否超速或太慢。他只能凭感觉拧油门结果就是他可能忽快忽慢根本无法稳定在30公里时速。开环控制就是这样——指令发出去了效果怎么样全靠运气和环境参数。1.2 闭环控制让系统学会“用自己的输出纠正自己”那怎么才能让控制精度上来最朴素的想法是装一个传感器测一下实际转速拿它与目标值比较根据差值反过来调整输出。这样系统就形成了一个“输出 → 检测 → 反馈 → 修正”的回路也就是闭环控制。闭环控制听起来简单但你立刻会碰到一个新问题**根据误差怎么调整输出**调整多了会震荡少了又达不到目标调慢了系统反应迟钝调快了又会超调甚至失控。这个时候PID就该出场了。PID是一种根据“当前误差、历史误差累计、误差变化趋势”三项信息综合计算出控制量从而让被控量稳定在目标值附近的经典控制算法。它的计算过程极简占用的资源也极少所以在Arduino这种8位单片机上也能轻松跑起来这也是它成为DIY项目和工业控制双料常青树的根本原因。从智能循迹小车的转向校正到温控箱的加热控制到四轴飞行器的姿态稳定背后其实都是那100行都不到、看起来毫不起眼的PID计算。2. PID公式拆解三个字母分别干了什么2.1 那行让无数人头疼的公式PID的数学形式网上到处都是教科书里长这样u(t) Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt刚接触的人看到积分和微分符号就直接劝退了。别怕我们把它翻译成人话。公式里的e(t)叫误差就是目标值和当前实际值之间的差也就是偏差。比如你希望车速是100雷达测到当前车速是80那误差e 100 - 80 20。接下来三个字母分别代表三种不同的“纠错策略”它们的计算结果全部相加最终得到控制量u这个u就是你要给电机的PWM值、要给加热棒的功率、要给舵机的位置指令。字母全称处理的信息打个比方PProportional比例当前误差看现在差多少差得多就多用力IIntegral积分历史误差累计把过去犯过的错攒起来算总账DDerivative微分误差变化趋势预判未来会差多少提前踩刹车或加速P决定系统的“反应速度”I负责消除“长期残留的偏差”D用来抑制“过冲和震荡”。一个只有P的系统能跑起来但停不准加了I才能消除稳态误差加了D才能稳稳当当不跳舞。下面我正式展开讲每一项。2.2 P比例项系统的油门与刹车比例项的计算极其简单P Kp * e误差e越大输出的修正量就越大误差变成0这一项的输出就是0。Kp是比例系数它决定了你对误差的“敏感程度”。Kp大了系统反应快但容易用力过猛表现在实际系统上就是震荡——小车来回摆、温度上蹿下跳、舵机隔着一两度来回抖动。Kp小了系统反应慢吞吞半天才能靠近目标但稳定性好。很多刚接触PID的人觉得“Kp越大越灵敏所以越大越好”这是一个很普遍的误区。实际调参时你会发现Kp调太大会让系统发散误差小的时候因为输出依然偏大系统冲过目标产生反方向的误差又被反方向拉过头一来一回越摆越猛最终彻底失控发出高频的蜂鸣声或者电机猛窜。我这里给出一个经验法则从很小的Kp开始比如0.1或者更小看系统的响应然后逐步成倍增大直到出现轻微震荡再退回去一半左右。这个做法在后面调参部分还会细讲。2.3 I积分项清掉历史的烂账比例项有一个先天不足系统一旦被某种持续的外力干扰比如电机带负载导致摩擦力大了或者小车爬坡时重力分量总在往后退光靠P项系统会在距离目标值不远的地方“停下来”误差一直有一点点但始终消不掉。这种情况专业上叫稳态误差。积分项就是来解决它的I Ki * e * dt注意这个代码的含义它是在每个控制周期时间间隔dt里把当前误差累加到一个变量I上再乘以Ki。换句话说积分项在“记账”——把过去每一次的误差都记下来只要误差一直在哪怕是0.5这种小误差积分的累计值就会一直增加输出的修正量也会越来越大直到把系统推到目标值。积分项的危险在于“积过头”。如果系统长时间没有到达目标I会积累到很大输出直接打满。当误差终于变成0时积分项里的“陈年旧账”还在它会让系统冲过目标。这种现象叫积分饱和integral windup是所有PID初学者最容易踩的坑之一。解决积分饱和的常见办法有两种一是给积分项设一个上限我叫它“积分限幅”比如最大值和最小值限制在正负某个范围内不允许无限累加二是一旦误差达到目标附近很小范围就暂停积分累加。在Arduino里实现这两种方法都不难代码我会在后面的完整例程里提供。2.4 D微分项看得见的未来微分项看的是误差的变化“速度”和“方向”。用离散方式表达就是D Kd * (e - last_e) / dt这个式子里的e - last_e表示相邻两个控制周期误差的变化量。除以dt才得到误差变化速率。它的意义在于如果误差正在快速减小说明系统正在猛烈地向目标逼近这时候应该减小输出避免冲过头。如果误差不减反增说明系统正在偏离目标这时候应该加大输出把系统拉回来。换句话说微分项像个“先知”不看现在偏了多少而看现在正在往哪个方向偏从而提前调整发力。不过微分项也有很现实的问题它对噪声极度敏感。比如温度传感器读出来的数值带一点点毛刺或者旋转编码器的计数偶尔跳一下微分项会把这种短促的抖动放大成一个剧烈的控制输出整个系统反而被搞得不稳定。解决这个问题的常见方法一是调小Kd二是对输入的测量值做简单的低通滤波甚至有些要求不高的项目干脆不用D只用PI控制器。这也是为什么网上很多Arduino示例里D系数默认设成0的原因。3. Arduino上的PID代码实现不只是照抄库3.1 从公式到代码的第一步离散化与采样周期Arduino的CPU是在每个循环里重复执行程序而不是真正的“连续”运行所以PID公式要用离散化的方式来实现。也就是说每隔固定的时间间隔dt采样一次当前值、计算一次误差、更新一次输出。这个固定的时间间隔非常关键我把它写在前面因为它比PID算法的三个系数更容易被忽略但影响更大。采样时间dt的选择原则是相对你的被控对象来说dt要足够短才能及时响应系统的变化。比如控制电机转速电机从静止到目标转速可能只要零点几秒那dt至少要控制在10~50ms如果做的是温度控制加热器把水温升1度可能要好几秒dt可以放宽到200~500ms。但不是说dt越短越好因为dt越短传感器噪声的影响越明显而且同一段时间内积分累加的次数变多了计算量也会摊大。Arduino Uno这种16MHz、2KB RAM的板子跑PID计算本身一分钟能跑几百万次性能根本不是瓶颈所以主要权衡来自传感器更新速率和噪声水平。在Arduino里获取准确的dt最忌讳的方法是delay(10)再配合millis()累计时间戳。delay()会让程序彻底停在这个函数里这期间你无法执行传感器读取或者其他逻辑很多实际项目正是在等待中错过了一次关键采样。正确的做法是用非阻塞的方式在loop()里用millis()判断上次采样到现在是否已经过了dt毫秒到了就执行一次PID更新没到就去做别的事。3.2 位置式PID的完整代码框架下面这套代码是我不依赖任何PID库、纯手写的位置式PID实现。它思路清晰完整可运行非常适合刚入门的人理解算法本质。用库当然方便但你要想清楚一个问题如果连SparkFun的PID库内部在算什么都不知道参数调坏了你连排查的方向都没有。// 位置式PID基础实现 // 适用于Arduino Uno等AVR系列或ESP32等大部分开发板 float targetValue 100.0; // 目标值比如目标转速、目标温度 float currentValue 0.0; // 当前测量值由传感器读取 float kp 1.2; // 比例系数 float ki 0.05; // 积分系数 float kd 0.1; // 微分系数 float error 0.0; // 当前误差 float lastError 0.0; // 上一次误差 float integral 0.0; // 误差积分累计值 float derivative 0.0; // 误差微分项 unsigned long lastTime 0; // 上次PID计算时间戳 const unsigned long dtMs 20; // 采样周期单位毫秒根据对象调整 float outputMin 0.0; // 输出下限 float outputMax 255.0; // 输出上限PWM的0-255 void setup() { Serial.begin(9600); lastTime millis(); } void loop() { unsigned long now millis(); unsigned long elapsed now - lastTime; // 到了采样周期才执行一次PID if (elapsed dtMs) { // 1. 读取当前值这一步要换成你的实际传感器读数 currentValue readSensor(); // 例如analogRead(A0); // 2. 计算误差 error targetValue - currentValue; // 3. 计算三个项 float pTerm kp * error; // 积分项累加误差这里已经乘了采样时间秒并做了积分限幅 integral error * (elapsed / 1000.0); float integrallimit 100.0; // 积分限幅防止积分饱和 if (integral integrallimit) integral integrallimit; if (integral -integrallimit) integral -integrallimit; float iTerm ki * integral; // 微分项误差变化率除以采样时间秒 derivative (error - lastError) / (elapsed / 1000.0); float dTerm kd * derivative; // 4. 相加得到输出并输出限幅 float output pTerm iTerm dTerm; if (output outputMax) output outputMax; if (output outputMin) output outputMin; // 5. 把输出作用到执行器上例如 analogWrite(pwmPin, output); applyOutput(output); // 6. 为下一个周期记录状态 lastError error; lastTime now; // 监控数据 Serial.print(T:); Serial.print(targetValue); Serial.print( C:); Serial.print(currentValue); Serial.print( E:); Serial.print(error); Serial.print( O:); Serial.println(output); } // 这里可以写其他非阻塞逻辑比如按键扫描、LED闪烁等 } float readSensor() { // 示例用模拟口读取一个电位器 // 如果你是温度控制就用温度传感器放大电路或模块 // 如果你是电机测速就用编码器的脉冲计数或霍尔传感器 return analogRead(A0); } void applyOutput(float output) { // 这里根据你的执行器做映射 // 例如digital write一个加热继电器、电机PWM、舵机角度映射 // analogWrite(pwmPin, (int)output); }这套代码里我特意把采样时间dtMs设成了20毫秒并且对积分项做了限幅这样至少能从代码层面规避两个最常犯的错误。想直接抄作业的人需要替换三处readSensor()里的传感器读数、applyOutput()里的执行器动作、以及targetValue目标值。3.3 输出限幅和积分限幅为什么必须写我之前见过有人在网上问明明PID算出来输出量很大但电机就是不转。后来发现他没有对PWM做限幅PID算出来一个几千的数值直接塞给analogWrite()但analogWrite()只接受0到255之间的整数于是数值被粗暴截断执行器要么全开要么全关控制完全失效。这是一个很典型又很隐蔽的坑。更隐蔽的问题在积分项。假设系统启动时误差很大积分项在几秒钟内积累到几百几千PID输出一直饱和在最大值驱动电机全速运行。等系统终于靠近目标时这个巨大的积分累计值还留在里面输出依然很大系统就被推过头。等误差变成反方向积分又要先把之前的“旧账”抵消掉才能开始反向修正这一来一回系统就会明显超调甚至长时间来回震荡。加入积分限幅把integral限制在正负integrallimit实际值要根据你的输出范围来定标准是积分项的贡献度不超过输出上限的一半左右就能把这种“记太多账”的风险压在可控范围内。输出限幅则更加直白PID算出来的控制量在物理世界里不可能无限大。电机最多全速转加热器最多全功率加热舵机最多转满角度。把输出限制在最小值和最大值之间不仅是保护执行器也是让整个系统的行为可预期。你在调试中看到的任何怪异现象先检查这两条限幅是不是写对了能省很多不必要的排查时间。3.4 要不要用第三方PID库我的建议Arduino生态里最流行的PID库是Arduino PID Library作者是Brett Beauregard。这个库封装得不错还支持自动调模式、积分分离等高级功能项目里直接用它确实能少写很多底层代码。但我的建议是如果你是第一次接触PID最好先照着上面的代码手写一遍。手写一遍的作用是让你亲眼见证每一项对输出的影响。你可以用串口绘图器实时画出pTerm、iTerm、dTerm的变化曲线亲眼看到“误差稳定但pTerm很小、iTerm在增长、输出因此上升”的整个过程。这个直观的反馈是任何库的抽象API都无法给你的。当你对每个变量都有了手感之后再去用库或者自己封装一个通用PID类都会顺手得多。4. 调参实战从零开始整定一个能用的PID系统4.1 调参的正确顺序先把P找到感觉很多教程把调参说得玄乎好像必须用数学工具或者仿真软件才能做。其实对于Arduino项目里的大多数系统手工整定的三个步骤就够用了完全不需要会摆奈奎斯特图。我给你一个可复制的流程以直流电机转速控制为例。第一步把I和D全部设成0只留Kp生效。把Kp设成一个比较小的值比如0.3系统开机后观察实际值的变化。这时候你大概率看到的现象是系统慢慢靠近目标值但最终会停在一个比目标值低一点的位置上不去——这就是稳态误差正常。逐步增加Kp0.5、0.8、1.0、1.2。你会发现系统到达目标值更快了但同时波动也在变大。当Kp加到某个值时系统开始围绕目标值持续震荡几乎停不下来。这时候记下这个临界Kp值然后把Kp调回临界值的一半左右。我实测过一个普通直流减速电机用PWM驱动、霍尔传感器测速。Kp1.2时开始出现持续震荡于是我把Kp定在0.7左右。系统的响应还是有一点缺口稳态误差但至少没有抖成筛子。这个值就是第二步的基础。4.2 第二步加I消除稳态误差维持Kp在0.7左右开始一点点增加Ki。从很小的数开始比如0.01然后观察稳态误差是不是在缓慢变小。Ki的典型特征就是见效慢。你可能要等上几十秒甚至一两分钟才能看到误差被完全压到接近0。继续加大Ki之前提醒你注意一个现象当Ki加到某个值时系统会出现一种频率很低的“慢悠悠”的震荡周期可能是几秒甚至更长。这说明I项过强了把历史误差放得太大了。此时把Ki调回一半左右再观察。在我那个电机例子里Ki加到0.06时稳态误差基本消失但偶尔能感觉到转速有轻微周期波动。我放在0.04系统终于在你感觉不到的范围内稳定住了。4.3 第三步加D抑制超调加了I之后你会发现一个新的问题系统启动时往往更容易冲过目标也就是产生超调。这是因为启动瞬间误差大积分项会积累大量“修正能量”等到达目标附近时已经“刹不住车”了。这时候D就有用武之地了。把Kd从0开始慢慢加到0.1、0.2观察启动过程的超调量。D的作用表现为当系统快速逼近目标时微分项会提前给一个负的输出相当于踩了刹车。超调量会明显下降。但你会同时发现一个新坑——D项让整个系统变得“神经质”比如传感器读数稍有抖动输出就跟着颤抖甚至发出高频的嘶嘶声。这说明Kd过大了异味信号被放大得无法容忍要把Kd调回一半左右。电机例子里Kd加到0.18时超调被压得不错但电机有轻微的高频抖动。我把Kd放在0.1超调量大概在5%左右系统稳定后平滑无声。4.4 我的一次完整实测记录为了让你有个更直观的参考我把一次完整的电机调速实测数据贴在这里。目标转速设定为100 RPM传感器是霍尔测速模块采样周期20ms。参数状态KpKiKd观察结果纯P偏小0.300能转到达目标很慢稳态误差约15RPM纯P适中0.700响应加快稳态误差约8RPM无震荡纯P临界1.200持续等幅震荡转速上下摆动约20RPMP小I0.70.020稳态误差消失慢约30秒后接近目标P合适I0.70.040稳态误差消除有轻微低频波动PI过大I0.70.100明显低频震荡周期约3秒转速波动18RPMPID完整0.70.040.10超调约6%稳定时间约1.2秒无高频抖动PID完整过大D0.70.040.30启动平滑但持续高频抖动电机发烫这张表告诉你三件事。第一调参是一个逐步逼近的过程每一步都要观察系统至少几十秒别急着加下一项。第二参数之间会互相影响加了I之后之前的P可能要微调加了D之后之前的I响应也会变。第三一个“能用”的PID并不需要数学上的最优能让项目稳定工作参数就是合格的。5. 常见问题与排查技巧实录5.1 高频问题速查表下面这些问题是Arduino PID玩家最容易碰到的我按症状、常见原因、处置顺序三条线整理成表希望你能少走点弯路。症状最先怀疑的原因检查与处理方法输出一直是最大值或最小值系统完全不受控输出/积分限幅缺失或传感器接线错误先确认readSensor()返回数值是否合理再检查上下限是否设置最后看积分限幅系统来回震荡振幅越来越大Kp过大把Kp减半再观察同时确认dt是否准确系统低频慢速震荡周期很长Ki过大把Ki减半或对积分做限幅系统高频抖动、执行器发烫Kd过大或传感器噪声降低Kd或者对测量值做滑动平均滤波系统稳定但有固定偏差一直追不上目标缺I项或Ki太小增大Ki确认积分项确实在执行累加改变目标值时系统超调明显D项偏小或I项积分饱和适当加大Kd检查积分限幅值是否太大启动阶段系统反应迟钝半天才动Kp太小或dt太大增大Kp把dt改小比如从100ms改成20ms执行器动作与PID输出相反输出极性写反确认error target - current还是反过来统一符号5.2 抓一个真实的疑难杂症采样周期漂移有一个问题我当年查了很久症状是PID在仿真软件里一切正常但烧到Arduino上以后系统表现忽好忽坏。仿真数值一直稳定实机却是温控一度误差上下飘转速偶尔抽搐。排查到后面才发现是采样周期的问题。有些PID库示例代码用delay()卡循环但因为delay()期间millis()还在计时所以如果循环里除了PID还有其他耗时操作实际上两次PID计算之间的时间间隔会变得不稳定——这次可能是18ms下次变成35ms再下次又变成27ms。而标准PID公式是按照固定采样周期推导的采样周期一变积分项和微分项的数值基准就全乱了。阶数差别到了这里解决方案就是回到我前面给的代码框架把PID计算放在一个由millis()判断、非阻塞调用的函数里确保两次计算的时间间隔恒定为dtMs。同时把耗时操作比如OLED刷新、液晶显示、串口打印大量数据放到PID更新之外去执行。串口打印尤其耽误事每行都好几十毫秒如果放在PID循环里采样周期会被拖得非常不稳。5.3 传感器噪声的实战处理微分会放大噪声这一点我前面反复强调了。但在实际项目中很多传感器信号本身就带噪声比如红外测距模块在波动环境下读数会有±3到5的抖动。你可以直接用滑动平均滤波给测量值做平滑。一个最简单好用的滑动平均滤波写法如下const int numReadings 5; float readings[numReadings]; int readIndex 0; float total 0; float average 0; // 在每次读取传感器时更新滑动窗口 total total - readings[readIndex]; readings[readIndex] analogRead(A0); total total readings[readIndex]; readIndex (readIndex 1) % numReadings; average total / numReadings;但注意滤波不是越重越好。窗口太大虽然能压住噪声但也会带来明显的滞后——你测量的实际值已经变了滤波后的值却还要反应好一阵子这种滞后会影响你反馈给PID的实时性甚至让微分项计算出错误的趋势。我的经验是对于转速控制3到5次滑动平均就够了对于温度控制可以放宽到10次左右因为温度本身就是大惯性系统滞后一点无伤大雅。滤波器的选择和控制周期是一对欢喜冤家需要在这个方向上也做平衡。5.4 温控项目里遇到的特殊坑我顺带提一个温控项目特有的坑很多人第一次做温度PID都会中招。加热执行器如果是继电器而不是固态继电器或者没有用PWM输出那它的执行方式就是“开”或“关”——这本质上是一个开关量输出而不是连续可调的输出。直接拿PID算出来的数值去控制这个开关量会导致继电器疯狂吸合断开几秒钟就烧了。解决思路有两种。一种是改用带PWM的加热模块比如用MOSFET驱动加热丝让输出变成连续可调的功率。另一种是保留继电器但用PWM周期来控制“占空比”——比如设定一个2秒的周期PID输出200意味着这个周期内加热1.6秒、停0.4秒执行效果等价于以80%功率加热。这两种做法里前者精度和稳定性更好后者适合硬线器件但响应天然慢一些。温度控制的另一个特点是惯性大加热到目标温度后即使停止加热温度也会在惯性作用下继续上升一段时间。这就是为什么温控PID里I项要设得很软Kd也要适中否则超调一下就可能高出目标好几度而且久久降不下来。5.5 从仿真到实机为什么仿真很稳实机就翻车很多人在Wokwi这类Arduino仿真平台上或者在Simulink里调好了PID参数兴致勃勃烧到实机上结果系统压根不稳定。这很正常因为仿真环境里你把传感器噪声、供电电压波动、机械摩擦、执行器死区这些真实世界的干扰全都屏蔽掉了。仿真里PID输出10执行器就老老实实执行10而实机上可能执行机构在输出小于40时根本不动40到60之间动作又诡异超过80又猛窜一截。这种非线性特性仿真基本模拟不出来。所以仿真平台更适合用来验证算法逻辑、熟悉PID公式、观察三个参数的理论影响真到实机阶段还是要老老实实按我前面说的三步法整定。这也是为什么我说“五分钟搞懂PID”是个伪命题——理解概念也许五分钟就够了但调好一个系统必须有耐心、有观察、有反复试验。如果你在做巡线小车、平衡车这类带强干扰的动态系统调参时间请务必预留一整晚。6. PID不是万能的何时该换别的算法写到这里我还是得负责任地告诉你PID确实适用范围极广但它不是银弹。有些系统的特点会让PID表现很差这时候死磕三个参数只会收获更多发际线后退的代价。第一种情况是大滞后系统。比如热水管很长冷水流过加热段要10秒才能把温度变化传到温度传感器。这种情况是反馈回路存在空档PID输出已经变了系统反馈要很久才反映出来。经典PID对这种系统很容易震荡解决的办法是使用Smith预估器或者串级PID但那已经超出“五分钟搞懂”的范畴了。第二种情况是强非线性系统。PID本质上是线性的输出是误差的线性组合。如果被控对象的特性在不同工作点差异巨大比如一个转矩特性随转速剧烈变化的直流电机固定一组Kp、Ki、Kd参数很可能出现低速时很稳定、高速时发疯的情况。应对思路包括使用变增益PID、模糊控制或者根据工作点查表切换不同PID参数。对Arduino项目来说查表切换参数是最简单直接的。还有一种情况是系统本身就是一个积分过程比如控制舵机位置本身位置就是角速度的积分PID里再带积分项容易产生双重积分效应导致响应慢、超调大。这个时候优先考虑PD控制就够了不要画蛇添足加I。至于如何判断自己是否应该放弃PID、改用更高级的方案我的标准很简单你花在调这组参数上的时间是否已经超过了项目本身其他所有部分的开发时间总和如果是那就应该停下来认真思考是不是被控对象特性对PID不友好或者你的执行机构精度不够而不是继续折磨那三个小数。我在实际使用中的体会是控制算法的选择永远是为项目目标服务的。Arduino平台的优势在于它的资源足以让你快速实现并验证一个PID闭环而踩过坑之后形成的直觉会让你在以后的每个项目里都收益颇丰。最后分享一个小技巧调参时千万别抛弃串口绘图功能把Serial.print输出的目标值、当前值、输出值喂给Arduino IDE自带的串口绘图器用眼睛去看曲线的形状比盯着串口监视器里刷屏的数字要直观得多那才是你判断P、I、D下一步该往哪儿调的最佳依据。
返回列表