Skip to content

24_PD制御

YukiOrigane edited this page Apr 14, 2020 · 1 revision

今回は,前回のPI制御に引き続き,PID制御までの説明を試みます。 試みましたが駄目でした。PD制御までです。

P制御の問題点② 振動

前回,P制御の問題点として"定常偏差"というものを取り挙げました。
これは,外乱等の要因により,最終的な収束値が,目標値とずれることを指します。
定常偏差は,I制御の導入により無くすことができます。

しかしながら,P制御もしくはI制御は,ゲインを大きくしていくと軌道がぶれぶれ(振動的)になります。
やりすぎると,ラインから外れてしまいます。制御では発散と呼びます。

一旦おさらいがてらグラフを描いてみましょうか。
ひとまずP制御だけにしましょう。前回のソースコードで,Ki = 0としてください。
一応,controller.mのコードを以下に示します。

persistent tr_sensor sum_e
Kp = 0.01;
Ki = 0.0;   % 積分ゲインを0に
r = 0;
if isempty(tr_sensor)
    tr_sensor = -7:1:7;
end

if isempty(sum_e)
    sum_e = 0;
end

z = 0;
Nz = ones(1,15)*( value_light_sensor(:,1)<50 );
if Nz > 0
    z = ( tr_sensor*( value_light_sensor(:,1) < 50 ) ) / Nz;
end

e = r-z;
sum_e = sum_e + e*delta_t;

u = [1;-1] .* ( Kp * e + Ki * sum_e ) + [0.3; 0.3];

実行してみましょう。グラフはこんな感じになりますか?

オーバーシュート

ラインを一度検知した後も,何度か大きくラインをはみ出しています。
この赤で囲ったはみ出し部分を,制御の用語でオーバーシュートと呼びます。
オーバーシュートが大きかったり,振動的である期間が長いと,例えば通路が狭いときに衝突する危険性があります。

これの補正を試みるのが,D制御です。

D制御

D制御(微分制御,Derivative Control)は,その名の通り,偏差を微分します。
あとは今まで通り,定数倍して入力に加えるだけです。
P制御と合わせて数式で見てみると

eq_24.1

少し雑ですが離散化して,

eq_24.2

大体こんな感じになります。
それでは実装してみましょう。

コードはこんな感じです。Ki = 0として積分項は止めてあります。(自分で実装したい人はそれでもいいですよ)

persistent tr_sensor sum_e pre_e
Kp = 0.01;
% Ki = 0.02;
Ki = 0.0;   % 積分ゲインを0に
Kd = 0.0001;

r = 0;
if isempty(tr_sensor)
    tr_sensor = -7:1:7;
end

if isempty(sum_e)
    sum_e = 0;
    pre_e = 0;
end

z = 0;
Nz = ones(1,15)*( value_light_sensor(:,1)<50 );
if Nz > 0
    z = ( tr_sensor*( value_light_sensor(:,1) < 50 ) ) / Nz;
end

e = r-z;
sum_e = sum_e + e*delta_t;

u = [1;-1] .* ( Kp * e + Ki * sum_e + Kd * (e - pre_e)/delta_t ) + [0.3; 0.3];

pre_e = e;

まず,

persistent tr_sensor sum_e pre_e

として,前回の偏差eの値を記録するために,pre_eという永続変数を作成します。
続いて,

Kd = 0.0001;

微分ゲインKdを設定します。

なんでPI制御の時と同様にKpと同じ0.01くらいから試さないんですか?オーダー2つも違いますよ?
と疑問に思った方,その通りです。
これは私の勘が0.01という値に対し警告を発しているためです。

オーダーいじるとどうなるかは後で見てみましょう。一度,Kd = 0.0001でお許しください。

説明を続けます。

if isempty(sum_e)
    sum_e = 0;
    pre_e = 0;
end

の部分でpre_eを初期化します。sum_eが初期化されてなければ、pre_eも初期化されていないので,同じ場所で初期化を行います。

ちなみに,今回,pre_eは初期値を0として扱いますが,pre_eの初期化には十分注意してください。
安直に0にすると,初回時に値が微分項が吹っ飛ぶ可能性があります。
本来,k = 0のとき,e(k-1)が存在しないことが原因です。
安全性を考えるならば,初回時のみKd = 0とするなどして,微分項が作用しないようにした方が良いでしょう。
ここでは,コードのシンプルさを要求すること,初回が0(ライン未検出)としてもそこまで被害は出ないと考えたため,このまま行きます。

u = [1;-1] .* ( Kp * e + Ki * sum_e + Kd * (e - pre_e)/delta_t ) + [0.3; 0.3];

で制御を行いますが,微分の項が追加されています。delta_tで偏差の差を割り算します。
積分項も残りっぱなしですが,Ki = 0としているので寄与しません

最後に,

pre_e = e;

として,今回の偏差を記録しておきます。

お疲れ様でした。実行してグラフを見てみましょう。

PD_1

どうですか?オーバーシュートが軽減したことがわかりますか?
横並びで比較したのが次の図です。

PandPD

左がP制御,右がPD制御です。
P制御の時は510を超えていた山が,PD制御では若干減少しているのがわかりますね。
これが微分項の威力です。

とはいえ,もう少しオーバーシュートを抑えたいですよね?
お待ちかね,チューニングのお時間です。

慣例に従い,オーダーを一つ上げてKd = 0.001として,実行してみましょう

PD_2

はい素晴らしい!
オーバーシュートがなくなりました!!
大分綺麗に追従するようになりましたね。

ですが,一つ困ったことが生じているのに気が付きますか?
そう,追従が遅くなっているのです。
改めて,Kd = 0.000とKd = 0.001でのグラフを比較してみましょう

PD_3

このように,最終的な収束値505付近に到達するのは,Dゲインが小さいほうが速いです。
結局微分項は応答をなまらせているため,このような状況になります。

もう一点,D制御には問題点があります。
ここでようやく,Kp = 0.01, Kd = 0.01の同じ値を試してみましょう。
皆さんもやってみてください。

滅茶苦茶になりましたか?
コースアウトすることもあるかもしれませんが,下に示すのはかろうじてゴールできた場合です。

PD_4

振動を抑えるはずのD制御が,制御を不安定にしてしまっています。
これは,微分項が誤差を増幅してしまう性質があることによります。
そのため,計測誤差やら量子化誤差(測定値の離散化の際に生じる誤差)等によりロボットが大きく振り回されてしまうのです。

以上,お分かり頂けましたか?
D制御はオーバーシュートを軽減し,応答曲線を滑らかにしてくれる一方で,追従の遅れや誤差の増幅につながります。
結局,適切なチューニングが非常に重要ということになります。
さて,いよいよ,P,I,Dそれぞれの良さを組み合わせた,PID制御に入ります。

Clone this wiki locally