原文は
こちらです (訳注: ここで紹介するロボット キットは、日本国内では販売されていません。しかし、似たようなキットが、
マルツ等から販売されています)。
本文献では、BeagleBone Black からロボット アームを制御する方法を学びます (訳注: アーム キット自体は、国内では販売されていませんが、この種の制御方法を学び、他に応用が可能と思います)。これにより、実世界のものを扱う方法と繰り返し作業を行う方法を学びます。
ロボット アームは、多数のサーボ モーターを用いて、アームの各部、手首を回転させ、指を動かします。サーボ モーターを増やすと、可動部分も増え、より柔軟性を増すことができます。しかし、当然、費用も増えますし、制御も複雑になります。
Lynxmodtion AL5D ロボット アームの基本モデルは、5 個のサーボ モーターを使用します。1 つは (全体の) 回転、肩の可動部分、肘、手首、そして物を掴むためのグリップ (親指と他の指の動きのこと) です。
上の写真のロボッ トアームの横にある小さなコンピューターは、シリアル サーボ制御装置 (
SSC-32) です。SSC-32 は、シリアル ポートから制御信号を受け取り、最大 32 個のサーボを制御します。
BeagleBone Black で、
サーボを直接制御する方法 (和訳は
ここです) を学びました。なぜ、SSC-32 を使うのでしょうか。SSC-32 を使うことにより、よりスピーディにサーボを制御できるとともに、サーボ制御をバッチ化できます。たとえば、1 つのコマンドで、肩、肘、手首を特定の位置へ 2 秒以内に移動させることができます。そして、各サーボは、他サーボと同時に指定位置へ移動するのです。SSC-32 を用いることにより、ロボット アームを動かすために、BeagleBone Black を使って、5 つから 6 つのパルス幅変調 (PWM) を出力しなくてもすみます。さらに、SSC-32 は、サーボに必要な 6V 電源を供給します。
ロボット アームの組み立て
ロボット アームはキットになっていて、組み立て説明書は
オンラインで参照できます。組み立て工程の重要ポイントを
ビデオで見ることもできます。説明書に少し違いを見つけたので、疑問点はオンラインで解決しました。組み立てに要した時間は、ほぼ 6 時間でした。ロボット アームを何かに固定したほうが良いでしょう。50 cm 四方の MDF ボードでも構いません。さもないと、私がやってしまったように、アームが固定されず、サーボの力で前に倒れてしまいます。
イメージの左下にある RS-232 ケーブルで、SSC-32 の DB-9 ポートと接続し、制御します。SSC-32 は、写真の下部中央にある静電気防止用の袋に入っています。SSC-32 の RS-232 ポートの隣には、TTL コネクタがあり、これを利用して BeagleBone Black からより細かい制御ができます。
TTL シリアル通信は、低電圧で動作し、RS-232 より単純に制御できます。
肩と肘の間の U 型の 2 つの部品は、サーボの負荷を軽減する Friction 板です。また、サーボ間にいれるスプリングによって、伸ばしたアームを元の位置に戻します。手首は、上下 90 度曲げることができます。手首を軸方向の回転させるための
手首アップグレードもあります。
アームを BeagleBone Black に取り付ける
BeagleBone Black のデジタル ピンは、3.3V で動作します。BeagleBone Black のピンには、
3.3V 以上をかけないでください。SSC-32 は、5V で動作します。BeagleBone Black からデータを送信するときは、SSC-32 が 3.3V を ”high” と認識するため、問題ありません。
SSC-32 から BeagleBone Black へのシリアル送信には注意が必要です。3.3V 以下にするための電圧シフト用ハードウェアが必要です。IC の 74HC4050 は、6 入力で、5V 入力を BeagleBone Black の許容電圧の 3.3V へシフトするとともに、過電圧への対応もできます。
SSC-32 の TTL シリアル ピンは、3 つあり、DB9 コネクタの隣にあり、グラウンド、RX、TX となっています。これらのピンは、ボードの端に近いピンにグラウンドの表示がしてあります。
上の 4050 (訳注: レベル シフター用の IC です) の写真にあるように、3.3V 電源線 (赤) が、4050 のピン 1 へ繋がっています。4050 のピン 8 は、グラウンドで、緑の線が SSC-32 のグラウンドと BeagleBone Black のグラウンド (ピン 1) につながっています。
BeagleBone Black の送信線 (ピン 13) は黄線で、SSC-32 の受信 (Rx) へ直接つなぎます。これらの配線にはブレッドボードを使い、4050 とは直接つなぎません。レベル シフトは、BeagleBone Black の受信 (RX) (11) ピンの近くで行います。SSC-32 の送信ピンから、4050 のレベル シフターのピン 3 へは、橙色の線を使用しました。レベル シフトした信号線は、4050 のピン 2 (電源ピンの近く) から取り、BeagleBone Black の RX (11) ピンへつなぎます。
BeagleBone Black の設定
Bonescript ライブラリは、
シリアル IO をサポートしています。Bonescript API を利用してシリアル ポートと交信すると、必要なデバイス ツリー オーバーレイを自動的に設定してくれます。私の場合、bone-debian-7.5-2014-05-14 を使うと問題があり、Bonescript に含まれるシリアル ポート API を利用できませんでした。
リビジョン C の BeagleBone Black ボードの Debian とリビジョン B の BeagleBone Black の ?ngstr?m 上で動作するフォールバックは、上記の代わりにシリアル ノード モジュールを使用します。ノード モジュールを使用するときの問題は、プログラムを実行したり、UART オーバーレイを設定するファイルを利用したりする前に、UART 用のデバイス ツリー オーバーレイを自分で設定する必要があることです。
BeagleBone Black 上で、UART4 をマニュアルで設定するためのコマンドを以下に示します。
root@beaglebone:/lib/firmware# echo BB-UART4 > /sys/devices/bone_capemgr.*/slots
root@beaglebone:/lib/firmware# sleep 1
root@beaglebone:/lib/firmware# chown debian /dev/ttyO4
root@beaglebone:/lib/firmware# ls -l /dev/ttyO4
crw-rw---T 1 root dialout 248, 4 May 15 02:50 /dev/ttyO4
次のコマンドを使って、シリアル ポート用の nodejs モジュールをインストールします。
root@beaglebone:~# npm install -g serialport
配線が正しく動作しているかを手軽に調べるために、screen コマンドを使って SSC-32 ボードのバージョンを表示させます。Enter キーを押下すると、バージョン情報が VERSION コマンドに上書きされます。あまり、嬉しい動作ではありませんが、双方向の通信が正しく動作していることがわかります。
screen /dev/ttyO4 115200
VER
SSC32-V2.03XE
本文献では、debian-7.5-2014-05-14-2gb.img と、リビジョン C の BeagleBone Black を使用しました。
アームを動かす
組み立て説明書どおりに組み立てると、サーボ 0 はベースの回転、サーボ 1 は肩、サーボ 2 は肘、サーボ 3 は手首、サーボ 4 はグリップ (指と親指) を、それぞれ担当します。
次のコードは、グリップを内側と外側へ一回動かします。UART4 (/dev/ttyO4) のシリアル ポートをオープンします。”var” コマンドを投入し、簡単なチェックをします。数秒後、グリップが動き、また数秒後、逆方向に動きます。グリップを動かすためのコマンドのうち、”#4” は、サーボ 4 を意味します。また、P1650 は、サーボを動かす位置を指定します。各サーボは、180 度の動作範囲を持ち、一方の端が 1000、他方の端が 2000 となっています。グリップ用のサーボが、突然がたがた動かないようにしました。
#!/usr/bin/node
var SerialPort = require("serialport").SerialPort;
var serialport = require("serialport");
var sp = new SerialPort("/dev/ttyO4", {
baudrate: 115200,
parser: serialport.parsers.readline("\r")
});
sp.on("open", function () {
console.log('open');
sp.on('data', function(data) {
console.log('data received: ' + data);
});
sp.write("ver\r", function(err, results) {
console.log('err ' + err);
console.log('results ' + results);
});
setTimeout(function() {
sp.write("#4 P1050\r");
}, 2000 );
setTimeout(function() {
sp.write("#4 P2050\r");
}, 4000 );
});
シングル ライト コマンドを使って、複数のサーボをグループ化して一度に動かすことができます。スピード (S) コマンドとタイム (T) コマンドを使用すると、サーボの動きを遅くすることができます。タイム コマンドで、グループに属するサーボが、指定時間で指定動作を順序どおりに完了するようにできます。
順番に動作を完了させる
上記のコードでは、各動作の間にギャップがあります。ひとつのコマンドで、サーボ グループを動作させることができます。各コマンドを CR で区切ります。新たにコマンドを投入すると、Lynxmotion AL5D は直ちにコマンドを実行します。コマンドを完了する前にこけないようにするために、コマンドの所要時間を記録することは大変な仕事です。
SSC-32 の ”query” コマンドは、一文字を返してきます。'+' は、前のコマンドが実行中であることを意味します。'.' は、コマンドが完了したことを意味します。
Lynxmotion に複雑な動作をさせるために、複数のコマンドを投入します。それには、前のコマンドの実行が完了してから、次のコマンドを投入する必要があります。いくつかのプログラミング環境では、一定時間、何もしないスリープやディレイ機能を使います。nodejs では、実行終了時に一度だけ呼び出されるコールバックを使います。下の ”waitForIdle” は、これを実現したものです。
”waitForIdle” は完了フラグをリセットし、”waitForIdle_func” を直後に実行するようにスケジュールします。”waitForIdle_func()” 内では、SSC-32 が動作を完了したら、コールバックが実行されるようにします。完了していなければ、”q” コマンドを使って、実行状態をチェックします。'.' が戻ってきたら、SSC-32 が実行を完了したということですから、on(data') コールバックを実行し、waitForIdle_func 関数と通信を行います。
var waitForIdle_complete = 0;
var waitForIdle_func = function( cb ) {
if( waitForIdle_complete ) {
cb();
} else {
sp.write("q\r", function( err ) {
if( err !== undefined ) {
console.log('ERR ' + err);
}
});
setTimeout( function() { waitForIdle_func(cb); }, 200 );
}
}
var waitForIdle = function( cb ) {
waitForIdle_complete = 0;
setTimeout( function() { waitForIdle_func(cb); }, 200 );
}
sp.on('data', function(data) {
console.log('data received __' + data + '__');
if( data == '.' ) {
console.log('movement complete!');
waitForIdle_complete = 1;
}
});
”async.queue” を使うと、コマンドとウエイトをリストし、待ち行列中のコマンドがすべて完了した時点で、drain() コールバックを実行することができます。この場合、すべての動作が完了すると、nodejs スクリプトはシンプルに終了します。
”pushAndWait” 関数は、サーボ動作コマンドを追加し、明示的にウエイトコマンドを挿入します。”pushAndWait” 関数による違いは、最後の 2 つの ”pushAndWait” メソッドと同じような 4 つの push() メソッドを見るとわかるでしょう。1 つの ”pushAndWait” メソッドは、もう 1 つのメソッドより早く完了するように、T1500 が設定されています。
var async = require("async");
...
var q = async.queue(function (task, callback) {
console.log('queue: ' + task.name);
if( task.cmd ) {
sp.write( task.cmd );
callback();
} else {
waitForIdle( function() { callback(); } );
}
}, 1 );
q.drain = function() {
console.log('queue: all items have been processed');
process.exit();
}
q.pushAndWait = function( name, cmd ) {
this.push( { name: name, cmd: cmd });
this.push( { name: 'wait', wait: 1 } );
}
q.push( { name: 'close', cmd: "#4 P1050 T2000 \r" });
q.push( { name: 'wait', wait: 1 } );
q.push( { name: 'open', cmd: "#4 P1900 T2000 \r" });
q.push( { name: 'wait', wait: 1 } );
q.pushAndWait( "close", "#4 P1050 T1500 \r" );
q.pushAndWait( "open", "#4 P1900 T2000 \r" );
このような待ち行列を利用したテクニックは、nodejs からシリアル コントロールするときに役に立つでしょう。特に、コントローラーが、各コマンドに応答を返し、前のコマンドが完了するまで次のコマンドを受け付けようとしない場合に有効です。
動作をポイントし、クリックする
3 車輪ロボットベースと同様、
Bootstrap、
jQuery と
bootstrap-slider の組み合わせは、Web インタフェースの制作を容易にしてくれます。Web ページには 6 つのスライダーがあり、そのうち 5 つはサーボの位置制御、1 つは移動の完了時間を指定するために用います。滑らかな動きを実現して、ロボット アームに余分なストレスを与えないように、低速で大きな動きを行えるようにするため、最後のスライダーは重要です。
下記に見るように、サーバーのコードは 50 行以下です。サーボ スライダーのレンジは 0 から 100 なので、moveServo() 関数は、入力値を SSC-32 が期待する (1000 から 2000 の間の) PWM 設定値に変換します。
var b = require('bonescript');
var SerialPort = require("serialport").SerialPort;
var serialport = require("serialport");
var io = require('socket.io').listen(8888);
var ssc32 = new SerialPort("/dev/ttyO4", {
baudrate: 115200
});
io.sockets.on('connection', function (socket) {
console.log("have connection");
var timeToCompleteMove = 1500;
var moveServo = function( servonum, perc ) {
if( typeof perc === 'undefined')
return;
var servoval = 1000 + 1000 * perc/100;
ssc32.write("#" + servonum + " P" + servoval + " T" + timeToCompleteMove + " \r");
}
socket.on('base', function (v) {
moveServo( 0, v.value );
});
socket.on('shoulder', function (v) {
moveServo( 1, v.value );
});
socket.on('elbow', function (v) {
moveServo( 2, v.value );
});
socket.on('wrist', function (v) {
moveServo( 3, v.value );
});
socket.on('grabber', function (v) {
moveServo( 4, v.value );
});
socket.on('time', function (v) {
if( typeof v.value === 'undefined')
return;
timeToCompleteMove = v.value;
});
});
ページ スタイリング以外に、index.html は下記のようなフラグメントを複数持っています。このフラグメントは、スライダーを生成し、スライダーの入力値が変化したときにサーバーにメッセージを送信します。
<div class="row">
<div class="col-md-1"><p class="lead">Grabber</p></div>
<div class="col-md-8"><input id="grabber" data-slider-id='grabberSlider'
type="text" data-slider-min="0" data-slider-max="100" data-slider-step="1"
data-slider-value="50"/></div>
</div>
...
$('#grabber').slider({});
$("#grabber").on('slide', function(slideEvt) {
socket.emit('grabber', {
value: slideEvt.value[0],
'/end': 'of-message'
});
});
ロボット アーム制御のコードが /home/debian/bonescript/webinterface に置いてあり、nodejs が lynxmodtion-arm-server.js から呼び出されると仮定します。すると、下記のコマンドは、BeagleBone Black 上で動作する Cloud9 Web サーバーを通して Web インタフェースを生成し、nodejs サーバーが自動的に起動するように設定します。
cd /var/lib/cloud9
ln -s /home/debian/bonescript/webinterface arm
cd ./autorun
ln -s /home/debian/bonescript/webinterface/lynxmotion-arm-server.js .
URL http://bbb.ip.addr/arm/ から Web ブラウザーでアームを制御できます。全部のソースを github に置いてあります。