![]() |
![]() |
この文章を書いた時点でのVRM4のバージョンは「4.0.1.1」です。これ以前のバージョンでは間違いなく動作しません。また、今後のバージョンアップによって仕様が変更される可能性もあります。 |
制御理論を習っていない人にとってはチンプンカンプンだと思いますが、しばしお付き合いを。VRMで制御理論を学べて、その実験も出来るという画期的なお話です。
スクリプトは単純で短いです。上の「スクリプトリスト」をクリックすると別窓で開きますので、随時参照してください。ちなみに、センサの類いは一切ありません。
レイアウトも単純なため、とくに公開しません。直線レールを3本、ひたすら長々と設置してあるだけです。この3本の線路にそれぞれ列車が走ります。真ん中は人間が操作する列車で気の向くまま自由に走らせます。そして、両側の2列車がこれに追従するように走ります。写真は加速中の模様で、真ん中の列車を両側の列車が一所懸命追い掛けているというシーンです。
「リアルタイム制御編」でちらっと『サンプリング制御も可能』と書いたことがあります。VRM4スクリプトには、SetEventTimerという命令があり、一定時間ごとに特定のメソッドを実行させることが可能です。これが、サンプリングタイムによる制御に他なりません。正確には離散系(ディスクリート)制御で、コンピュータが得意とする方式ですね。レイアウトスクリプトの15行目で、SetEventTimerを実行して、ここでは0.5秒ごとに「ctrl」メソッドを起動するように設定してあります。
※制御理論ははっきり言って、直感で理解できない人には一生理解できないかもしれません。でも、一度分かってしまえば、非常に明確で体系的に理解しやすい理論です。
まずは、単純に考えてみます。列車を追従運転させるわけですから、人が運転する列車(TRAIN00)を目標値として、これの真似をすればよろしい。即ち、TRAIN00の現在の速度をgetして、追従させる列車(TRAIN02)にそれを与えてやればいいのです。
ここでリストを見てください。各列車には「getSpeed」と「setSpeed」というメソッドがプログラミングされてあります。「getSpeed」は現在の速度(実際は電圧)を得るメソッドで、「setSpeed」は所定の電圧を与えるメソッドです。上記の制御を行うには、レイアウトスクリプトの「ctrl」メソッドで以下のようにします。
18行目:TRAIN00(目標値)の電圧をゲット
21行目:それをそのままTRAIN02の入力(input)へ代入
22行目:TRAIN02の「setSpeed」をcall
これで実際列車を走らせてみると、最初の写真のようになります。TRAIN02はTRAIN00を一所懸命追い掛けますが、いつも一歩先んじられてしまいます。これは、なぜでしょうか? 上記の制御は0.5秒周期で実行されていますので、最大0.5秒の遅れが生じます。端的に言えば、TRAIN00が発車してから0.5秒後にTRAIN02が慌てて発車するといった感じです。
注:サンプリング周期はコントローラの最大能力まで上げるのが普通ですが、ここでは実験の効果を分かりやすくする為、わざと0.5秒という遅い周期で実行しています。また、SetVoltageではなくSetTimerVoltageを用いているのも遅れの原因となっています。SetVoltageでは応答時間0となり、系として堅すぎるし、現実的にもそんなシステムはあり得ないので、SetTimerVoltageを用いています。
では、追従性をよくする為、フィードバック制御を行ってみましょう。制御対象(PLANT)をTRAIN01として、ref.は目標値でTRAIN00の電圧です。フィードフォワードの場合は、この目標値をそのままTRAIN01の入力(input)としています。フィードバックの場合は、TRAIN01の速度=電圧(output)を検出して、これを目標値(ref.)と比較し、その差に定数(gain)を掛けたものをTRAIN01の入力(input)とします。即ち、目標値とのずれが大きいほど、大きな力で制御対象をコントロールするということです。さて、これをスクリプトで実現するのは実に簡単です:
13行目:フィードバックゲインを変数gainにセットしています。実数で扱いますから、setfを用います。
19行目:TRAIN00に加えて、TRAIN01の電圧もゲットします。
24行目:取り敢えず、TRAIN00の電圧(Speed)=目標値をinput1に代入します(プログラムではよくやる手法)。
25行目:それとTRAIN01の電圧(Speed01)の差を求めます。
26行目:その結果に定数(gain)を掛算します。
27行目:その結果にフィードフォワード項目(input2)を足し算します。
29行目:計算結果(input1)をTRAIN01の入力とするため、TRAIN01のsetSpeedをcallします。
単純な四則演算をやっているだけですね。これをビューアーで実行させたのが最初の写真です。フィードフォワードに比べて随分と応答性がよくなっています。
フィードバック制御で問題になってくるのがゲインの調整です。これには試行錯誤で最適な値を求めないといけません。この例では、0.01がよさそうでしたのでスクリプトの13行目でそれをセットしています。これを例えば、0.001にすると追従性が悪くなり、TRAIN01はTRAIN00より随分と遅れて走るようになります(それでもフィードフォワードよりはまし)。0.05にするとゲインを掛けすぎで、この場合は振動的になります。実際、TRAIN01はTRAIN00の前に出たり後ろに下がったり振動するようになります。この辺は、ご自分で13行目の値をいろいろ変えてその目で確かめてください0.01は適当に決めた値ですので、もっといい結果になる値があると思います。わざと外れた値を代入してみて、その結果どうなるかを実験してみるのもいいでしょう。正直言ってこれほど如実に変化があるとは思いませんでした。VRMがフィードバック制御の実験材料になるとは、作った本人も驚いているくらいです。
制御が得意な人へ応用編:本制御では積分項は用いていません。inputとoutputが同じ速度(電圧)で遅れ要素がないからです。これが、位置と速度となると一次遅れ系になりますので、オフセットをなくすために積分項が必要になってきます。ただし、前述したように、SetTimerVoltageを用いているため、これが一次遅れの要因になり、実際、TRAIN00が停止した時TRAIN01も停止しますが、その位置がずれます。この位置のずれをなくす為には積分項の追加が必要です。