PID制御:次のループの前に遅延を追加するのは良い考えですか?

Jan 11 2021

差動駆動ロボットを正確な度数で回転させるためにC ++でPID制御を実装していますが、多くの問題があります。

ループランタイムが速いため、制御ループを早期に終了します

ロボットがその誤差を0.5度未満と測定した場合、ロボットは制御ループを終了し、ターンが「終了」したと見なします(.5は、ある時点で変更される可能性のあるランダムな値です)。制御ループが非常に高速で実行されているため、ロボットは非常に高速で回転し、設定値を超えて回転し、ループ/カットモーターの出力を終了することができます。これは、ロボットが短時間設定値にあったためです。これがPID制御の全体的な目的であり、オーバーシュートすることなく正確に設定値に到達することですが、この問題により、PID定数の調整が非常に困難になっています。たとえば、安定した振動が発生するようなkpの値を見つけようとしますが、ロボットは設定値を超えると「終了」したと見なすため、振動は発生しません。これを修正するには、ロボットが終了する前に一定時間設定値になければならないシステムを実装しました。これは効果的で、振動が発生する可能性がありますが、ループを早期に終了するという問題は異常な問題のようであり、私の解決策です。正しくない可能性があります。

実行時間が速いため、D項は効果がありません

Pだけを使って制御された方法でロボットを振動させたら、オーバーシュートを防ぐためにDを追加しようとしました。ただし、これはほとんどの時間効果がありませんでした。制御ループが非常に高速で実行されているため、20回のうち19回ループするため、エラーの変化率は0です。ロボットが動かなかったか、十分に動かなかったためです。その時に測定されます。これを確認するために、エラーの変化と各ループの微分項を印刷しました。これらは両方とも、妥当な値を取得する前に約20ループサイクルで0になり、さらに20サイクルで0に戻ることがわかりました。私が言ったように、これはループサイクルが非常に速いため、ロボットが文字通り、エラーの顕著な変化に対して十分に動いていないためだと思います。これは、D項がほとんど常に0であるため、ロボットの動きに本質的に影響を与えないことを意味するため、大きな問題でした。この問題を修正するために、0の値の代わりに、導関数の最後のゼロ以外の値を使用してみました。しかし、これはうまく機能せず、最後の導関数が現在のエラーの変化率を表していない場合、ロボットはランダムに振動します。

注:静的摩擦係数にも小さなフィードフォワードを使用しており、このフィードフォワードを「f」と呼びます。

遅延を追加する必要がありますか?

これらの問題の両方の原因は非常に高速に実行されるループであると私は考えたので、私が考えたのはループの最後に待機ステートメントを追加することでした。ただし、意図的にループを遅くすることは、全体的に悪い解決策のようです。これは良い考えですか?

turnHeading(double finalAngle, double kp, double ki, double kd, double f){
    std::clock_t timer;
    timer = std::clock();

    double pastTime = 0;
    double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);

    const double initialHeading = getHeading();
    finalAngle = angleWrapDeg(finalAngle);

    const double initialAngleDiff = initialHeading - finalAngle;
    double error = angleDiff(getHeading(), finalAngle);
    double pastError = error;

    double firstTimeAtSetpoint = 0;
    double timeAtSetPoint = 0;
    bool atSetpoint = false;

    double integral = 0;
    double derivative = 0;
    double lastNonZeroD = 0;

    while (timeAtSetPoint < .05)
    {
        updatePos(encoderL.read(), encoderR.read());
        error = angleDiff(getHeading(), finalAngle);

        currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
        double dt = currentTime - pastTime;

        double proportional = error / fabs(initialAngleDiff);
        integral += dt * ((error + pastError) / 2.0);
        double derivative = (error - pastError) / dt;
        
        //FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
        // if(epsilonEquals(derivative, 0))
        // {
        //     derivative = lastNonZeroD;
        // }
        // else
        // {
        //     lastNonZeroD = derivative;
        // }

        double power = kp * proportional + ki * integral + kd * derivative;

        if (power > 0)
        {
            setMotorPowers(-power - f, power + f);
        }
        else
        {
            setMotorPowers(-power + f, power - f);
        }

        if (fabs(error) < 2)
        {
            if (!atSetpoint)
            {
                atSetpoint = true;
                firstTimeAtSetpoint = currentTime;
            }
            else //at setpoint
            {
                timeAtSetPoint = currentTime - firstTimeAtSetpoint;
            }
        }
        else //no longer at setpoint
        {
            atSetpoint = false;
            timeAtSetPoint = 0;
        }
        pastTime = currentTime;
        pastError = error;
    }
    setMotorPowers(0, 0);
}

turnHeading(90, .37, 0, .00004, .12);

回答

3 UgoPattacini Jan 12 2021 at 05:14

コントローラを外さないでください。

コントローラの目的は、事前に決定された動的応答に従ってシステムを目的の設定値に誘導するだけでなく、このタスクを妨げる可能性のある潜在的な外部要因を打ち消すことです。システムが設定値に達すると、設定値から遠く離れてシステムを駆動する外乱について考えてみてください。したがって、コントローラーは常に動作可能である必要があります(タスク自体を変更する必要があり、その結果、コントローラーも変更する必要がある場合を除く)。

この目的のために、あなたは確かに積分部分を合計する必要があります。それはモデル化されていない量と外乱の存在下でヌル定常状態エラーを達成する責任があります。

これはかなり関連したリソースです: https://robotics.stackexchange.com/a/19198/6941。

導関数を省略します。

業界のPIDコントローラーの95%はPIコントローラーです(「フィードバックシステム」Astrom、Murrayを参照)。これは、D部分が遅いプロセス(温度やタンクレベルの調整に関係するプロセスなど)でのみ重要な役割を果たすことができるためです。これは間違いなくあなたのケースではありません。微分項の難しさについてどういうわけか関連するリソースは次のとおりです。

  • https://robotics.stackexchange.com/a/21556/6941。
  • https://robotics.stackexchange.com/a/21555/6941。

Pだけを使って制御された方法でロボットを振動させたら、オーバーシュートを防ぐためにDを追加しようとしました。

さて、あなたはあなたのコントローラーを調整するためにZiegler-Nicholsの処方に従っているようです。あなたがあなたに利益の見積もりを与えるために固執する必要がある表があります。ただし、これらはヒューリスティックであり、あなたのケースでは機能しない可能性があります。

Dパーツをドロップして、PIコントローラーに焦点を合わせるだけです。意図的に生成された振動に依存しない別のZNバリアントもあります。https://robotics.stackexchange.com/a/21616/6941。

システムに遅延を追加しないでください。

遅延は制御ループで対処するのに悪者であり、最悪の獣エンジニアは、システム全体を不安定にする位相マージンを大幅に削減するため、戦わなければなりません(これを見てください🎥)。

ループが速すぎると思われる場合は、入力整形をセットポイントに適用して(たとえば、最小ジャーク軌道を使用して)、鋭いステップの開始を滑らかにします。閉ループシステムはより優雅に反応します。もう1つの可能性は、Pゲインが高すぎることです。コントローラーをデチューンするだけです。

この点で、積分項Iを挿入する場合は、サンプル時間について推論する必要があります。 $T_s$ 同じように。

1 TimWescott Jan 12 2021 at 08:36

コントローラの固定サンプルレートを決定する方がはるかに良いでしょう。非常に大まかな経験則では、線形領域動作した後、ループから抜け出すために必要な整定時間、サンプリング間隔を整定時間の10分の1から100分の1にする必要があります。言い換えると、サンプルレートは、必要なループ帯域幅の10〜100倍高速である必要があります。

小さな追加ボーナスとして、これはループの外側でdtを計算できることを意味します

好みにより、サンプルレートはハードウェア、たとえばハードウェアタイマーによって決定されるべきであり、さらに良いことに、位置のサンプリングは、タイマーからトリガーされるハードウェアによって行われるべきです。これにより、ソフトウェアのリアルタイムの制約が大幅に緩和されます。

妥当なサンプルレートを使用していて、エンコーダーに変更を登録していない場合は、エンコーダーに十分なステップがありません。

@Ugo_Pattachiniに同意しません。ループにモーターが含まれ、コントローラーが十分にクリーンである場合、いくつかの差動アクションが有益な場合があります。ただし、帯域制限が必要になる可能性は十分にあります(つまり、リードラグコントローラーが必要です)。パンツの座席のPID調整を行う場合は、次のツールがない可能性があります。帯域制限を正しく設定します。

ただし、エンコーダーのサンプリングに非常にタイトなタイミングを適用できない場合は、微分制御を使用しようとしないでください。不規則なサンプリング時間とモーターの速度によって余分なノイズが発生し、微分コントローラーはノイズを増幅する傾向があります。制御するクリーンな信号を調整できない場合、微分制御は適していません。