PID(比例積分微分)出力のスケーリング


8

式を使用してPID関数を実装しました。

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

出力を特定の範囲に保つにはどうすればよいですか?0〜255と言います0〜255の範囲外の値を無視すると、不安定な動作になりますか?

回答:


7

次の2つの問題を処理する必要があります。

  1. 算術オーバーフロー
  2. インテグレーターのワインドアップ

算術オーバーフローはかなり簡単です。整数演算を行うときは常に、より広い幅の中間値を使用するようにしてください。たとえば、aおよびbが16ビットであり、それらを加算または減算する場合は、32ビットの中間値を使用します。値を16ビット値(符号なしの場合は0から65535、符号付きの場合は-32768から32767)の範囲に制限してから、16ビットにキャストダウンします。あなたがいる場合は、絶対に確認してくださいあなたがオーバーフローを得ることはできないことをあなたがしているので、絶対に確認してください入力変数の範囲の、そしてあなたは、このステップをスキップしますが、用心することができます。

インテグレーターのワインドアップの問題はより微妙です。長時間にわたって大きなエラーが発生し、コントローラー出力の飽和限界に達したが、エラーがゼロ以外の場合、インテグレーターはエラーを蓄積し続け、達成する必要があるよりもはるかに大きくなる可能性があります。定常状態。コントローラーが飽和状態から脱すると、インテグレーターがダウンする必要があり、不必要な遅延が発生し、コントローラーの応答が不安定になる可能性があります。


別のメモ:

私は強くお勧めします(そうです、この質問は18か月前であることを知っているので、あなたはおそらくあなたの仕事を終えていますが、読者の利益のために、そうではないと仮定しましょう)積分項を別の方法で計算することです:Kiの代わりに*(積分誤差)、(Ki * error)の積分を計算します。

そうする理由はいくつかあります。それらは、PIコントローラーを正しく実装する方法について書いたブログ投稿で読むことができます。


6

通常、積分項(エラーの合計)を制限します。リンギングを処理できない場合は、ゲインを下げてシステムを過度に減衰させる必要があります。また、エラー、prevError、および(エラーの合計)の変数が、クリップまたはオーバーフローしないより大きな変数であることを確認してください。

修正をクリップし、それを次のエラー項にフィードバックすると、非線形性が発生し、クリップするたびに制御ループがステップ応答を取得し、揺れ動く動作を引き起こします。


4

検討する必要があるいくつかの改良点:

  • 合計と差を使用するだけでなく、適切なフィルターを使用して適切なI項とD項を生成します(そうしないと、ノイズ、精度の問題、その他のさまざまなエラーが発生しやすくなります)。注意:あなたのI用語に十分な解像度があることを確認してください。

  • DおよびI項が無効になっているプロップバンドを定義します(つまり、プロップバンド外の比例のみの制御、プロップバンド内のPID制御)


2

まあ、ジェイソンSが言ったように、この質問は古いです:)。しかし、以下は私のアプローチです。XC8コンパイラを使用して、8MHzの内部発振器で実行されているPIC16F616にこれを実装しました。コードはコメントでそれ自体を説明する必要があります。そうでない場合は、私に尋ねてください。また、後で自分のWebサイトで行うように、プロジェクト全体を共有することもできます。

/*
 * 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.
    }
}

typedefを使用する<stdint.h>ためuint8_tuint16_t、いうよりunsigned intunsigned char
Jason S

...しかし、一体なぜunsignedPIコントローラーの変数を使用しているのですか?これにより、コードが非常に複雑になります。個別のif/elseケースは不要です(エラー符号に応じて異なるゲインを使用する場合を除く)。導関数の絶対値も使用していますが、これは正しくありません。
Jason S

@JasonS現時点では覚えていませんが、当時は+-127では不十分だったと思います。また、導関数の絶対値を使用する方法がわかりません。コードのどの部分を意味しますか?
abdullah kahraman 2015

PID_derivative割り当てを含む行を見てください。とを切り替えても同じ値にPID_errorなりPID_lastErrorます。そして、そのことについてあなたは既に失ってしまったPID_error場合は、最後の時間:の記号をsetMotorSpeed =8してcurrentMotorSpeed = 15、そして今回setMotorSpeed = 15currentMotorSpeed = 8、あなたが買ってあげる、PID_derivative間違って0の値を、。
Jason S

場合は、コンピューティング製品のためのあなたのコードが間違っているunsigned char8ビットタイプで、unsigned int場合:16ビットタイプであるPID_kd = 8PID_derivative = 32、その後、彼らの製品になります(unsigned char)256 == 0Cで、同じタイプの2つの整数の積は、Tは、そのことをもあるので、同じタイプT。8x8-> 16の乗算を実行する場合は、乗算の前に項の1つを符号なし16ビット数にキャストするか、またはコンパイラ組み込み関数(MCHPが「ビルトイン」と呼ぶ)を使用して、 8x8-> 16を掛けます。
Jason S
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.