Zumoが道をたどるようにコードを配備する
はじめに
PololuのZumoロボットは2つのモータで動く無限軌道式ロボットで,経路追従に使えるIRセンサアレイを搭載している.
このプロジェクトでは,経路追従制御器を開発し,それをセンサとモータとのインターフェースのためのPololuが提供するライブラリと統合し,マイクロコントローラに配備して,ロボットに経路追従させる.
ArduinoとZumoのライブラリ
Pololuが提供するライブラリを使用する.また,センサの校正を行うためにライブラリに含まれているArduinoスケッチも使用する.
まだインストールしていない場合はArduino IDEと,ライブラリQTRSensors,ZumoReflectanceSensorArray,Pushbutton,ZumoMotorsをインストールする.ライブラリのインストール方法はArduinoのサイトで調べられる.
これがMacにおけるArduinoのSketchBookディレクトリの場所である.
sketchBookDir = FileNameJoin[{$HomeDirectory, "Documents", "Arduino", "libraries"}];うまくいっていれば,ライブラリが4つあるはずである.
libs = Table[FileNameJoin[{sketchBookDir, d}], {d, {"QTRSensors", "ZumoReflectanceSensorArray", "Pushbutton", "ZumoMotors"}}];DirectoryQ /@ libsモデル
システムへの入力は,左と右のモータの速度
および
である.これはロボットの位置(x, y)と向き θ に影響を与える.
ここでは主にロボットの向きが必要であるので,モータの向き θ を速度
および
に関連付ける簡単な運動学的モデルを使用する.
eq = Derivative[1][θ][t] == (r (Subscript[Φ, r][t] - Subscript[Φ, l][t])/2 yd);左右のモータの速度は系への物理的な入力であるが,制御設計の目的で,入力を基底速度Φおよび差動速度Δと考える.
controlMixerRules = {Subscript[Φ, l][t] -> Φ[t] - Δ[t], Subscript[Φ, r][t] -> Φ[t] + Δ[t]};zumoModel = eq /. controlMixerRules新しい入力セットで,差分速度Δだけがロボットの向きに影響を及ぼしていることが明確に分かる.Δを入力,出力を θ としてStateSpaceModelが構築できる.
ssm = StateSpaceModel[zumoModel, θ[t], Δ[t], θ[t], t] /. {r -> (1.8/100), yd -> (4/100)}このモデルはトラック上のロボットの向きを制御する制御器の設計に使用する.基底速度Φはロボットの平行移動の動作または速度を制御するもので,別に設計する.そのため,この新しい入力セットを選んだことで,トラックの問題が2つの別々の問題に切り離されたことが分かる.
差動入力制御器の設計
角度 θ はロボットがトラックからどのくらい外れたのかを測定する.ロバストな制御器を作成するために,ここでは θ とその積分
の両方を使う.状態
のモデルを使い,θ は制御器設計に使う.
{a, b, c, d} = Normal[ssm];ssmI = StateSpaceModel[{ArrayFlatten[{{0, -1}, {0, a}}], ArrayFlatten[{{0}, {b}}], {{1, 0}}}]モデルは可制御である.
ControllableModelQ[ssmI]全状態フィードバックを使う制御法則を設計する.
𝓀 = StateFeedbackGains[ssmI, {-14 , -16 }]制御器は全状態フィードバックを使うので,
と θ の両方の値が必要であり,センサは θ だけを測定する.
モデルは可観測でもある.
ObservableModelQ[ssmI]したがって,両方の状態を推定するオブザーバを加えることができる.
ℓ = EstimatorGains[ssmI, {-700, -800}]制御法則と推定器で制御器のコア部分を構成する.
ℯ𝓇 = EstimatorRegulator[ssmI, {ℓ, 𝓀}]閉ループ系.
使用した積分器はモデルの一部ではないが,制御器設計にのみ使用した.そのため積分器は制御器の一部として実装しなければならない.
𝒾𝓃𝓉 = TransferFunctionModel[(1/s), s]ループゲイン.
cascade[s__] := Fold[SystemsModelSeriesConnect, First[{s}], Rest[{s}]]lg = cascade[𝒾𝓃𝓉, ℯ𝓇, TransferFunctionModel[{{{-1}}, 1}, 𝓈], ssm]閉ループ系が指定の場所に極を持つことを確認する.
csys = SystemsModelFeedbackConnect[lg]
Eigenvalues[Normal[%][[1]]]15度/秒の差動入力に対するロボットの応答のシミュレーションを行う.
OutputResponse[csys, 15°(UnitStep[t] - UnitStep[t - 0.5]), {t, 0, 2}];
Plot[% / Degree, {t, 0, 2}, PlotRange -> All]制御入力Δの単位はrad/sであるが,ライブラリはモータ速度の単位にrpmを使用する.したがってこの変換も制御器の一部として実装されていなければならない.
𝒸𝓃𝓋 = TransferFunctionModel[QuantityMagnitude[UnitConvert[["rad/s"], ["FullAngle/Minute"]]]]制御器のコア全体.
𝒸 = cascade[TransferFunctionModel[{{{-1}}, 1}, 𝓈], 𝒾𝓃𝓉, ℯ𝓇, TransferFunctionModel[{{{-1}}, 1}, 𝓈], 𝒸𝓃𝓋]最後に,制御器をマイクロコントローラに実装できるように離散化する.
𝒸𝒹 = ToDiscreteTimeModel[𝒸, 0.001]速度制御器の設計
ロボットが急カーブを曲がるときに減速し,カーブがなだらかになったら加速する,アドホック速度制御器を実装する.
arg = {Abs[u] < 1 °, 325, 1° ≤ Abs[u] < 2 °, 300, 2 ° ≤ Abs[u] < 5°, 150, 5° ≤ Abs[u] < 25°, 100, True, 35}//N;
Subscript[𝓈𝓅, 1] = NonlinearStateSpaceModel[{{}, Which@@arg}, {}, u, SamplingPeriod -> 0.001]ParametricPlot[{(u/Degree), Which@@arg}, {u, -30 °, 30°}, AspectRatio -> 1, AxesOrigin -> {0, 0}, PlotRange -> All]このような急激な速度の変化を避けるために,制御器に移動平均フィルタと積分器も加える.
Subscript[𝓈𝓅, 2] = cascade[StateSpaceModel[y[k] == (1.0/3) (u[k - 2] + u[k - 1] + u[k]), y[k], u[k], y[k], k, SamplingPeriod -> 0.001], ToDiscreteTimeModel[TransferFunctionModel[(1/sbar + 1), sbar], 0.001]]センサの校正
校正を行うためには,トラックの直線部分と分度器が必要である.
また,Pololuのスケッチも使用する.スケッチはすでにインストールしてあるZumoReflectanceSensorArrayライブラリにある.
sensorCalibFile = FileNameJoin[{sketchBookDir, "ZumoReflectanceSensorArray", "examples", "SensorCalibration", "SensorCalibration.ino"}];FileExistsQ[sensorCalibFile]スケッチとArduino IDEのシリアルモニタを開き,スケッチをアップロードする.
(*RunProcess[$SystemShell,All,"open "<>sensorCalibFile] *)(* non-Mac?*)スケッチをアップロードしたら,ロボット上のLEDが約10秒間点灯する.この間にロボットをトラック上で数回前後に動かし,センサがトラックと背景の両方を見るようにする.
次に,ロボットをトラックに対してさまざまな方向に向ける.センサの測定値とそれに対応する角度を記録する.
sensorData = IconizedObject[«sensor data»];LinearModeFitを使ってセンサの測定値とロボットの向きの関係をモデル化する.
sensorFit = LinearModelFit[sensorData, r, r]実際の値とフィットした値を比較する.
Show[ListLinePlot[Sort[sensorData], PlotStyle -> Dashed, FrameLabel -> {"測定値", "角度 (rad)"}], Plot[sensorFit[r], {r, 0, 5000}], Frame -> True, ImageSize -> Medium]入出力モデルを作成し,センサの値から向きを取得する.
𝓇𝓈 = NonlinearStateSpaceModel[{{}, sensorFit[r]}, {}, r, SamplingPeriod -> 0.001]制御器の全容
センサの測定値の変換,差動制御器,速度制御器から構成される制御器の全容.
controller = SystemsConnectionsModel[{𝓇𝓈, 𝒸𝒹, Subscript[𝓈𝓅, 1], Subscript[𝓈𝓅, 2]}, {{1, 1}{2, 1}, {1, 1}{3, 1}, {3, 1}{4, 1}}, {{1, 1}}, {{2, 1}, {4, 1}}]制御器の配備
制御器を配備する前に,Zumoライブラリでインターフェース接続する必要がある.
センサとモータのライブラリの他に,押しボタンライブラリも使用する.
FileNameTake /@ libs押しボタンはロボットがコードをダウンロードした直後に動き出すのを防ぐために使用する.これにより,コードをダウンロードした後にUSBケーブルを外し,ロボットをトラックに置く時間ができる.プッシュボタンを押すと,ロボットは動き始める.
外部ライブラリを使ってセンサの測定値を取得する.
l = {"unsigned int sensors[6]", "reflectanceSensors.readLine(sensors)"};
ics = <|"Type" -> "ExternalLibrary", "Loop" -> l|>;外部ライブラリの機能を使って実際のモータ速度も設定する.
l = {"int sp", "motors.setSpeeds(sp-spd, sp+spd)"};ocs = {<|"Type" -> "ExternalLibrary", "Loop" -> {"int spd"}|>, <|"Type" -> "ExternalLibrary", "Loop" -> l|>};インクルードするヘッダファイル.
incls = {"<QTRSensors.h>", "<ZumoReflectanceSensorArray.h>", "<ZumoMotors.h>", "<Pushbutton.h>"};必要なオブジェクトを作成する.
utils = {"ZumoReflectanceSensorArray reflectanceSensors", "ZumoMotors motors", "Pushbutton button(ZUMO_BUTTON)"};初期化コードは,ボタンが押されたらセンサを初期化し,ロボットをトラックで旋回させてトラックと背景がよく見えるように自己校正する.
Needs["SymbolicC`"]calib = {CDeclare["int", "i"], CFor[CAssign["i", 0], COperator[Less, {"i", 80}], COperator[Increment, "i"], {CIf["(i > 10 && i <= 30) || (i > 50 && i <= 70)", "motors.setSpeeds(-200,200)", "motors.setSpeeds(200,-200)"], "reflectanceSensors.calibrate()", "delay(20)"}], "motors.setSpeeds(0,0)"};inits = {"reflectanceSensors.init()", "button.waitForButton()", calib};ToCCodeString[inits]パッケージをロードする.
Needs["MicrocontrollerKit`"]コントローラを埋め込む.
MicrocontrollerEmbedCode[controller, <|"Target" -> "ArduinoUno", "Inputs" -> ics, "Outputs" -> ocs, "IncludeFiles" -> incls, "Utilities" -> utils, "Initializations" -> inits, "Timer" -> None|>, "/dev/cu.usbmodem14101", <|"Libraries" -> libs|>]USBケーブルをロボットから外し,ロボットをトラック上に置いて,押しボタンを押す.
以上,追従制御器を設計する方法と,外部ライブラリのコードを自動生成コードと統合する方法を示した.
発展
- 制御器と推定器の極の位置を変更し,それが制御器の性能にどのように影響するのかを調べる.
- 全次元オブザーバではなく,低次元オブザーバを設計する.
- その他の制御設計方法を試す.
- 速度制御器が改善されたかどうか調べる.
- 制御器が十分にロバストで,センサの校正の誤差を扱うことができるかどうかを調べる.
- トラックの交差も扱える制御器を設計してみる.