标度PID(比例积分微分)输出


8

我已经使用公式实现了PID函数,

correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)

如何使输出保持在一定范围内?说0-255如果我忽略任何不在0到255之间的值,它将产生抖动的行为?

Answers:


7

您需要处理两个问题:

  1. 算术溢出
  2. 积分器结束

算术溢出非常简单-每当您进行整数数学运算时,请确保使用较大宽度的中间值:例如,如果ab为16位,并且将它们相加/相减,则使用32位中间值值,并将其限制为16位值的范围(无符号的范围为0到65535,带符号的范围为-32768到32767),然后再转换回16位。如果您完全确定输入变量的范围是绝对不会溢出的,那么可以跳过此步骤,但要当心。

积分器结束问题更加微妙。如果长时间内有较大的误差,以至于达到控制器输出的饱和极限,但误差仍然不为零,则积分器将继续累积误差,可能会比要达到的误差大得多。稳定状态。一旦控制器达到饱和状态,积分器必须降下来,从而导致不必要的延迟,并可能导致控制器响应不稳定。


另一个注意事项:

我强烈建议(是的,我知道这个问题已有18个月了,所以您可能已经完成了任务,但是为了读者的利益,让我们假装不是),您应该以不同的方式计算积分项:代替Ki *(积分误差),计算(Ki *误差)的积分。

这样做有几个原因;您可以在我写的有关如何正确实现PI控制器的博客文章中阅读它们。


6

我通常只限制积分项(误差之和),如果您无法处理振铃,则需要降低增益以使系统过阻尼。还要确保您的error变量,prevError和(error sum)是不会裁剪或溢出的较大变量。

当您仅裁剪校正,然后将其反馈到下一个误差项时,将导致非线性,并且每次裁剪时,控制回路都会在其中得到阶跃响应,这将导致抖动的行为。


4

您可能需要考虑一些改进:

  • 使用合适的滤波器生成适当的I和D项,而不仅仅是使用和与差(否则您将很容易出现噪声,精度问题和各种其他错误)。注意:请确保您的I词具有足够的分辨率。

  • 定义一个禁用了D和I项的支撑带(即,在支撑带之外的仅比例控制,在支撑带内的PID控制)


2

好了,正如Jason S所说的,这个问题很古老:)。但是下面是我的方法。我已经使用XC8编译器在运行于8MHz内部振荡器的PIC16F616上实现了此功能。该代码应在注释中说明自己,否则请问我。另外,我可以共享整个项目,就像稍后在网站上所做的一样。

/*
 * applyEncoder Task:
 * -----------------
 * Calculates the PID (proportional-integral-derivative) to set the motor
 * speed.
 *
 * PID_error = setMotorSpeed - currentMotorSpeed
 * PID_sum = PID_Kp * (PID_error) + PID_Ki * ∫(PID_error) + PID_Kd * (ΔPID_error)
 *
 * or if the motor is speedier than it is set;
 *
 * PID_error = currentMotorSpeed - setMotorSpeed
 * PID_sum = - PID_Kp * (PID_error) - PID_Ki * ∫(PID_error) - PID_Kd * (ΔPID_error)
 *
 * Maximum value of PID_sum will be about:
 * 127*255 + 63*Iul + 63*255 = 65500
 *
 * Where Iul is Integral upper limit and is about 250.
 * 
 * If we divide by 256, we scale that down to about 0 to 255, that is the scale
 * of the PWM value.
 *
 * This task takes about 750us. Real figure is at the debug pin.
 * 
 * This task will fire when the startPID bit is set. This happens when a
 * sample is taken, about every 50 ms. When the startPID bit is not set,
 * the task yields the control of the CPU for other tasks' use.
 */
void applyPID(void)
{
    static unsigned int PID_sum = 0; // Sum of all PID terms.
    static unsigned int PID_integral = 0; // Integral for the integral term.
    static unsigned char PID_derivative = 0; // PID derivative term.
    static unsigned char PID_error; // Error term.
    static unsigned char PID_lastError = 0; // Record of the previous error term.
    static unsigned int tmp1; // Temporary register for holding miscellaneous stuff.
    static unsigned int tmp2; // Temporary register for holding miscellaneous stuff.
    OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
    while (1)
    {
        while (!startPID) // Wait for startPID bit to be 1.
        {
            OS_yield(); // If startPID is not 1, yield the CPU to other tasks in the mean-time.
        }
        DebugPin = 1; // We will measure how much time it takes to implement a PID controller.


        if (currentMotorSpeed > setMotorSpeed) // If the motor is speedier than it is set,
        {
            // PID error is the difference between set value and current value.
            PID_error = (unsigned char) (currentMotorSpeed - setMotorSpeed);

            // Integrate errors by subtracting them from the PID_integral variable.
            if (PID_error < PID_integral) // If the subtraction will not underflow,
                PID_integral -= PID_error; // Subtract the error from the current error integration.
            else
                PID_integral = 0; // If the subtraction will underflow, then set it to zero.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.

            // Proportional term is: Kp * error
            tmp1 = PID_Kp * PID_error; // Calculate the proportional term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.
        }
        else // If the motor is slower than it is set,
        {
            PID_error = (unsigned char) (setMotorSpeed - currentMotorSpeed);
            // Proportional term is: Kp * error
            PID_sum = PID_Kp * PID_error;

            PID_integral += PID_error; // Add the error to the integral term.
            if (PID_integral > PID_integralUpperLimit) // If we have reached the upper limit of the integral,
                PID_integral = PID_integralUpperLimit; // then limit it there.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.
        }

        // Scale the sum to 0 - 255 from 0 - 65535 , dividing by 256, or right shifting 8.
        PID_sum >>= 8;

        // Set the duty cycle to the calculated and scaled PID_sum.
        PWM_dutyCycle = (unsigned char) PID_sum;
        PID_lastError = PID_error; // Make the current error the last error, since it is old now.

        startPID = 0; // Clear the flag. That will let this task wait for the flag.
        DebugPin = 0; // We are finished with the PID control block.
    }
}

<stdint.h>for uint8_tuint16_t而不是unsigned intand中使用typedef unsigned char
杰森·S

...但是为什么unsigned要在PI控制器上使用变量呢?这增加了代码的复杂性。单独的if/else情况是不必要的(除非您根据错误符号使用不同的增益)您还使用了导数的绝对值,这是不正确的。
詹森·S

@JasonS目前我不记得了,但我想那时+-127对我来说还不够。另外,我不知道如何使用导数的绝对值,这意味着代码的哪一部分?
阿卜杜拉·卡拉曼2015年

查看您的包含PID_derivative作业的行;如果切换PID_error和,则得到的值相同PID_lastError。因此,您已经失去了PID_error的迹象:如果上次是setMotorSpeed =8currentMotorSpeed = 15,而这次是setMotorSpeed = 15currentMotorSpeed = 8,那么您将获得PID_derivative0值,这是错误的。
杰森S

同样,如果您的计算产品代码unsigned char是8位类型和unsigned int16位类型的,则是错误的:if PID_kd = 8PID_derivative = 32,那么它们的乘积将是(unsigned char)256 == 0,因为在C中,相同类型T的两个整数的乘积也是如果要进行8x8-> 16乘法,则需要在乘法之前将其中一项转换为无符号的16位数字,或使用设计用于以下目的的编译器固有函数(MCHP称其为“ builtins”)给你一个8x8-> 16的乘积
杰森S
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.