前回、Arduino標準MouseライブラリとTFTシールドのタッチパネルを使って、ペンタブレットを作成しました。今回はその流れで空中マウスを作って見ようと思います。
実は、過去に一度、空中マウスを作ったことがありました(下記事)。
「#10-2 空中マウスを作ってみよう」
上の記事では、STM32と加速度センサを使いました(下図)。センサチップはMPU9250です。
センサチップMPU9250は、3軸加速度センサ、3軸ジャイロセンサと、AK8963(3軸磁気センサ)を搭載しています。「#10-2 空中マウスを作ってみよう」の記事で試作した時は、この内、加速度センサを使いました。
加速度センサで重力加速度の方向を検知して、そこから機体を傾けることで、傾けた方向へマウスポインタを動かす、と言うアルゴリズムになっていました。
これはこれでアリかな?と思うのですが、マウスカーソルの移動を停止したい場合には、機体を水平位置で静止させる必要があって、これが少〜し使い難いかな?って思っていました。
さて、今回作る別解は、恐らく市販の空中マウスなどでも採用されているであろう、ジャイロセンサを使います。また、ArduinoとしてはHIDマウスのデバッグがし易いArduino DUE(下図)を使います。
今回も2個のマウスボタンに対応します。
2端子タクトスイッチを使ったボタンの実装方法は前回こちらで解説済みです(下図、参考)。
今回はマウスボタン以外に、マウスとしての機能をON/OFFするためのマスタスイッチを1個追加します。
空中マウスをプレゼンテーション等に使う時、マウス操作をする時以外はマウスモードOFFにしておくと、カーソルがふらふら動かないとか、U/Iとしての利点があるためです。PCにUSB接続した時の空中マウスの状況によって、起動後いきなりカーソルが動いてしまうのは嬉しくないため、起動直後のモードはマウスモードOFFとしてあります。
マスタスイッチはトグルスイッチにしてあるので、1回押すとマウスモードON、もう一回押すとOFF、の様にトグルします。マウスモードがONになると、LED_BUILTINが光るようになっているので、この状態で目視確認できます。
Mouseライブラリについての解説はこちらです。
今回使うのは、Arduino標準Mouseライブラリですので、Arduino IDEをインストールした時一緒にインストールされています。
Mouseライブラリを使う時、いつも、ざっくり適当に決めていたのですが、本来であればレポートレートを確定しておかないとなりません。
マウスのレポートレートと言うのは、マウスがPCに対して、マウスの移動量をレポート(報告)する間隔のことを指します。
現在使っているゲーミングマウスのレポートレートを確かめたところ、1,000Hzに設定されていました。
1,000Hz → 1ms間隔かぁ〜。
DUE(Cortex-M3 @84MHz)の実力をもってすれば、出来なくもないかな……。でも、空中マウスでゲームプレイする人は、いないだろうし、「普通のマウス」で良いのだけど?と、調べてみたところ、「普通のマウス」は、レポートレート100Hz程度らしいです。
100Hz → 10ms間隔
うん。まあ、こんな所でしょう。今回は、マウスレポートレートを100Hzでプログラムすることにしました(下の定数で設定)。
const int air_msec_ITVL=10; // air: mouse move interval [ms]
完成したプログラムを以下にリンクしておきます。
完成プログラム:air_mouse_gyr.ino
基本的に、空中マウスとして自立するようになっています。プログラム先頭付近の「#define USE_SERIAL」を有効にしてコンパイルすると、シリアルモニタから以下のコマンドを実行することができます。
シリアルモニタコマンド仕様:
すべて、デバッグ用のコマンドです。デバッグする時以外は、「#define USE_SERIAL」を無効にしてコンパイルしてください。
回路図を起こしてみました。以下図となります。
回路図って程の物でもないですが……(1^_^;)。
「#10-2 空中マウスを作ってみよう」の時、ジャイロセンサは使わなかったのですが、MPU9250を使うついでにレジスタの読み出しだけは実施していました。
今回、本格的にジャイロセンサを使って見る、と言う事でデータシートを読み直してみました。
MPU9250のデータシートを検索(別ウインドウが開きます)
ただね……、見つかったデータシートを読む限り、ちょっと言葉足らずと言うか説明不足が否めなくて、完全には理解できませんでした。と、言う前提で、筆者の読解できた範囲で解説を続けるので、ご了解ください。
MPU9250のジャイロセンサは、デフォルト(電源投入状態)で常に測定されていて、レジスタ値が適時書き換わっています。レジスタは以下の通りです。
アドレス | レジスタ名 | 説明 |
0x43 | GYRO_XOUT_H |
X軸周りの回転角速度レジスタ 16ビットAD変換結果[LSB] |
0x44 | GYRO_XOUT_L | |
0x45 | GYRO_YOUT_H |
Y軸周りの回転角速度レジスタ 16ビットAD変換結果[LSB] |
0x46 | GYRO_YOUT_L | |
0x47 | GYRO_ZOUT_H |
Z軸周りの回転角速度レジスタ 16ビットAD変換結果[LSB] |
0x48 | GYRO_ZOUT_L | |
0x13 | XG_OFFSET_H | GYRO_XOUTのオフセットレジスタ |
0x14 | XG_OFFSET_L | |
0x15 | YG_OFFSET_H | GYRO_YOUTのオフセットレジスタ |
0x16 | YG_OFFSET_L | |
0x17 | ZG_OFFSET_H | GYRO_ZOUTのオフセットレジスタ |
0x18 | ZG_OFFSET_L |
さて、レジスタの仕様としては、解り易いのです。ここまでは理解できましたが、問題はここから先です。
これ(↑)、同じこと言ってます?考えれば考えるほど、良く解らなくなる。
G_OFFSETレジスタの方は、「FS_SELによって割り算する値が変わりますよ」と言っているのに、GYRO_OUTレジスタの方は、「FS_SELに関係なく131で割ると[°/s]に換算できる」って言っている様に読める。
131の根拠も、216LSB÷500dpsなんだけど、FS_SELでフルスケール(dps)値を変更できるのに、常に500dpsで割り算して良いの?ってなる。
極めつけは、「Nominal Conditions」 → 「公称条件」。普通に読み解くと、「公称条件を満たした時、示された数式が有効」なのでは?と、すると、FS_SEL=0(250dps)の設定時に有効な式なの?他の設定では使えないの?
ダメ。理解できない……。
降参する前に、もう一度、頭を整理してみました。
恐らく、これで正解。もっと解りやすいデータシート書けなかったのか(1@_@)??
幸い、今回の空中マウスでは回転角度の絶対値[°]は必要ないので、角度への換算はしませんでした。GYRO_OUTレジスタを読み出し、MSB側8ビット程度を使ってマウスカーソルを動かしています。
オフセットが嫌ですね〜。
ジャイロセンサには、個体差のあるオフセットが付いているので、これを何とかしないとなりません。もし、オフセットが一定以上大きかった場合、MPU9250を静止させているにもかかわらず、マウスが一定方向に動き続けてしまうことになるからです。
一番簡単なのは、以下の方法ですね。
オフセットには個体差があるので、全ての個体で上の校正を実施しなければならないのは面倒ですが、一番楽なので、この方法を採用します。
ジャイロセンサのオフセット値を知るために予備実験をしましょう。
こちらの回路を組んで、プログラム(air_mouse_gyr.ino)をDUEに書き込んでください。この時、プログラム先頭付近の「#define USE_SERIAL」を有効にしてコンパイルしてください。
予備実験サンプル:【 】内はシリアルモニタからの送信データ
【gyr,32】
OK+ gyr -313 -31 46
OK+ gyr -347 -22 5
OK+ gyr -350 -1 -21
:
: (中略)
:
OK+ gyr -344 26 41
OK+ gyr -328 10 -2
OK+ gyr -344 18 4
OK: gyr
MPU9250を机の上などに静かに置いた状態で上のコマンドを実行し、実行が終わるまで静止させておいてください。
以下表および以下図に、手元のMPU9250での測定結果をまとめておきます。
ジャイロセンサの オフセット値 |
最小値 [LSB] |
最大値 [LSB] |
幅 [LSB] |
平均値 [LSB] |
x軸 | -383 | -313 | 70 | -340 |
y軸 | -31 | 27 | 58 | 4 |
z軸 | -21 | 55 | 76 | 26 |
※単位は全てLSB、ジャイロセンサのデータレジスタGYRO_OUTの読み出し値その物。
上の予備実験で測定したオフセットの平均値を定数へ設定して、プログラム(air_mouse_gyr.ino)を完成させます。
const int gyr_OFST_X=-340; // gir: gyro sensor offset [lsb]
const int gyr_OFST_Y=4;
const int gyr_OFST_Z=26;
ここからは蛇足になってしまうけど、少し考察しておきます。
オフセットを見破れば良いので、少し工夫をすると自動的にオフセットキャンセルする方法も、あるかもしれません。
本節の様なキャリブレーションが不要になるので、興味のある方は工夫してみては?
手元のMPU9250モジュール(下図)の場合、表面のシルクに座標軸が描画されています。
上図でも確認できると思います。3軸周りの回転は、この軸でレポートされます。今回、DUEをHIDマウスにして、機体を回転させることでマウスカーソルを操作しますので、DUEとMPU9250の固定方向がミソです。
下図が、今回のプログラム(air_mouse_gyr.ino)で前提とした、DUEとMPU9250の固定方向です。
※上写真の様に、シールド基板とブレッドボードを使って、DUEとMPU9250の位置関係を固定しました。位置関係が固定できさえすれば、方法は問いません。
上図の向きで、DUE+MPU9250を両手でつかんで操作する場合、左右へ振るとz軸周り、上下に振るとy軸周りの回転が検出されます。……x軸周りの回転は、今回使いません。
使用するモジュールが異なったり、上図と別な角度で固定したりすると、回転軸とマウス移動方向の対応がプログラム(air_mouse_gyr.ino)と不整合となって、期待した動作になりません。
固定方向を上図に合わせるか、不可能な場合は、プログラム(air_mouse_gyr.ino)の方を変更して使ってください(以下、コード該当箇所)。
// gyro to mouse pointer mapping
dx=-(gyr_z - gyr_OFST_Z); // 左右dx → z軸周り(符号反転)
dy=-(gyr_y - gyr_OFST_Y); // 上下dy → y軸周り(符号反転)
※ジャイロセンサのオフセットキャンセルは、オフセットレジスタ(G_OFFSET)を使わず、上の様にプログラムで実施しています。
なお、ジャイロセンサは、回転速度(回転の方向と速さ)を検出可能なセンサですが、べつにMPU9250を独楽の様に回転させる必要は無くて、DUE+MPU9250を手首や肘、肩、腰などを中心として回転させた状態でも、検出できます。
人間が手に持って操作する限り、普通に動かすと関節を中心とした回転運動になるため、ジャイロセンサで問題なく検出できます。逆に、手に持った状態で回転を伴わない様に上下・左右に振り回すことの方が、相当難しいと思いますよ。
実験はジャイロセンサのオフセットを測定した予備実験によって山を越えていますので、後は実際に空中マウスとして使って見るだけです。
プログラム(air_mouse_gyr.ino)をコンパイルしてDUEに書き込んでください。
外部回路をDUEに接続(回路図、ボタンの実装)、MPU9250とDUEの位置関係を以下図の通りに固定してください。
DUEのネイティブUSBポートをPCのUSB端子に接続してください。
※もし、PCに接続した時、マウスとして認識されないようなら、一度DUEのリセットボタンを押してみてください。DUEのネイティブUSBポートだけをPCに接続した時、リセットを掛けないとHIDマウスとして機能しない様な気がします。
DUEが空中マウスになった筈ですので、忘れずにマスタスイッチを押してマウスモードに入れてから、マウスカーソルを操作してみましょう。その様子を以下動画にしておきましたのでご確認ください。
多分、操作性としてはマウスに大きく劣ります。空中マウスの真骨頂は、プレゼンテーションなどスクリーンに映し出した画面を、その場で操作したい時です。マウスを動かすテーブルや台などが無くても、空中の動作でマウスカーソルを操作することができますCool!