Phidgets は Phidgets Inc. から発売されている、IO拡張ボードとセンサ群製品です。 日本では、ぷらっとほーむなどで購入することができます。
PCにUSB接続の拡張IOボードを接続し、様々なセンサ、アクチュエータユニットを追加して、プログラム等から計測・制御することができるキットです。
PCと接続する場合は、Interface Kitが必要ですが、PiRT-Unitでのみ使用する場合は、Sensor Kitのみでよいでしょう。
PIRT-UnitのADピンは、Phidgetのセンサが接続可能なピンアサインとなっており、Phidgetデバイスを接続することで、容易に拡張することができるようになっています。
ここでは、Phidget の Ministick sensorを利用して、移動ロボットを制御してみます。 Ministick sensor は Phidget Sensor Kit #2 に含まれています。
Ministick sensorの出力ピンとPiRT-Unitを以下のように接続します。
X軸方向 (横向き) がADのCH0に、Y軸方向 (縦向き) がADのCH1に対応します。
このデータを読むプログラムをPythonで書いてみます。
#!/usr/bin/env python # -*- coding: euc-jp -*- import sys import time import spidev class ADC: def __init__(self): self.spi = spidev.SpiDev() self.spi.open(0, 0) def get_value(self, channel): sned_ch = [0x00,0x08,0x10,0x18] if ((channel > 3) or (channel < 0)): return -1 r = self.spi.xfer2([sned_ch[channel],0,0,0]) ret = ((r[2] << 6 ) & 0x300) | ((r[2] << 6) & 0xc0) | ((r[3] >> 2) & 0x3f) return ret def get_voltage(self, channel): ret = self.get_value(channel) * 5.0 / 1024 return ret def main(): adc = ADC() while 1: adc1 = adc.get_value(0) msg1 = "%1.5fV(%04x)" % ((float(adc1)*5/1024),adc1) print msg1, adc1 = adc.get_value(1) msg1 = "%1.5fV(%04x)" % ((float(adc1)*5/1024),adc1) print msg1, adc2 = adc.get_value(2) msg2 = "%1.5fV(%04x)" % ((float(adc2)*5/1024),adc2) print msg2, adc3 = adc.get_value(3) msg3 = "%1.5fV(%04x)" % ((float(adc3)*5/1024),adc3) print msg3, sys.stdout.write("\n") time.sleep(0.5) if __name__ == '__main__': main()
このようなプログラムを作成します。TeraTermなどでRaspberry Piにログインして、サンプルプログラムを作成、テストします。
Linux raspbian-armhf 3.2.27+ #307 PREEMPT Mon Nov 26 23:22:29 GMT 2012 armv6l The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Tue May 14 07:07:04 2013 from dhcpe2078.a02.aist.go.jp pi@raspbian-armhf ~ $ vi adc_test.py pi@raspbian-armhf ~ $ chmod 755 adc_test.py pi@raspbian-armhf ~ $ ./adc_test.py
サンプルプログラムを実行すると以下のような画面になります。ジョイスティックを倒してみると、Ch0,Ch1のデータが変化します。
それぞれ、X軸は左に倒すと、Y軸は上に倒すと電圧値が上がり、逆に倒すと電圧値が下がることがわかります。 ministick sensorの座標の関係を図に表わすと以下のようになります。
これは、一般的な座標系とはX軸が逆向きになっていますので、センサ値を処理する際には注意が必要です。
また、中心位置では、それぞれ5.0Vの半分、2.5V程度を指していることもわかると思います。 ただし、この値はぴったり2.5Vではなく、状況によっても変化するので、使用する前にキャリブレーションが必要なこともわかります。
ministick sensorを利用したジョイスティックコンポーネントを作成します。 仕様としては、以下のようにします。
基本プロファイル | |
コンポーネント名 | Ministick |
モジュール概要 | Phidget ministick sensor component |
バージョン | 1.0.0 |
ベンダ名 | AIST |
モジュールカテゴリ | Input Device |
アクティビティ | |
onInitialize, onFinalize, onActivated, onDeactivated, onExecute | |
データポート | |
[out] pos | |
概要 | ジョイスティックのX-Y位置データ |
データ型 | TimedFloatSeq |
詳細 | data[0]: x位置, data[1]: y位置 |
単位 | 無し |
[out] vel | |
概要 | 移動ロボットの速度ベクトル |
データ型 | TimedVelocity2D |
詳細 | vx: 並進速度, vy: 0.0, va: 角速度 |
単位 | vx [m/s], va [rad/s] |
[out] wheel_vel | |
概要 | 車輪速度 |
データ型 | TimedFloatSeq |
詳細 | data[0]: 左車輪角速度, data[1]: 右車輪角速度 |
単位 | [rad/s] |
コンフィギュレーション | |
scaling | |
概要 | スケーリングファクタ |
データ型 | double |
GUIコントロール | slider.0.1 |
制約条件 | 0.0<=x<=10.0 |
tread | |
概要 | 移動ロボットのトレッド幅 |
データ型 | double |
GUIコントロール | slider.0.01 |
制約条件 | 0.0<=x<=1.0 |
print_xy | |
概要 | XYデータプリントのデバッグフラグ |
データ型 | string |
GUIコントロール | radio |
制約条件 | (YES,NO) |
print_vel | |
概要 | velデータのデバッグプリントフラグ |
データ型 | string |
GUIコントロール | radio |
制約条件 | (YES,NO) |
print_wvel | |
概要 | wheel_velデータのデバッグプリントフラグ |
データ型 | string |
GUIコントロール | radio |
制約条件 | (YES,NO) |
この仕様に従い、RTCBuilderでPythonのテンプレートコードを生成します。
コンポーネントにADCの読み込みなどの機能を追加していきます。
まず、コンポーネントのコンストラクタでSPIオブジェクトを生成し初期化します。
他の import 文の近くに、spidevをimportする一文を追加します。計算などで使用するのでmathモジュールもインポートします。
# Import RTM module import RTC import OpenRTM_aist import math import spidev
さらに、コンストラクタで、必要な変数を初期化子、SPIオブジェクトを生成します。
class Ministick(OpenRTM_aist.DataFlowComponentBase): def __init__(self, manager): OpenRTM_aist.DataFlowComponentBase.__init__(self, manager) self._scaling = [1.0] self._tread = [0.2] self._print_xy = ["NO"] self._print_vel = ["NO"] self._print_wvel = ["NO"] self.x = 0.0 self.y = 0.0 self.spi = spidev.SpiDev() self.spi.open(0, 0)
AD変換器からデータを読む関数を Ministickクラスに追加します。 init() 関数の次あたりに、以下の関数を追記します。
def get_adc(self, channel): sned_ch = [0x00,0x08,0x10,0x18] if ((channel > 3) or (channel < 0)): return -1 r = self.spi.xfer2([sned_ch[channel],0,0,0]) ret = ((r[2] << 6 ) & 0x300) | ((r[2] << 6) & 0xc0) | ((r[3] >> 2) & 0x3f) return ret
X-Yの位置から車輪の速度へ変換する関数をMinistickクラスに追加します。
dev xy_to_wvel(self, x, y): th = math.atan2(y, x) v = math.hypot(x, y) vl = v * math.cos(th - (math.pi/4.0)) vr = v * math.sin(th - (math.pi/4.0)) return (vl, vr)
車輪速度から速度ベクトルへ変換する関数をMinistickクラスに追加します。
def wvel_to_vel2d(self, vl, vr): v = (vr + vl) / 2.0 if v < 0.0: w = - (vr - vl) / self._tread[0] else: w = (vr - vl) / self._tread[0] return RTC.Velocity2D(v, 0.0, w)
コンポーネント初期化時に、ジョイスティックのニュートラル位置をキャリブレーションします。 AD変換器からデータを100回程度読み込み平均し、オフセットデータとして保存します。
def onInitialize(self): : 中略 self.x_offset_v = 0.0 self.y_offset_v = 0.0 for i in range(1, 100): self.x_offset_v += self.get_adc(0) self.y_offset_v += self.get_adc(1) self.x_offset_v = self.x_offset_v / 100.0 self.y_offset_v = self.y_offset_v / 100.0 return RTC.RTC_OK
最後に、onExecute関数を実装します。
def onExecute(self, ec_id): self.x = - (self.get_adc(0) - self.x_offset_v) * self._scaling[0] / 1000.0 self.y = (self.get_adc(1) - self.y_offset_v) * self._scaling[0] / 1000.0 if self._print_xy[0] != "NO": print "(x, y) = ", self.x, self.y self._d_pos.data = [self.x, self.y] self._d_wvel.data = self.xy_to_wvel(self.x, self.y) if self._print_wvel[0] != "NO": print "(vl, vr) = ", self._d_wvel.data[0], self._d_wvel.data[1] self._d_vel.data = self.wvel_to_vel2d(self._d_wvel.data[0], self._d_wvel.data[1]) if self._print_vel[0] != "NO": print "(vx, va) = ", self._d_vel.data.vx, self._d_vel.data.va self._posOut.write() self._velOut.write() self._wvelOut.write() return RTC.RTC_OK
Ministickコンポーネントと、いろいろなものを接続してテストしてみます。
TkMobileRobotCanvasと接続してみます。
Windows上のインストールされているOpenRTM-aist-Pythonのサンプルから TkMobileRobotCanvasを起動します。 同時に、ネームサービスとRTSystemEditorも起動してください。
Createボタンを押し、移動ロボットを一つ生成します。 ネームサーバにコンポーネントが一つ現れるので、SystemEditor上にドラッグアンドドロップします。
次に、Raspberry Pi上でネームサーバとMinistickコンポーネントを起動します。
$ rtm-naming $ ./Ministick.py
RTSystemEditorから、RaspberryPiのネームサーバに接続すると、Ministickコンポーネントが見えますので、SystemEditor上にドラッグアンドドロップします。
Ministick の wvel データポートから先ほど生成した移動ロボットコンポーネントへポートを接続します。
アクティベートすると、両方のコンポーネントが緑色になり操作可能になります。
前述のKobukiの制御、の節に従ってKobukiコンポーネントをRaspberryPi上で起動します。
Ministickコンポーネントを別のRaspberry Pi上で起動します。
それぞれのコンポーネントはローカルのネームサーバ(デフォルト)に参照を登録させて、別のPCからこれら2つのネームサーバにRTSystemEditorで接続します。 Name Service Viewに2つのコンポーネントが見えるはずですので、これら (Ministick の vel と KobukiAIST の targetVelocity) を接続します。(下図参照)
RTSystemEditorのメニューバーの緑色の再生ボタン(全活性化)を押すと、システムが動き出しMinistickでKobukiを操作できます。
Ministickがニュートラル状態でも、TkMobileRobotのロボットやKobukiが少しずつ動く場合があります。これは、ニュートラル状態のキャリブレーションが十分ではないためです。 Ministickコンポーネントを工夫して、使いやすいコンポーネントにしてみましょう。
例えば、キャリブレーションをonActivatedで行うようにすれば、一旦Deactivateして再度Activateすれば零点をリセットできます。 また、onDeactivatedの時には速度0を出力するようにすれば、JyoistickのDeactivateによりロボットが安全に停止します。 あるいは、零点付近に不感帯を設けることで、多少キャリブレーションがずれても、ニュートラル状態で必ず0が出力されるようにできます。
以下のようなパーミッション関係のエラーが出ることがあります。 上述のudevの設定が正しく行われていない可能性がありますので、見直してください。
pi@raspbian-armhf ~ $ ./adc_text.py Traceback (most recent call last): File "./adc_text.py", line 47, in <module> main() File "./adc_text.py", line 25, in main adc = ADC() File "./adc_text.py", line 10, in __init__ self.spi.open(0, 0) IOError: [Errno 13] Permission denied
また、sudo を利用しても実行可能です。