ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PID控制算法详解:从原理到STM32/Arduino代码实现与调参

PID控制算法详解:从原理到STM32/Arduino代码实现与调参 1. PID控制从“感觉”到“精准”的工程艺术如果你玩过四轴飞行器、调试过3D打印机或者只是想让一个小车沿着黑线稳稳当当地跑那你大概率已经和PID控制器打过交道了。它不像深度学习那样充满神秘感也不像某些复杂算法那样需要深厚的数学功底。PID更像是一位经验丰富的老师傅靠着一套朴实无华但极其有效的“感觉”来让系统听话。这种“感觉”具体来说就是比例P、积分I、微分D三种作用的组合。今天我们不谈高深的理论推导就从最接地气的角度聊聊PID到底在干什么以及最让初学者头疼的两个实现形式增量式和位置式最后用代码把它们“敲”出来看看在实际的微控制器比如STM32、Arduino或者上位机比如Python里它们是怎么呼吸和工作的。简单来说PID控制器就是一个“纠错专家”。它的任务就是让系统的实际值比如电机当前转速、小车当前位置、水温当前温度尽可能地紧紧跟随我们设定的目标值。这个“跟随”的过程不是一蹴而就的PID通过计算“误差”目标值减去实际值然后对这个误差进行三种不同的加工处理最终输出一个控制量去驱动执行机构比如电机的PWM占空比、阀门的开度。比例项负责“当下误差有多大我就用多大劲”积分项负责“历史欠的账我得慢慢补上”微分项则像个“预言家”负责“看这趋势误差要变大我得提前刹个车”。这三者配合才能实现快速、平稳且精准的控制。那么为什么会有“增量式”和“位置式”之分呢这其实是输出控制量的两种不同“表述方式”。位置式PID直接计算并输出一个“绝对”的控制量比如“PWM占空比应该设置为50%”。而增量式PID计算的是控制量的“变化量”比如“PWM占空比应该在上一拍的基础上增加5%”。这个小小的区别背后却关联着抗积分饱和、执行器限制、无扰切换等一系列工程实践中的关键问题。对于嵌入式开发、机器人控制、智能车竞赛的爱好者而言理解并能在代码中正确实现这两种形式是迈入自动控制大门的关键一步。接下来我们就一层层剥开它的外壳。2. PID算法的核心比例、积分、微分项到底在做什么在深入代码之前我们必须把P、I、D这三个字母代表的物理意义和数学行为吃透。很多人调参调不好就是因为没真正理解每个参数拧动时系统到底会发生什么变化。我们用一个最经典的例子——控制一个直流电机的转速让它稳定在1000转/分钟——来贯穿整个解释过程。2.1 比例控制最直接的本能反应比例控制是PID的基石它的思想最简单误差越大出力越大。用公式表示就是P_out Kp * e(t)。其中Kp是比例系数e(t)是当前时刻的误差设定值 - 当前值。假设我们的电机当前是0转目标1000转那么初始误差e1000。如果Kp0.5那么比例项输出的控制量就是500这个数值对应PWM的占空比或其他控制信号。这个大力度的输出会让电机快速启动加速。比例控制的特点与局限快速响应误差一出现控制作用立刻产生没有延迟。静态误差这是纯比例控制无法克服的宿命。当电机转速接近1000转时误差e变小比例输出P_out也随之变小。最终系统会停留在一个平衡点此时比例输出产生的扭矩刚好等于电机负载如摩擦、阻力消耗的扭矩。这个平衡点对应的转速必然低于1000转存在的这个差值就是静态误差。Kp越大静态误差越小但不可能为零。可能振荡如果Kp太大系统会因为“用力过猛”而冲过头超过目标值后产生反向误差控制器又反向大力纠正导致系统在目标值附近来回振荡甚至发散。实操心得调试时通常先整定Kp。从小到大慢慢增加直到系统出现持续振荡此时记下这个Kp值称为临界增益Ku。在实际系统中初始Kp可以设为0.5 * Ku左右为积分和微分项留出作用空间。2.2 积分控制消除历史遗留的“偏差”积分项是为了消灭比例控制留下的静态误差而生的。它的思想是不仅看当前的误差还要把历史上所有的误差“累加”起来。误差持续存在的时间越长累积值就越大积分项输出的控制力也就越强直到把误差彻底消除。公式为I_out Ki * ∫ e(t) dt。在离散的数字系统中积分就是用累加来近似I_out Ki * ∑ e(t) * Δt其中Δt是控制周期。继续电机例子当系统因纯比例控制停在980转时始终存在e20的静态误差。积分项会把这个20不断累加到自己的“记忆”里。即使这个误差不变随着时间推移积分输出会越来越大最终额外提供一个控制力将转速从980推高到1000实现无静差控制。积分控制的特点与风险消除静差这是它的核心使命。滞后与超调积分动作是滞后的。它靠“攒”误差来发力当系统开始响应时积分项可能已经“攒”了很大的力导致系统冲过目标值产生超调。积分饱和这是积分项最著名的“坑”。当系统启动、设定值大幅跳变、或者执行机构已到达极限如PWM已输出100%时误差会持续较大且无法被消除。积分项会不受控制地一直累加到一个非常大的值正饱和或负饱和。当误差反向时需要很长时间才能把这个巨大的积分值“消化”掉导致系统响应迟钝出现明显的“失控”期。抗积分饱和是PID实现时必须处理的重大问题。实操心得Ki值一般较小需要耐心调试。过大极易引起超调和振荡。一个常见的处理积分饱和的方法是“积分分离”或“积分限幅”当误差过大或执行器饱和时停止积分项的累加或者对积分项的输出值进行限幅。2.3 微分控制预见未来的“阻尼器”微分项关注的是误差变化的趋势即误差的变化率。它的思想是如果误差正在快速减小即实际值正在快速逼近目标值那么就应该提前减小控制力防止冲过头反之如果误差在快速增大就应该提前增加控制力抑制这种坏趋势。公式为D_out Kd * de(t)/dt。离散化后常用D_out Kd * [e(t) - e(t-1)] / Δt。还是电机当转速从0加速到接近1000转时误差e从1000快速减小到接近0其变化率de/dt是一个很大的负数。微分项此时会输出一个负的控制量相当于提前“踩刹车”平滑地让转速稳定在目标值有效减小超调和振荡。微分控制的特点与陷阱预测阻尼改善系统动态性能提高稳定性减少超调。对噪声敏感这是微分项的致命弱点。因为微分本质是求差商如果反馈信号如编码器读数带有高频噪声那么e(t)-e(t-1)会被噪声严重放大导致控制输出剧烈抖动。因此在实际系统中微分项往往不是直接对误差微分而是对测量值实际值微分因为设定值是阶跃信号其微分在跳变处是无穷大会引入冲击。微分先行一种改进策略只对测量值微分公式变为D_out -Kd * [y(t) - y(t-1)] / Δt其中y(t)是实际测量值。这样可以避免设定值突变带来的微分冲击。实操心得Kd的调试要格外小心。通常先调好Kp和Ki再加入Kd来抑制振荡。如果系统加入了微分后反而更抖了首先要怀疑是不是测量噪声太大需要考虑对测量值进行滤波如一阶低通滤波或者使用“不完全微分”等形式来软化微分作用。将三项叠加就得到了经典的位置式PID控制律u(t) Kp*e(t) Ki*∫e(t)dt Kd*de(t)/dt。这个u(t)就是控制器输出的绝对控制量。接下来我们要看如何把这个连续的公式变成微控制器能执行的离散代码。3. 位置式PID最直观的离散化实现与代码陷阱位置式PID是教科书上最先介绍的形式它直接计算控制量的绝对大小。在数字系统中我们需要对连续的积分和微分进行离散化近似。假设我们的控制周期是T单位秒当前是第k个控制周期。积分项离散化用矩形法累加∫e(t)dt ≈ T * ∑ e(i)其中i从0到k。微分项离散化用后向差分de(t)/dt ≈ [e(k) - e(k-1)] / T。于是得到离散的位置式PID公式u(k) Kp * e(k) Ki * T * ∑ e(i) Kd * [e(k) - e(k-1)] / T为了编程方便通常把Ki和Kd系数中的T吸收进去定义Kp KpKi Ki * T// 离散积分系数Kd Kd / T// 离散微分系数这样公式简化为u(k) Kp * e(k) Ki * ∑ e(i) Kd * [e(k) - e(k-1)]下面是一个用C语言实现的、非常清晰但存在问题的位置式PID示例// 位置式PID结构体 typedef struct { float Target; // 目标值 float Current; // 当前值 float Err; // 当前误差 float Err_Last; // 上一次误差 float Integral; // 积分累计值 float Kp, Ki, Kd; // 注意这里的Ki, Kd是已经包含了时间T的离散系数 Ki, Kd float Output; // 控制器输出 } PID_TypeDef; // 位置式PID计算函数 float PID_Calc(PID_TypeDef *pid) { pid-Err pid-Target - pid-Current; // 计算当前误差 pid-Integral pid-Err; // 积分项累加 —— 这里是第一个大坑 // PID输出计算 pid-Output pid-Kp * pid-Err pid-Ki * pid-Integral pid-Kd * (pid-Err - pid-Err_Last); pid-Err_Last pid-Err; // 更新上一次误差 return pid-Output; // 返回绝对控制量 }这段代码看起来简洁但隐藏着几个工程实践中必须解决的致命陷阱积分饱和代码中pid-Integral pid-Err;这一行是万恶之源。一旦系统出现大幅误差如启动时或者输出已到极限但误差仍在积分项会无限制地累加“wind up”导致系统恢复时产生巨大的超调或延迟。输出限幅缺失执行机构如PWM总有物理限制输出范围通常是[OutMin, OutMax]。计算出的Output必须被限制在这个范围内否则指令无效。积分限幅与抗饱和处理必须与输出限幅配合。一种经典的方法是“遇限削弱积分法”当输出饱和时只累加与饱和方向相反的误差。例如当输出达到上限时如果误差仍为正说明积分已经够大了就不要再累加正误差了只有当误差为负时才进行累加帮助积分值尽快退出饱和区。一个增强了鲁棒性的位置式PID实现应包含以下关键改进float PID_Calc_Robust(PID_TypeDef *pid) { float output, increment; pid-Err pid-Target - pid-Current; // 计算比例项 output pid-Kp * pid-Err; // 计算积分项带抗饱和逻辑 pid-Integral pid-Err; // 对积分项本身进行限幅防止其无限增大 if (pid-Integral INTEGRAL_LIMIT) pid-Integral INTEGRAL_LIMIT; if (pid-Integral -INTEGRAL_LIMIT) pid-Integral -INTEGRAL_LIMIT; output pid-Ki * pid-Integral; // 计算微分项采用微分先行对测量值微分 // 假设结构体中增加了 Current_Last 变量 output pid-Kd * (pid-Current_Last - pid-Current); // 注意符号对测量值微分常用负号 pid-Current_Last pid-Current; // 对最终输出进行限幅 if (output OUT_MAX) output OUT_MAX; if (output OUT_MIN) output OUT_MIN; // 抗积分饱和如果输出饱和且误差与饱和方向同向则回退本次积分累加 if ( (output OUT_MAX pid-Err 0) || (output OUT_MIN pid-Err 0) ) { pid-Integral - pid-Err; // 撤销本次累加 } pid-Output output; pid-Err_Last pid-Err; return pid-Output; }位置式PID输出的是全量直接对应执行机构的位置如阀门开度、舵机角度因此被称为“位置式”。它的优点是与理论公式对应直观缺点是需要存储积分和并且积分饱和问题突出。接下来我们看另一种从不同角度出发能天然缓解一些问题的形式——增量式PID。4. 增量式PID关注变化更贴合数字执行器增量式PID的思想不是计算控制量的绝对大小而是计算本次控制周期相对于上一个周期控制量应该变化多少。即Δu(k) u(k) - u(k-1)。我们从位置式公式出发推导u(k) Kp*e(k) Ki*∑e(i) Kd*[e(k)-e(k-1)]u(k-1) Kp*e(k-1) Ki*∑e(i-1) Kd*[e(k-1)-e(k-2)]两者相减得到增量式公式Δu(k) Kp*[e(k)-e(k-1)] Ki*e(k) Kd*[e(k)-2e(k-1)e(k-2)]这个公式非常漂亮它有以下特点不需要累加积分和公式中只出现了当前和过去两个时刻的误差e(k),e(k-1),e(k-2)。这意味着我们不需要在内存中维护一个可能发散的积分项只需要保存最近几次的误差即可。手动模式无扰切换在实际工业控制中系统有时需要从“自动”PID控制切换到“手动”操作员给定控制量。对于位置式切换瞬间如果u(k)与手动值相差很大会产生冲击。而对于增量式切换时只需将手动值作为u(k-1)然后增量式输出的是变化量Δu叠加到当前手动值上切换可以非常平滑。抗积分饱和增量式PID的输出是增量当执行机构达到极限而停止动作时由于Δu的输出被限制或无效它不会像位置式那样积累一个巨大的积分值。一旦误差反向Δu能立即反向输出系统响应更快。更适用于执行增量式动作的机构例如步进电机它接收的就是“走多少步”的脉冲指令这天然就是增量形式。当然增量式也有其对应的实现结构。控制器输出Δu(k)后需要执行机构自己来累加u(k) u(k-1) Δu(k)。这个累加动作通常在控制器外部完成例如在给电机发送PWM增量指令的驱动代码里。下面是一个典型的增量式PID C语言实现// 增量式PID结构体 typedef struct { float Target; float Current; float Err, Err_Last, Err_BeforeLast; // 需要保存最近三次误差 float Kp, Ki, Kd; // 同样是离散化后的系数 float Output_Delta; // 输出增量 } PID_Incremental_TypeDef; float PID_Incremental_Calc(PID_Incremental_TypeDef *pid) { pid-Err pid-Target - pid-Current; // 核心增量式计算 pid-Output_Delta pid-Kp * (pid-Err - pid-Err_Last) pid-Ki * pid-Err pid-Kd * (pid-Err - 2*pid-Err_Last pid-Err_BeforeLast); // 更新误差历史 pid-Err_BeforeLast pid-Err_Last; pid-Err_Last pid-Err; return pid-Output_Delta; // 返回控制增量 } // 外部调用示例 void Control_Loop() { static float actual_control 0.0; // 实际控制量需要外部维护 float delta; // 获取当前测量值 Current_Measure pid.Current Current_Measure; // 计算控制增量 delta PID_Incremental_Calc(pid); // 对增量进行限幅防止单次变化过大 if (delta DELTA_MAX) delta DELTA_MAX; if (delta -DELTA_MAX) delta -DELTA_MAX; // 更新实际控制量 actual_control delta; // 对实际控制量进行最终限幅 if (actual_control OUT_MAX) actual_control OUT_MAX; if (actual_control OUT_MIN) actual_control OUT_MIN; // 将 actual_control 输出给执行机构如设置PWM比较寄存器 Set_PWM(actual_control); }可以看到增量式PID将积分作用隐藏在了增量公式的Ki*e(k)项中并通过外部累加来实现。它天然地避免了积分器的无限增长但需要外部维护一个实际控制量。位置式与增量式在数学上是等价的只是输出形式不同。选择哪一种更多地取决于执行机构的类型、系统安全考虑如防积分饱和和编程习惯。5. 从理论到实战在STM32与Arduino上驯服电机理解了算法最终要落地到代码。我们以最常用的STM32或类似ARM Cortex-M MCU和Arduino为例看看一个完整的PID电机速度环控制应该包含哪些要素。这里我们控制一个有编码器反馈的直流减速电机。5.1 系统组成与关键模块被控对象直流电机 减速箱。执行机构电机驱动板如TB6612、DRV8833、BTS7960接收MCU的PWM信号和方向信号输出功率驱动电机。传感器旋转编码器如AB相增量式编码器用于测量电机实际转速。控制器STM32/Arduino运行PID算法根据目标转速和编码器反馈计算PWM输出。控制周期一个至关重要的参数。通过定时器中断实现例如每10ms执行一次PID计算和输出更新。周期太短计算负担重且可能受噪声影响大周期太长系统响应慢。对于电机速度环通常选择5-20ms。5.2 STM32 HAL库环境下的实现框架假设使用STM32CubeMX配置了定时器TIM2用于产生10ms中断TIM3的通道1PA6产生PWMTIM4用于编码器模式读取电机转速。// pid.c #include pid.h PID_TypeDef speed_pid; // 定义一个速度环PID实例 void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd, float out_max, float out_min) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-OutputMax out_max; pid-OutputMin out_min; pid-Integral 0; pid-Err_Last 0; pid-Current_Last 0; // 其他变量初始化... } float PID_Calculate(PID_TypeDef *pid, float target, float current) { float output 0; pid-Err target - current; // 比例项 output pid-Kp * pid-Err; // 积分项带限幅 pid-Integral pid-Err; // 积分限幅 if(pid-Integral INTEGRAL_LIMIT_HIGH) pid-Integral INTEGRAL_LIMIT_HIGH; if(pid-Integral INTEGRAL_LIMIT_LOW) pid-Integral INTEGRAL_LIMIT_LOW; output pid-Ki * pid-Integral; // 微分项对测量值微分微分先行 output pid-Kd * (pid-Current_Last - current); // 注意current是本次测量值 pid-Current_Last current; // 输出限幅 if(output pid-OutputMax) output pid-OutputMax; if(output pid-OutputMin) output pid-OutputMin; // 抗积分饱和条件积分 if ( (output pid-OutputMax pid-Err 0) || (output pid-OutputMin pid-Err 0) ) { pid-Integral - pid-Err; // 撤销本次积分累加 // 或者更激进一点pid-Integral 0; // 积分清零法 } pid-Output output; pid-Err_Last pid-Err; return pid-Output; } // 在定时器中断服务函数中调用 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 10ms定时器 int32_t encoder_count; float current_speed_rpm, pwm_output; // 1. 获取当前速度通过编码器计数差值计算 encoder_count (int32_t)TIM4-CNT; // 读取编码器计数器值 TIM4-CNT 0; // 清零为下一个周期准备 // 将计数转换为转速单位RPM。公式速度 (计数/每转脉冲数) / (周期时间/60) current_speed_rpm (encoder_count / PULSES_PER_REVOLUTION) / (0.01 / 60.0); // 假设10ms周期0.01s // 2. 可选对速度测量值进行低通滤波抑制噪声 // current_speed_rpm LowPassFilter(current_speed_rpm); // 3. PID计算 pwm_output PID_Calculate(speed_pid, TARGET_SPEED_RPM, current_speed_rpm); // 4. 输出PWM注意pwm_output可能是浮点数需要转换为整数占空比 uint16_t ccr_val (uint16_t)(pwm_output); // 假设pwm_output范围对应0-ARR __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, ccr_val); } }5.3 Arduino平台上的快速实现在Arduino上逻辑类似但利用其简单的millis()函数进行非阻塞定时控制更适合初学者理解。// Arduino PID速度控制示例位置式带简化抗饱和 class SimplePID { public: float Kp, Ki, Kd; float target, current; float integral, prevError, prevMeasurement; float outputMin, outputMax; float integralMin, integralMax; SimplePID(float p, float i, float d, float outMin, float outMax) { Kp p; Ki i; Kd d; outputMin outMin; outputMax outMax; integralMin -100.0; integralMax 100.0; // 积分限幅 integral 0; prevError 0; prevMeasurement 0; } float compute(float setpoint, float measurement) { target setpoint; current measurement; float error target - current; // 比例项 float output Kp * error; // 积分项带限幅 integral error; if(integral integralMax) integral integralMax; if(integral integralMin) integral integralMin; output Ki * integral; // 微分项对测量值微分 output Kd * (prevMeasurement - current); // 注意符号 prevMeasurement current; // 输出限幅 if(output outputMax) output outputMax; if(output outputMin) output outputMin; // 简单的抗饱和输出饱和时冻结积分 if (output outputMax || output outputMin) { integral - error; // 撤销本次积分 } prevError error; return output; } }; // 全局变量 SimplePID myPID(1.0, 0.05, 0.01, 0, 255); // 参数需调试输出对应PWM 0-255 unsigned long lastTime 0; const long controlInterval 10; // 控制周期10ms float targetSpeed 100.0; // 目标速度单位需与测量值一致 int encoderPinA 2; // 编码器A相接中断引脚 int encoderPinB 3; volatile long encoderCount 0; // 注意在中断中修改需声明为volatile // 编码器中断服务函数 void readEncoder() { if(digitalRead(encoderPinB) HIGH) { encoderCount; } else { encoderCount--; } } void setup() { Serial.begin(115200); pinMode(encoderPinA, INPUT_PULLUP); pinMode(encoderPinB, INPUT_PULLUP); // 在Arduino Uno上引脚2支持外部中断0 attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoder, RISING); // 初始化电机驱动引脚等... } void loop() { unsigned long now millis(); if (now - lastTime controlInterval) { lastTime now; // 1. 计算当前速度每10ms的计数差值转换为速度 long count encoderCount; encoderCount 0; // 读取后清零 float currentSpeed (count / PULSES_PER_REV) / (controlInterval / 1000.0 / 60.0); // 转换为RPM // 2. PID计算 float pwmValue myPID.compute(targetSpeed, currentSpeed); // 3. 输出PWM analogWrite(motorPwmPin, (int)pwmValue); // 4. 可选串口打印调试信息 Serial.print(Target:); Serial.print(targetSpeed); Serial.print( Current:); Serial.print(currentSpeed); Serial.print( PWM:); Serial.println(pwmValue); } // 其他非实时任务... }这个框架清晰地展示了从传感器读数、速度计算、PID运算到PWM输出的完整链路。在实际项目中你还需要处理电机方向、编码器计数溢出、更精细的低通滤波等问题。6. 参数整定从“瞎调”到“有章可循”PID控制器性能好坏七八成取决于参数Kp, Ki, Kd或其离散形式Kp, Ki, Kd的整定。对于新手调参往往是玄学。这里介绍两种经典工程方法试凑法和齐格勒-尼科尔斯Ziegler-Nichols法。试凑法经验法步骤只调Kp将Ki和Kd设为0。逐渐增大Kp直到系统出现等幅振荡即临界振荡。此时的Kp记为Ku振荡周期记为Tu。加入Ki将Kp设为0.45 * Ku左右。然后慢慢增加Ki观察系统静态误差是否消除。Ki太大会引起超调和振荡需要耐心微调。加入Kd在Kp和Ki基本调好的基础上加入Kd。逐渐增大Kd观察系统超调是否减小响应是否更平滑。Kd对噪声敏感如果引入后系统抖动加剧可能是测量噪声太大需要先滤波或减小Kd。齐格勒-尼科尔斯Z-N法临界比例度法这是一种基于系统模型的公式化方法能给出一个不错的起点。同样先只加Kp找到临界振荡增益Ku和振荡周期Tu。根据下表计算参数针对标准PID即位置式 | 控制器类型 | Kp | Ti (积分时间) | Td (微分时间) | | :--------- | :--------- | :------------ | :------------ | | P | 0.5 * Ku | ∞ | 0 | | PI | 0.45 * Ku | 0.83 * Tu | 0 | | PID | 0.6 * Ku | 0.5 * Tu | 0.125 * Tu |对于离散PID我们需要Ki Kp * (T / Ti)Kd Kp * (Td / T)。其中T是控制周期。 例如用PID参数Kp 0.6*Ku,Ki Kp * (T / (0.5*Tu)),Kd Kp * ((0.125*Tu) / T)。实操心得Z-N法给出的参数通常比较激进超调可能较大需要在此基础上进行微调。对于电机这类存在非线性如静摩擦的系统Z-N法可能不太准确试凑法结合经验更常用。一个稳妥的调试顺序是先让系统在纯P控制下能大致跟随然后加I消除静差最后加D抑制超调。调试时最好能通过串口实时绘制目标值、实际值和输出值的曲线直观观察系统响应。7. 进阶话题与常见坑点排查当你基本调通一个PID回路后可能会遇到一些更棘手的问题。这里分享几个常见坑点和进阶思路。1. 微分项引发的高频振荡现象加入Kd后电机发出高频“滋滋”声测量值在高频小幅抖动。根因微分项放大了测量信号中的高频噪声。解决方案对测量值进行低通滤波在PID计算前先对current值进行滤波。一阶低通滤波current_filtered α * current (1-α) * current_filtered_last其中α是滤波系数0α1越小滤波越强但滞后也越大。使用不完全微分在标准的微分项上串联一个一阶惯性环节公式稍复杂但能有效平滑微分输出。许多工业PID控制器都有此选项。降低Kd或放弃微分对于速度环有时PI控制就足够了。2. 设定值突变Setpoint Change带来的冲击现象目标速度从0突然跳到很高PID输出一个巨大的值可能对电机或机械结构造成冲击。解决方案设定值斜坡不要直接给阶跃信号而是让目标值以一定的斜率从当前值渐变到新设定值。微分先行如前所述只对测量值微分不对误差微分可以避免设定值跳变时微分项的冲击。3. 采样周期T的选择与影响T太大控制不及时系统性能下降甚至不稳定。根据香农采样定理采样频率至少应为系统带宽的2倍以上工程上通常取10倍。T太小计算负担重且微分项Kd Kd/T会变得非常大对噪声极度敏感。同时过小的T可能导致积分项Ki Ki*T过小积分作用太弱。经验值对于电机速度环T在5-20ms是常见范围。温度控制等慢过程T可以是秒级甚至分钟级。4. 变积分与积分分离这是高级技巧用于改善动态性能。积分分离当误差大于某个阈值时取消积分作用Ki0防止大幅误差下积分饱和当误差进入小范围时才启用积分以消除静差。变积分积分系数Ki不是常数而是误差的函数。例如误差大时Ki小防止积分过快误差小时Ki大快速消除静差。5. 串级PID控制对于复杂对象单回路PID可能力不从心。例如无人机姿态控制外环是角度环输入目标角度输出目标角速度内环是角速度环输入目标角速度输出电机力矩。内环速度环响应更快负责抑制扰动外环位置/角度环响应稍慢负责精准定位。内环的输出作为外环的执行机构目标。调试时需要先调内环再调外环内环的带宽要远高于外环。PID控制器是一个深不见底的领域从基础的实现到应对非线性、时变系统的自适应PID、模糊PID有无数可探索的空间。但对于绝大多数应用理解透彻位置式和增量式的区别实现一个带抗饱和、输出限幅和滤波的稳健PID并掌握基本的试凑调参方法就已经能解决项目中80%的控制问题了。剩下的就是在不断的调试、观察和思考中积累那种属于工程师的“手感”。当你看着电机从抖动变得平稳小车从跑偏到循迹笔直那种成就感正是驱动我们不断折腾的最大乐趣。
返回列表