USBカメラ用パン・チルト台の製作

1.概要

R-MSMは、pwmの出力機能を有しています(PWMポートを利用する)。また、コマンドとして、小型で安価なサーボモータSG-90を動作させることのできるサーボモータ制御コマンドを備えています(サーボモータ制御コマンド)。
このサーボモータ制御コマンドを使って、市販のUSBカメラパン・チルト台を駆動します。
ここでは、SG90サーボ用ミニ2軸 オリジナル FPV 特殊なナイロン PTZNOサーボ) カメラマウント A838 を使用しました。このキットにはサーボモータSG-90が付属していないので注意して下さい。別途購入が必要です。

2.組立て

組立方法は、こちらのHP(ラズパイカメラ用2軸マウント(A838)の組み立て方法)を参照して組み立てください。

3. R-MSMとの接続

下の図のように、2個のSG-90をR-MSMと接続します。以下の説明では、J11のPWM0にパン(左右)側のSG90を、PWM1にチルト(上限)側のSG90を接続した状態での説明を行います。

4.USBカメラのパン・チルト動作仕様

パン・チルト台に、USBカメラを据付け、静止画を一定時間間隔で撮影します。
撮影したら、以下の様にカメラを動かします(番号に従って、①~⑫を繰返す)。

これにより、ある範囲を監視することが可能になります。

5. usbcameraノードによる画像の取得

USBカメラを使った静止画像の取得には、usbcameraノードを使います。
usbcameraノードを使った静止画像の取得は、こちら(PDHにつないだUSBカメラ画像(静止画)の表示)を参考にしてください。

5-1.基本構成

基本構成は、injectノードとusbcameraノードとviewerノードの3つです。gateノードは、ON/OFFのスイッチに使っています。
viewerノードは、“node-red-contrib-image-tools”に含まれています。“node-red-contib-image-output”のimageノードでも代用できます。viewerノードは、msg.payloadにイメージ画像のパス付のファイル名を載せて送るとそれを表示してくれるところが便利です。

injectノードで定期的にusbccameraノードにアクセスして、画像を取得します。usbcameraノードで“File Mode”=Buffer Modeに設定します。次段のカメラデータが送られ、viewerノードで表示されます。

6. パン・チルト台の制御コマンド

パン・チルト台は、SG-90のサーボコマンドで制御します。具体的には、servo0_deg=90とservo1_deg=90を使います。
前に述べた動作仕様を実現するために、以下のコマンドを順次発行します。

7. パン・チルト台の制御ならびに静止画像取得用Node-REDフローの製作

7-1. 全体説明

今回作成したパン・チルト台の制御ならびに静止画像取得用Node-REDフローを以下に示します。
3つの部分に分かれます。①静止画像取得部は、上述の静止画像取得フローの基本構成のままです。②パン・チルト台制御部は、静止画像取得が終わるたびに、パン・チルトの角度を上述のように変化させる部分です。PDHとR-MSMをUSBで接続するか、Bluetoothで接続するかで、シリアルポートの相手先の記述が変わります。③パン・チルト台動作確認部は、単に動作確認のために、injectノードでコマンドを投げる部分です。以下、②パン・チルト台制御部のフローに関して説明します。

7-2. パン・チルト台制御部の説明

パン・チルト台制御部は、usbcameraノードの撮影が終了したら、動作を開始します。初段のa)制御部でパンとチルドの角度を算出します。次段のb)命令生成部で、入力された角度を、servo0_deg=45、servo1_deg=110というコマンドに変換します。2つのコマンドがぶつからないように、delayコマンドで2s差を持たせて出力しています。最後のc)出力部でR-MSMに送ります。ここでは、PDHとUSBをUSBケーブルで接続しているので、シリアルポートを/dev/ttyUSB0としていますが、bluetoothの場合には、/dev/rfcomm0のようになります。

a) 制御部は、functionノードです。プログラムの動作に関して説明します。

変数の動作は、以下の表のように動きます。
前述の表から、servo0_degは、45、90、135の値のみを取ります。また、servo1_degは、70、90、110の値のみを取ります。
timを0,1,2で繰返します。それに合わせて,panとtiltを増減させます。
増減は、pan = pan + d_p, tilt = tilt + d_t で行います。

d_pは、tim=2の時に極性を反転させるだけで、d_pの加算はしません。
d_tは、tim=2で、tilt=0かtilt=2のときに極性を反転させます。d_tの加算は、tim=2で極性反転後に毎回行います。

servo0 = 45 + pan * 45;
servo1 = 70 + tilt * 70;
でpanとtiltを角度に変換します。

結果は、JSON形式で出力します。
msg.payload = { “tim”:tim, “servo0”: servo0, “servo1”: servo1, “pan”: pan, “tilt”: tilt };

functionノード:servo_controlの中身のプログラム

8. 動作時の様子

Appendix Node-REDのフロー

[{"id":"9002a659479a2353","type":"serial out","z":"1df42f23bf1f7d27","name":"","serial":"dd56e3c72b075773","x":830,"y":320,"wires":[]},{"id":"5736383f5f446581","type":"template","z":"1df42f23bf1f7d27","name":"servo0","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"servo0_deg={{payload.servo0}}","output":"str","x":670,"y":260,"wires":[["fc155a33eb01c720","9002a659479a2353"]]},{"id":"b5626c4f675ae5de","type":"template","z":"1df42f23bf1f7d27","name":"servo1","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"servo1_deg={{payload.servo1}}","output":"str","x":670,"y":300,"wires":[["fc155a33eb01c720","9002a659479a2353"]]},{"id":"4518a72bf4a30cab","type":"link in","z":"1df42f23bf1f7d27","name":"link in 11","links":["86713db1450378ee"],"x":705,"y":340,"wires":[["9002a659479a2353","fc155a33eb01c720"]]},{"id":"5ef68932696c8f21","type":"function","z":"1df42f23bf1f7d27","name":"servo control","func":"var pan = context.get('pan') || 0;\nvar tilt = context.get('tilt') || 0;\nvar d_p = context.get('d_p') || 1;\nvar d_t = context.get('d_t') || -1;\nvar tim = context.get('tim') || 0;\n\nvar servo0 = 45 + pan * 45;\nvar servo1 = 70 + tilt * 20;\n\nmsg.payload = { \"tim\":tim, \"servo0\": servo0, \"servo1\": servo1, \"pan\": pan, \"tilt\": tilt };\n\nif (tim != 2){\n    pan += d_p;\n} else {\n    d_p *= -1;\n}\n\nif (tim == 2) {\n    if ((tilt == 0) || (tilt == 2)){\n        d_t *= -1;\n    }\n    tilt += d_t;\n    tim = 0;\n} else {\n    tim += 1;\n}\n\ncontext.set('tim', tim);\ncontext.set('pan', pan);\ncontext.set('tilt', tilt);\ncontext.set('d_p', d_p);\ncontext.set('d_t', d_t);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":410,"y":280,"wires":[["5736383f5f446581","360616f5e46fe1f9"]]},{"id":"fc155a33eb01c720","type":"debug","z":"1df42f23bf1f7d27","name":"debug 50","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":820,"y":280,"wires":[]},{"id":"360616f5e46fe1f9","type":"delay","z":"1df42f23bf1f7d27","name":"2s","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":550,"y":300,"wires":[["b5626c4f675ae5de"]]},{"id":"19a64a1fe05cec77","type":"usbcamera","z":"1df42f23bf1f7d27","filemode":"1","filename":"usbcam.jpg","filedefpath":"1","filepath":"/home/pi/Pictures/opencv","fileformat":"jpeg","resolution":"2","name":"usbcam.jpg","x":410,"y":60,"wires":[["1d3d57271a90e8d0","5ef68932696c8f21"]]},{"id":"63ed50b1e269dec9","type":"gate","z":"1df42f23bf1f7d27","name":"","controlTopic":"control","defaultState":"closed","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","defaultCmd":"default","statusCmd":"status","persist":false,"storeName":"memory","x":250,"y":60,"wires":[["19a64a1fe05cec77"]]},{"id":"1d3d57271a90e8d0","type":"image viewer","z":"1df42f23bf1f7d27","name":"","width":160,"data":"payload","dataType":"msg","active":true,"x":570,"y":60,"wires":[[]]},{"id":"2dfa2798a4544398","type":"inject","z":"1df42f23bf1f7d27","name":"10秒毎","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"10","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":40,"wires":[["63ed50b1e269dec9"]]},{"id":"d8cffbcc5155663d","type":"inject","z":"1df42f23bf1f7d27","name":"toggle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"control","payload":"toggle","payloadType":"str","x":130,"y":80,"wires":[["63ed50b1e269dec9"]]},{"id":"413b989c7ca23eb9","type":"comment","z":"1df42f23bf1f7d27","name":"for operation comfirmation","info":"","x":150,"y":240,"wires":[]},{"id":"7f5efb5f9ac35374","type":"inject","z":"1df42f23bf1f7d27","name":"servo0_deg=45","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo0_deg=45","payloadType":"str","x":160,"y":280,"wires":[["86713db1450378ee"]]},{"id":"64147f523a2d30ac","type":"inject","z":"1df42f23bf1f7d27","name":"servo0_deg=90","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo0_deg=90","payloadType":"str","x":160,"y":320,"wires":[["86713db1450378ee"]]},{"id":"3721c5ca85511574","type":"inject","z":"1df42f23bf1f7d27","name":"servo0_deg=135","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo0_deg=135","payloadType":"str","x":160,"y":360,"wires":[["86713db1450378ee"]]},{"id":"9c2c3c0f9fe50d3a","type":"inject","z":"1df42f23bf1f7d27","name":"servo1_deg=70","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo1_deg=70","payloadType":"str","x":160,"y":400,"wires":[["86713db1450378ee"]]},{"id":"8dab95e0926b9459","type":"inject","z":"1df42f23bf1f7d27","name":"servo1_deg=90","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo1_deg=90","payloadType":"str","x":160,"y":440,"wires":[["86713db1450378ee"]]},{"id":"0e076dd9a5edaa9b","type":"inject","z":"1df42f23bf1f7d27","name":"servo1_deg=110","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"servo1_deg=110","payloadType":"str","x":160,"y":480,"wires":[["86713db1450378ee"]]},{"id":"86713db1450378ee","type":"link out","z":"1df42f23bf1f7d27","name":"for debug","mode":"link","links":["4518a72bf4a30cab"],"x":305,"y":380,"wires":[]},{"id":"dd56e3c72b075773","type":"serial-port","serialport":"/dev/ttyUSB0","serialbaud":"1000000","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"\\n","bin":"false","out":"char","addchar":"\\n","responsetimeout":"10000"}]