1関節ロボットによる逆振り子理論の検証

以下の記事の元になっているのは、この論文である
早朝から、アルミの板と棒を切ったり穴開けたりしながら、この間議論してきた理論を実証すべく、自由な関節を1個だけ持ったロボットを作った。たくさんのボルトやナットを使ったが、一時期、電子工作に凝っていたので、その時のものでほとんど間に合わせた。
「冷蔵庫にあるものでラーメンを作った」という感じのロボットだ。
いや、「これはロボットではない」と言われればあえて否定しないが、自分では、私が自分で作った最初のロボットの筐体だと思っている。脳はあるが、まだ知恵は何も入っていない。あえて名前をつければ、AiComedian ver.0.1 である。アイコメと呼ぼうか。
アルミの縦棒が机についているあたりに、アルミの横棒があり、それらをつないでいるのが横方向にだけ自由に回転する関節、ジョイントである。
横棒には、前後に倒れないように4ミリのアルミ棒が左右に二本つけられている。だから、この縦棒は、左右にしか動かないのである。
ただし、今こうして立っているのは、上部に二つのサーボモータがついていて、下の横棒との間に0.3ミリのステンレス線が張ってあるからである。
さらにその上には、コンピュータ( RaspberryPi)と3軸加速度センサー、I2Cによるサーボコントローラを乗せたアルミの板がある。
サーボは、水平角度を0度として運用しているが、およそであって、今の状態は、手前のサーボ(1番)がサーボの角度で0度、向かいのサーボ(0番)がサーボの角度で+3度でほぼ釣り合った状態になっている。サーボの角度を下向きに変化させると、縦棒は勢いよく倒れる。
C++で加速度センサーとサーボをコントロールするクラスを、それぞれに作ってあるので、センサーの検出した傾きから、サーボをコントロールして、逆さ振り子の状態を実際に創り出したいと思っている。
ことの成否は、転倒開始時に、加速度センサーがどんなデータを送ってくるかにかかっている。

加速度センサーの精度

以下の記事の元になっているのは、この論文
ロボットの転倒に関する議論をしてきたが、逆振り子で転倒を回避する場合でも、自らの傾き、ないしは揺らぎを捉えることが不可欠である。ここでは、KXSD9-2050という750円のセンサーを使っているので、どこまで精度が出るのかがいたって不安である。
そこで、より詳細に調べてみた。
KXSD9-2050については、sensitivityを819counts/g (g: 重力加速度 = 9.80665 m / s2)に変更したことを除いて、初期設定を採用している。
10ミリ秒に一つのデータを取る計算で、500個のデータを取った。その間に、センサー(RaspberryPiに固定している)を6回だけ、数ミリずらせる速度をY軸方向に与えた。そのうちの50個目から200個目までのデータを図で表したものが以下の画像である。
Y軸だけをみている。縦軸のgはY軸方向の重力加速度である。水平方向であるから0でなければならないが、0.05となっているのは、多少Y軸が傾いていることからくるのか、私の家が傾いているのか、机が傾いているのか、そんなところだろう。ホワイトノイズとして処理する。
数ミリ動かしたことによる影響は、赤と青で色ぬりされた部分に現れている。赤から始まっているのは、Y軸のプラスの方向に加速度が与えられ、マイナスになっているのは、速度が低下して、ほぼ再び止まったことを表している。止まったということは、赤の面積と青の面積がほぼ等しいことを表す。
実際移動した距離は加速度がプラスから始まっているので、Y軸方向に移動してどこかで止まったということで、それまでのプラスの距離になる。
指で数ミリ動かしただけで、これだけのものがとらえられることは、この安物のセンサーも結構使える可能性があることを示している。
今、赤と青の部分が単純な三角形だったとしよう。横幅が8目盛、高さが0.1gくらいになる。この時、最高速度は、
80(ms)X0.1(g)X(1/2)=0.08X0.1X0.5X9.80665=0.039226(m/s)
およそ1秒間に4センチ動くくらいの速度が最高速度だったということになる。
それから速度が0になるまで同じくらいの時間がかかったとしよう。簡単化のために、全て、三角形で捉えよう。(実際は曲線)
そうすると、移動した距離は、
0.08X2X0.039226/2=0.00313808(m)
すなわち、3.2ミリ移動したことになる。私が先に数ミリ動かしたと言ったが、ほぼその値に一致する。
ということは、この加速度センサーが捉えている加速度は、ノイズできなものはありながらも、なかなか、実態を反映しているということなのである。

3D加速度センサーのKXSD9-2050をRaspberryPi 3 から使う

3D加速度センサーのデータの拾い方に苦労したが、これでいいのかもしれないというところまで来たので、記録しておこう。
加速度センサーを起動して、500回分ループ動かした(データを取得した)。1データ100m秒間隔でとっている。
まず、水平においている。縦が、Z軸だから、重力がかかっているので、1gのはずだが、3gになっているのは、測定レンジを6gにしてあるからなのかもしれないがよくわかっていない。確かに、裏返すと-1gで、3gだと-3gで結局幅が6gになるので、それでいいのかもしれない。
まず、X軸に向かって回転しながら、裏返すところまで持って行った。途中ちょっとフラフラした分も描かれているが、Z軸がマイナスに触れた分、X軸に重力がかかっている。次に、Y軸に沿ってマイナス側に回転させたら、確かにY軸はマイナスに触れている。その後、X軸とY軸をそれまでの操作と逆方向に回転させた。図はほぼそれを忠実に再現している。
モジュールのつなぎ方を記録しておく。

モジュールは、KXSD9-2050(画像の右下)だ。RaspberryPi(画像では、下にあるものだが、ボードを1枚被せて、電源5V、電源3.3V、I2Cの拡張コネクタを乗せている)とI2C(アイスクエアシーと発音する)で接続する。
ピン接続は以下の通りだ。
モジュール1→電源3.3V+(新しく5Vから降圧したピンに、Raspberryからとっても良い)
モジュール2→電源のGNDー
モジュール5→RaspberryPiのSCL、GPIO3
モジュール6→電源3.3V+(これをつながないと、I2Cがうまく機能しない)
モジュール7→RaspberryPiのSDA、GPIO2
以上だ。3.3Vを二つ繋がなければならないのが気をつける点だ。
繋いだら、RaspberryPiのターミナルで
i2cdetect -y 1
と打って、モジュールのアドレスを確認しておく。
$ i2cdetect -y 1
0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --
サーボモータ関係のものが二つ繋がっているが、モジュールのアドレスは、0x18となる。
プログラムは、
#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
// コンパイル
// gcc 3dsensor.c -o 3dsensor -lwiringPi
int main(int argc, char **argv)
{
int i2c_fd;             // I2Cデバイスファイル
// sudo i2cdetect -y 1 を実行すればKXSD9-2050のI2Cアドレスを知ることができる
int i2cAddress = 0x18;  // KXSD9-2050のI2Cアドレス
int i;
int xregH,xregL,xout;
int yregH,yregL,yout;
int zregH,zregL,zout;
double xac,yac,zac;
// I2Cデバイスファイルをオープン
i2c_fd = wiringPiI2CSetup(i2cAddress);
// KXSD9-2050 イニシャライズ
if((wiringPiI2CWriteReg8(i2c_fd,0x0a,0xca)) < 0){
printf("write error register 0x0a: KXSD9-2050 イニシャライズに失敗\n");
return 1;
}
printf("write register:0x0a = 0xca\n");
// WiringPi イニシャライズ
if(wiringPiSetupGpio() == -1){
printf("WiringPi イニシャライズに失敗\n");
return 1;
}
// 加速度データを取得
printf("データを500個取得します\n");
for(i=0; i<500; i++){
// デバイスからデータ取得
xregH = wiringPiI2CReadReg8(i2c_fd,0x00);
xregL = wiringPiI2CReadReg8(i2c_fd,0x01);
xout = xregH<<4|xregL>>4;
//printf("xH: %6d  xL: %6d\n",xregH,xregL);
yregH = wiringPiI2CReadReg8(i2c_fd,0x02);
yregL = wiringPiI2CReadReg8(i2c_fd,0x03);
yout = yregH<<4|yregL>>4;
//printf("yH: %6d  yL: %6d\n",yregH,yregL);
zregH = wiringPiI2CReadReg8(i2c_fd,0x04);
zregL = wiringPiI2CReadReg8(i2c_fd,0x05);
zout = zregH<<4|zregL>>4;
//printf("xH: %6d  xL: %6d\n",xregH,xregL);
xac = (double)(xout-2048)/(double)273;
yac = (double)(yout-2048)/(double)273;
zac = (double)(zout-2048)/(double)273;
//printf("X軸 : %6d   Y軸 : %6d   Z軸 : %6d\n",xout,yout,zout);
printf("No. %4d : X軸:%6d Y軸:%6d Z軸:%6d ==>> X軸:%10.6f Y軸:%10.6f Z軸:%10.6f\n",i,xout,yout,zout,xac,yac,zac);
delay(100);
}
return 0;
}
指定したアドレス(仕様書に書いてある)から、データを取得できる。ただ、2バイトで送られてくる。そのうちのHighとLowから、12ビットが使われるのがちょっと面倒。そこで、取得したデータの、Highを4ビットだけ左にシフトさせ、Lowを逆に4ビット右に論理和をとったものをデータとしている。
操作のイメージは次のようになる。有効なビットをA、無効なビットをX、ゼロは0で表そう。
まず、Highは AAAAAAAAを左に4ビットシフトさせてAAAAAAAA0000とする。
次にLowはもともとAAAAXXXXとなっているので、右に4ビットシフトさせて0000AAAAとするわけである。
上記二つの値の論理和をとると、
AAAAAAAAAAAA
と見事に12ビットになるという手筈なのだが、これでいいかどうか確証はない。
このデータから元の加速度を出す方法も大事だ。プログラムの中に書かれているようにやればいいはずだ。まず、オフセット値が与えられている。ここの場合、2048カウントである。得られたデータとこのオフセット値の差をとって、その差を1gあたりのカウント値 (6g幅を使用している場合(デフォルト)は273)で割ることによってgの値が取れるはずなのだ。これも、誰かに確かめているわけではないが、論理的にはそうなるということだ。
これで計算したものが、冒頭の図である。図がほぼそんな感じなので、間違っていないと思う。
なお、プログラム始めの
KXSD9-2050 イニシャライズ
は、マニュアルによると、電源起動時にやっているようなのだが、念のためにやっておいた。