PID控制 通俗理解和简单实践

目录

简介

PID实战简介

任务:控制机器车的轮胎达到目标转速。

恒定值控制器(BangBang)

P-Proportional 比例控制器(P)

I-Integral 积分控制器(PI)

D-Derivative 微分控制器(PID)

总结和反思


简介

网上不少关于PID控制的讲解,但是很多依然很抽象,或者只停留在理论层面上不够生动形象,所以自己花时间用Arduino写了一个简单的PID程序来理解+验证,并实现了两轮小车的轮子转速控制、转向控制以及巡线的PID控制。

扫描二维码关注公众号,回复: 14715641 查看本文章
该项目使用Visual Studio Code编写代码,Arduino Leonardo板子和 Pololu 3pi+ 32U4 五脏俱全小机器人。

7fe14d327dcc0db12f258b4c0ccc39f5.jpeg


PID实战简介

PID叫比例积分控制器,不理解的小伙伴会觉得很抽象,在这里我尝试通过实战让这种控制器更容易理解:我们将通过一个简单的小任务来一步步开发理解简单的PID控制器。

任务:控制机器车的轮胎达到目标转速。

已知:

        • 编码器转速- input。这里需要用到小车轮胎的编码器,但是这不是重点,我们只需要知道编码器是整个控制器的input,它将表示小车车轮的实时转速。
        • 代码 analogWrite(motorID, power); - output。该代码表示将power数值输出到指定硬件pin上运行,它将表示指定ID马达的功率大小。
        • 目标转速Target和差值Error,我们的控制器将根据差值确保车轮达到并维持在该值上。

未知:

        • 输出值u(t), t代表该时刻,t-1代表上一时刻,以此类推之后的(t)

现在就是非常直观简单的问题:根据 调整output使车轮达到Target转速。

恒定值控制器(BangBang)

最简单的方法就是转速小于Target时增大马达功率,转速大于Target时减小马达功率。增大减小值固定,然后我们就能很好的让车轮稳定在预计转速上了,但是它会在目标值附近疯狂摆动,因为每一次调整都是固定值,我们无法动态调整,差值Error大的时候多调一些,小的时候少调一些,所以我们引入P比例控制来解决这个问题。

P-Proportional 比例控制器(P)

P就是为了动态调整,差值Error大的时候多调一些,小的时候少调一些,所以我们直接使用Error带入控制器中,并加入Kp来调节其控制力度。

运行结果可以通过Ardunio的Serial Plotter直接观察到图像,不难看出比例控制效果更好了:

4c3e8754756973e15a52c33a574dec3f.png

若比例增益Kp大,在相同误差量下,会有较大的输出,反应更为迅速,但若比例增益太大,会使系统不稳出现超调。相反的,若比例增益小,若在相同误差量下,其输出较小,因此控制器会不敏感,容易出现稳态误差

什么是稳态误差?

当轮子转速非常接近预期值时,我们得到的Error差值将会很小,这就导致马达给出的功率变化力度也会响应变小,当环境存在摩擦力等情况时,就会出现马达功率和摩擦力平衡的情况,即虽然控制器一直在告诉马达加点力,马达也确实照做了,但是由于平衡了摩擦,轮子转速无法再有所提高,于是便稳定在了一个还没有到达目标的尴尬境地,这就是为什么我们下一步将用到I-积分控制来避免这个问题

7e15f6eaceb521c49c3f3b9bcaf9b148.png

I-Integral 积分控制器(PI)

为了解决稳态误差的问题,我们引入了积分控制。简单理解就是,如果控制器长时间没有让设备达到预期值,I控制器将会不断积累误差并将误差累加到控制器的输出上,这样一来随着时间的积累,控制器的输出越来越大,直到新的大功率克服外界影响从而让机器达到预期值。

虽然I控制器缓解了稳态误差的问题,但又有一个新问题,即它容易超调(累加多了)。

如果Ki值太低,那么I部分控制器的效果将会不明显,需要很长时间才能把稳态误差掰回来;但是一旦提高Ki,那么又容易得到一个过大的累加值,导致PI控制器容易被累加过多的误差值“带偏”,冲出预期值,然后控制器重新纠正错误,但是又一次超调。最终造成实际值在期望值周围来回摆动,但是最终会趋于稳定。

39c6e9999e9c820a4ca6a887f83e37ad.png

D-Derivative 微分控制器(PID)

D 的出现有时为了缓解I的超调现象,通俗来说就是表示控制器输出值在该时间点的速度。如果PI控制器输出的值变化速度过大,那么相应地D部分就会减去部分值来抵消这一超调。

注意∆Error=此刻Error -上时刻Error ,∆Error总是和PI输出值正负相反。

这样一来一个合适的Kd值能够缓和PI的超调。

2a27316772101061ba5fd75299280766.png

45e44a619ccec04319e36413c3ed202c.png

最终核心代码实现:


        float PID_intergral = 0, PID_derivative = 0, PID_lastError = 0;
        unsigned long PID_Intergral_Timer = 0, PID_Derivative_Timer = 0, PID_Intergral_RecordDuration = 100, PID_Derivative_RecordDuration = 200;
        /** Set specific Motor to specific speed by using Motor_PID control method, to avoid state error accumulation from P control method and to avoid the derivative error accumulation from PI control method
         * @param motorID: find MotorID in GlobalVarables.h
         * @param targetVelocity: the target velocity
         * @param Kp: the proportion differential between the target velocity and the current velocity: y= Kp(targetVelocity - currentVelocity)+I(stateError)+D(derivativeError)
         * @param Ki: the proportion differential between the state error and the current velocity: y= Kp(targetVelocity - currentVelocity)+I(stateError)+D(derivativeError)
         * @param Kd: the proportion differential between the derivative error and the current velocity: y= Kp(targetVelocity - currentVelocity)+I(stateError)+D(derivativeError)
         */
        void PID_ChangeSpeed_ToTarget(int motorID, float targetVelocity, float Kp, float Ki, float Kd)
        {
            int LeftOrRight = setMotorLeftOrRight(motorID);
            //Angular_Velocity[0]表示左轮,Angular_Velocity[1]表示右轮
            float error = targetVelocity - Angular_Velocity[LeftOrRight];

            if ((millis() - PID_Intergral_Timer) >= PID_Intergral_RecordDuration) // caculate the Intergal error
            {
                // Serial.println("Timer: " + String(millis() - PI_Timer));
                PID_intergral += error * 0.01;

                PID_Intergral_Timer = millis();
            }

            if ((millis() - PID_Derivative_Timer) >= PID_Derivative_RecordDuration) // caculate the derivative error
            {
                // Serial.println("Timer: " + String(millis() - PI_Timer));
                PID_derivative = (error - PID_lastError) / PID_Derivative_RecordDuration;
                PID_lastError = error;
                PID_Derivative_Timer = millis();
            }
            //最终PID:,单独拿出来可直接构成P,PI,PID3种控制器
            Motor_Value[LeftOrRight] += Kp * error + Ki * PI_intergral + Kd * PID_derivative;
            //防止异常值
            Motor_Value[LeftOrRight] = constrain(Motor_Value[LeftOrRight], 0, 100);
            //这里调用需要最终控制的硬件函数:
            motor.SetSingleMotorPower(motorID, Motor_Value[LeftOrRight]);
        }

整个工程文件将会放在GitHub上:待补充

总结和反思

PID控制可以应用在许多控制问题,多半在大略调整参数后就有不错的效果,不过有些应用下可能反而会有差的效果,而且一般无法提供非常理想的控制结果。一个好的PID控制器需要大量的调试和实践验证,非常费时费力,而且一旦环境发生大的改变,当前PID控制器可能就不好使了。

BangBang控制器:它是一个简单的控制器。它有很高的精度,不需要太多的功能操作和记忆。它的逻辑简单明了。然而,一个严重的缺点是,它将产生许多振动,导致更多的问题(如对机器人的损害和增加误差)。这就需要更多的逻辑叠加来抑制振动,例如增加几层不同强度的 "如果 "判断来处理多种错误强度。如果所需的应用场景只追求确定的精度而不考虑其他问题,Bang-Bang控制器的成本最低。

P控制器:它比Bang-Bang控制器更平稳。P控制器的输出随着误差值的变化而变化,在某些情况下表现得更好。然而,它最大的问题是它的稳态误差可能相当大。当预期值接近时,P项的再反应变小,这通常发生在Kp的小值或显著的外部阻力影响情况下。然而,如果我们增加Kp的值,这种现象可以得到缓解,如图所示,但太高的Kp会导致系统不稳定。因此,P控制器符合应用场景,只需要高响应速度、低颠簸和低精度。

62866de5057b1ac92ed64fcc75879cbb.png

2ab97de1f904f962dade46e6a1e1ff0c.png

PI控制器。对于P控制器的大稳态误差问题,叠加积分响应,将一个周期内的误差值之和乘以一个正常数Ki。然而,简单的PI控制器将导致超调,输出将围绕期望值波动,因为系统不能消除冗余的修正。然而,由于正和负的积累相互克服,PI系统最终会稳定在设定值上,所以PI在一段时间后有一个非常准确的再响应。因此,PI控制器适用于需要高精度、低振动和接受某些超调和时间冗余的情况。

PID控制器:为了克服超调,应用了一个导数再响应。它计算误差的导数,并将其乘以一个正的常数Kd,这可以显著地抑制PI控制器的过冲现象。然而,当它突然抑制了积分控制器的过冲时,可能会产生最小量的颠簸。PID控制器整合了上述两个控制器的优点并对其进行了优化。因此,它具有低过冲、快速响应和高精确度。对于组合线任务,由于90度角的任务,P、PI和PID控制器的性能被不断增加的颠簸计数所降低;这是因为在做0-90度的任务时,它触发了车轮逆转的部分。PID的成本时间要调到一个预期的水平,可能不适合所有的情况。例如,在直线跟踪的直角任务中。

我们不敢否认,我们的算法还不够完美,可能需要进一步的研究和实验。此外,由于开发周期较长,它不适合于一些期限有限的项目。此外,在某些情况下,P或Bang-Bang控制器已经足够了。然而,有必要根据实际情况和许多实验来选择最合适的控制器。

猜你喜欢

转载自blog.csdn.net/weixin_46146935/article/details/128721674
今日推荐