本帖最后由 SeanM 于 2022-6-21 17:24 编辑
问题
积分饱和是新手最经常遇到的问题。当PID算法不知道自己的上限在哪里的时候,这个问题最容易发生。比如说,arduino的PWM输出在0-255之间,一般来说PID并不知道这个限制。这就会导致PID的输出可能是300、400甚至是500。实际上,PWM被限制到了255以内,PID会不断输出更大的值,但实际上却到不了这个值。
积分饱和问题通常表现为奇怪的控制延迟。从上图可以看出来,PID的输出大于外部的限制。当控制目标(setpoint)被调低后,输出依然会在255的限制线以上,并持续一段时间。
解决方案-Step 1
有很多办法可以解决积分饱和的问题。我选择的解决方案是告诉PID输出的限制值是多少。下面代码中可以看到新加了一个SetOuputLimits()函数.当PID输出达到外部限制时,算法会停止积分。因为这时候再积分也没有用。由于积分不在饱和,当控制目标(setpoint)下降后到可以控制的区间后,PID的输出会立刻做出反映。
解决方案-Step 2
再仔细观察上面的图,可以发现当我们搞定了积分饱和造成的控制延迟后,事情被没有完全和我们预料的一样。由于比例项和微分项的影响,PID实际的输出与它以为的输出还是有差异。
即使积分项已经被控制了,P和D依然会导致结果超过输出上限。对于我来说,这还是不可以接受。因为调用了SetOutputLimits(),就应当保证输出不会超过上限。所以在Step 2,我们应该保证整个输出值(Output value)不高于设定值。
(你可能会想,为什么我们要同时控制输出值和积分项。只控制输出值不就好了吗?我们只控制输出值,那么积分项就会逐渐累计,越来越大。在这个时候,输出看起来没有什么以上,但是一旦调整了控制目标,你就能看到明显的延迟(因为积分项已经很大了,难以马上抵消掉))
代码
- /*working variables*/
- unsigned long lastTime;
- double Input, Output, Setpoint;
- double ITerm, lastInput;
- double kp, ki, kd;
- int SampleTime = 1000; //1 sec
- double outMin, outMax;
- void Compute()
- {
- unsigned long now = millis();
- int timeChange = (now - lastTime);
- if(timeChange>=SampleTime)
- {
- /*Compute all the working error variables*/
- double error = Setpoint - Input;
- ITerm+= (ki * error);
- if(ITerm> outMax) ITerm= outMax;
- else if(ITerm< outMin) ITerm= outMin;
- double dInput = (Input - lastInput);
-
- /*Compute PID Output*/
- Output = kp * error + ITerm- kd * dInput;
- if(Output > outMax) Output = outMax;
- else if(Output < outMin) Output = outMin;
-
- /*Remember some variables for next time*/
- lastInput = Input;
- lastTime = now;
- }
- }
-
- void SetTunings(double Kp, double Ki, double Kd)
- {
- double SampleTimeInSec = ((double)SampleTime)/1000;
- kp = Kp;
- ki = Ki * SampleTimeInSec;
- kd = Kd / SampleTimeInSec;
- }
-
- void SetSampleTime(int NewSampleTime)
- {
- if (NewSampleTime > 0)
- {
- double ratio = (double)NewSampleTime
- / (double)SampleTime;
- ki *= ratio;
- kd /= ratio;
- SampleTime = (unsigned long)NewSampleTime;
- }
- }
-
- void SetOutputLimits(double Min, double Max)
- {
- if(Min > Max) return;
- outMin = Min;
- outMax = Max;
-
- if(Output > outMax) Output = outMax;
- else if(Output < outMin) Output = outMin;
-
- if(ITerm> outMax) ITerm= outMax;
- else if(ITerm< outMin) ITerm= outMin;
- }
复制代码
新加一个函数,让用户可以指定输出的限额(52-63行)。这个限额同时限制了积分项(I-Term)(17-18行)和输出值(Output)(23-24行)
结果
结果
正如我们预料的一样,饱和现象被消灭了,输出也保持在我们要求的范围内。这意味着我们没有必要在算法外部另加限制。如果你要把输出限制在23-167的范围内,可以直接设置Output的范围。
译者注:关于积分饱和还可参考Integral (Reset) Windup, Jacketing Logic and the Velocity PI Form(https://controlguru.com/integral-reset-windup-jacketing-logic-and-the-velocity-pi-form/) |