はじめに
”ロボカップアジアパシフィック2021あいち”で頂いた質問の続きです。
プログラムの動作を確認する方法は?
と言う質問がありました。
出前授業でも良く受ける質問ですね。
イメージとしては比例制御が主体で、積分制御と微分制御は比例制御の欠点を補う補助的な存在と考えて下さい。
比例制御
比例制御は目標値との差分に比例してモータパワーの制御を行います。
下図の様にラインセンサの値と目標値の差に対して制御を行います。
例えば、現在値と目標値の差分値が5%だと仮定します。
モータ制御値(%)が差分値と同じ値になるとした場合、DCモータに5%指示した時にモータは回転するでしょうか?
恐らく10%を超えないと動き出さないと思います。
これが比例制御の欠点で、目標値付近の制御は苦手です。
もう一つは急カーブを曲がるのが苦手です。
限界感度法で得た比例ゲインは直進時に左右に振動を繰り返さない値なので、(限界感度法については、以下参照下さい 評価の精度を上げるには治具を作ることをお勧めします。)
PID制御のパラメータ設定方法(限界感度法 ライ評価の精度を上げるには治具を作ることをお勧めします。ントレース編) - 隠居エンジニアのものづくり (hatenablog.com)
急カーブでは飛び出してしまいます。
PID制御のプログラム例(Arduino 抜粋)
#define KP 0.1 // 比例ゲイン
#define KI 1.4 // 積分ゲイン
#define KD 0.0032 // 微分ゲイン
#define DELTA_T 0.018 // 制御1周期時間 18ms
void Line_PID()
{
Line_Diff = LineVal_R - LineVal_L; // 左右差分計算
Diff[1] = Line_Diff; // 現在差分値を保存
P = Diff[1] * KP; // 比例制御値
I = I + ((Diff[1] + Diff[0]) / 2) * DELTA_T * KI; // 積分制御値
D = (Diff[1] - Diff[0]) / DELTA_T * KD; // 微分制御値
Power = P + I + D; // PID制御値計算をモータ制御に渡す
Diff[0] = Diff[1]; // 計算値をdiff[0]に保存
}
比例制御の動作確認例
PID制御のプログラム動作確認を行うには、それぞれの動作を個別に評価する事が大切です。
void Line_PID()
{
Line_Diff = LineVal_R - LineVal_L; // 左右差分計算
Diff[1] = Line_Diff; // 現在差分値を保存
P = Diff[1] * KP; // 比例制御値
I = I + ((Diff[1] + Diff[0]) / 2) * DELTA_T * KI; // 積分制御値
D = (Diff[1] - Diff[0]) / DELTA_T * KD; // 微分制御値
//Power = P + I + D; // PID制御値計算をモータ制御に渡す
Diff[0] = Diff[1]; // 計算値をdiff[0]に保存
Serial.println(P); // シリアルポートにP値を出力
}
パソコンにデータを取り込んで表計算ソフトでグラフ化します。
ロボットが滑らかに直線ラインを走行して、上図の様に"P値"が0付近に留まっていればOKです。
積分制御
目標値付近の制御が苦手な比例制御を助ける働きを積分制御が担当します。
積分制御値は前回値と現在値の描く台形の面積を蓄積して用います。
" 制御値 = 制御値 +((前回値と目標値の差+現在値と目標値の差)÷2 )× 制御周期 × 積分ゲイン "
グラフを90度回転して見て頂くと制御値の式は(上底+下底)÷2×高さに積分ゲインを掛け算しただけの簡単なものだと分かります。
上図の様にモータが回転を始めない10%の所で止まったとしても、時間と共に面積は大きくなっていきます。
次回の制御では以下の様に前回計算した面積に今回計算した面積が加算されます。
これにより僅かな差に対しても目標値へ近づける制御が行えます。
積分制御の動作確認例
void Line_PID()
{
Line_Diff = LineVal_R - LineVal_L; // 左右差分計算
Diff[1] = Line_Diff; // 現在差分値を保存
P = Diff[1] * KP; // 比例制御値
I = I + ((Diff[1] + Diff[0]) / 2) * DELTA_T * KI; // 積分制御値
D = (Diff[1] - Diff[0]) / DELTA_T * KD; // 微分制御値
//Power = P + I + D; // PID制御値計算をモータ制御に渡す
Diff[0] = Diff[1]; // 計算値をdiff[0]に保存
Serial.print(P); // シリアルポートに出力
Serial.print("\t");
Serial.println(I);
}
ロボットをラインの中心より僅かにズレた位置に置いて、プログラムを動かします。
徐々に"I値"が増えていれば第一段階クリアです。
次にP値が負側になる様にズラします。
上図の様に徐々に"I値"が減っていればOKです。
積分制御のみでロボットを走らせると以下の様になります。
先ず蛇行します。
この蛇行が次第に大きくなってラインを見失います。
かなり控えめでないと直線走行時でも蛇行を始めるリスクがあると心得て下さい。
微分制御
比例制御ではロボットが蛇行することなく滑らかに直線ラインを走行する事と、急カーブを飛び出さずに曲がる事はトレードオフの関係にあります。
滑らかな直線走行を保ちつつ、急カーブの時に大きな値を出して比例制御を助ける役割をするのが微分制御です。
微分制御の動作確認例
void Line_PID()
{
Line_Diff = LineVal_R - LineVal_L; // 左右差分計算
Diff[1] = Line_Diff; // 現在差分値を保存
P = Diff[1] * KP; // 比例制御値
I = I + ((Diff[1] + Diff[0]) / 2) * DELTA_T * KI; // 積分制御値
D = (Diff[1] - Diff[0]) / DELTA_T * KD; // 微分制御値
//Power = P + I + D; // PID制御値計算をモータ制御に渡す
Diff[0] = Diff[1]; // 計算値をdiff[0]に保存
Serial.print(P); // シリアルポートに出力
Serial.print("\t");
Serial.println(D);
}
"D値"は0付近をノイズの様に行ったり来たりします。
次にゆっくりロボットを正側にズラします。
このズラしている間"D値"は正側に大きな値を示します。
停止した後は、また0付近をノイズの様に行ったり来たりします。
更にゆっくりロボットを負側にズラします。
今度はズラしている間"D値"は負側に大きな値を示します。
ロボカップジュニアレスキューラインは他の多くのライントレース競技とは異なり、直角やギャップ、障害物、マーカーで線の太さが変わって見えるなどが含まれるのでライントレースの制御には特異な部分があります。
車高(ロボットの底面から床面までの距離)が高くハイグリップなタイヤでの旋回動作はビビりながらになりがちで、この”ビビり”が"D値"のバタつきを発生します。
負側にズラした所を拡大したのが下図ですが、P値のガタつきに呼応して"D値"のバタつきが発生しています。
PD制御としては変化方向の値を"D値"が後押しているのが分かります。
静止状態では差分値の変化がないので、微分制御のみでロボットを走らせようとしてもモータが動くような値になることはなく、全く動きません。
まとめ
P制御が主体ですので、P制御ゲインを適切に決めることが重要です。
I制御、D制御はP制御の補助なのでゲインの高すぎは禁物です。
レスキューラインでは分岐にて必ずマーカーの確認が必要なので、交差点分岐の処理はPID制御とは別に用意する事をお勧めします。