ON THE HAND

引っ越しました http://tech.andhandworks.com

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

このエントリーをはてなブックマークに追加

  1. --/--/--(--) --:--:--|
  2. スポンサー広告

WiiリモコンとArduinoでQuadcopterを飛ばす(6):Wiiリモコンをプロポにする

【ご注意】
ここで紹介する方法は飽くまで個々の技術を組み合わせた例です。
Wiiリモコンのセンサ等はラジコンに用いることを想定されておらず、
操作不能に陥り想定外の場所に暴走したり墜落することがあります。
規制区域はもちろんのこと、安易な飛行は非常に危険ですので
重々ご注意ください。

遅々として進まないままだらだら書いているうちにドローンの飛行が
規制されるようになってしまって、こんなのどこで飛ばすんだよ状態ですが
一応書き進めます。

前回はArduinoに安全装置を実装する方法について説明しました。
今回は Arduino 部分の残りのコードを完成させてWiiリモコンから操作できるようにします。

使用するWiiリモコンはモーションプラスが
付いているだけの普通のものです。
本当はクラシックコントローラで
アナログ操作したかったのですが
Arduinoから認識させられませんでした。
ヌンチャクは認識するのですが操作中に
接続が切れまくって使い物にならないので断念。
Arduino には PS3 コントローラ用のライブラリがあるので
PS3のDUALSHOCK3を使うとWiiリモコンよりは
操作しやすいかもしれません。
今回のプログラムは使えませんが。

ソースコード全体

GitHub にソースコードを置きました。
https://github.com/onthehand/Quad_PPM/blob/master/Quad_PPM.ino

ほとんどの部分はこれまでに説明済みなので、
おさらいも兼ねて loop 関数を一通り見ていきます。

Wiiリモコンとの接続

接続確認。
接続が切れているときは何もしません。
void loop() {
  Usb.Task();
  if(! Wii.wiimoteConnected || Wii.getButtonPress(HOME)){ activate = 0; return; }

一定時間おきに加速度センサをチェックします。
前回チェック時と同一の値だったら cc をインクリメントし、
cc が一定値を超えたら disarm しています。
詳しくは前回の安全装置その1をご参照ください。

WatchDog と組み合わせてタイマーをリセットした方がカッコいいですが
これ以上割り込みを増やしたくなかったので少々ダサい実装です。
  if( millis() > uptime ){
    // check connecition
    if( pp == Wii.getPitch() && pr == Wii.getRoll() ){ cc ++ ; }else{ cc = 0; }
    pp = Wii.getPitch();
    pr = Wii.getRoll();
    if(Wii.getButtonPress(RIGHT)){
      currentTh++;
    }else if(Wii.getButtonPress(LEFT)){
      currentTh--;
    }
    uptime = millis() + SPAN;    
  }
  if ( cc > WII_TIMEOUT / SPAN ){ activate = 0; return; }
  activate = 1;

ところで、WiiリモコンのRIGHTボタンを押すと
スロットル(currentTh)をインクリメントしています。
LEFTボタンならデクリメント。

何故 UP, DOWN ボタンではないのかと言うと、
Wiiリモコンを横持ちするからです。
横持ちすると RIGHT ボタンが上に来ます。

上ボタンを押しっぱなしにした時のスロットル加速度は
SPAN 変数の値で決まります。
Wiiリモコン接続確認間隔も SPAN 変数を共用しているので
スロットル加速度を下げると接続切れ対応が遅くなってしまいます。
このあたりも実装がダサいですね。

カメラ操作

前々回のカメラについての説明をご参照ください
分かりにくい実装ですが、Wiiリモコンの「2」ボタンを押すとカメラにパルスを飛ばします。
  if( 0 < camtrig && camtrig < millis() ){
    camtrig = 0;
    digitalWrite( PIN_CAMERA, LOW );
  }
  if( camtrig == 0 && Wii.getButtonPress(TWO) ){
    camtrig = millis() + CAMERA_PULSE;
    digitalWrite( PIN_CAMERA, HIGH );
  }

ロール/ピッチ/ヨーの制御

コードがどんどんカオスになっていきます。

y, r, p はそれぞれYaw, Roll, Pitch用の変数です。
ヨーは左右の方向制御なので単純に UP/DOWN ボタン
(リモコン横持ちで左右ボタン)に割り当てました。
checkButton関数はただ単に次の対応に従って値を返しているだけです。
  • DOWNボタン押下→HIGH_PULSE_TIME(Yawが右)
  • UPボタン押下→LOW_PULSE_TIME(Yawが左)
  • 何も押されていない→HALF_PULSE_TIME(Yawが真ん中)


RollとPitchは加速度センサの値をMultiWiiのパルス時間幅に引き延ばして使用しています。
一見、変数割り当てが逆になっているように見えますが、これもリモコン横持ちの影響です。
Wiiリモコンは長手方向を軸にした回転が Rollです。
横持ちした場合に前後の操作が Wii リモコンの Roll 回転に相当しますが、
この方向の操作は Quadcopter の仰角制御に割り当てるのが自然なので、
Quadcopter から見ると Pitch 方向の回転に相当します。
複雑。
  unsigned int y = checkButton(DOWN,UP,HIGH_PULSE_TIME,HALF_PULSE_TIME,LOW_PULSE_TIME);
  unsigned int r = map((unsigned int)(Wii.getPitch()*100.0), 9000, 27000, MAX_PULSE_TIME, MIN_PULSE_TIME);
  unsigned int p = map((unsigned int)(Wii.getRoll()*100.0), 9000, 27000, MIN_PULSE_TIME, MAX_PULSE_TIME);

おまけ機能

リモコンの「1」ボタンを押すとMultiWiiのAUX1がHIGHになるようにしました。
AUX1, AUX2 と言うのは本物のプロポに付いているトグルスイッチだそうです。
MultiWii側でAUX1にBAROを割り当てておくと、WiiリモコンからBAROのON/OFFを操作できます。

ちなみに、BAROと言うのは高度計です。
BAROをONにすると気圧センサを使って高度を一定に自動制御してくれる、、、はず
なのですが誤差がひどくてあまりアテになりません。

AUX2 も一応使えるようになっていますが実際には使っていません。
と言うのも、MultiWii はカメラにパルスを送る機能を持っていて、
AUX2 を割り当てようとしていたのですが、ただでさえ割り込みに敏感な
MultiWii でカメラを操作するよりも Arduino 側で直接カメラ操作した方が
安全かつ確実だという判断です。
  // BARO is supposed to be activated by AUX1.
  unsigned int a1 = (Wii.getButtonPress(ONE)) ? HIGH_PULSE_TIME : HALF_PULSE_TIME;
  unsigned int a2 = (Wii.getButtonPress(TWO)) ? HIGH_PULSE_TIME : HALF_PULSE_TIME;
  if( Wii.getWiiState() & 0x01 ){ a2 = LOW_PULSE_TIME; }

ARM/DISARM

通常であれば ARM, DISARM はプロポの左右レバーの組み合わせで操作するのですが、
Wiiリモコンでそれをやるのは不可能なのでショートカットを作ってあります。
今回はBボタンとプラスマイナスボタンの同時押しという、
偶然の誤操作があり得ない組み合わせにしています。
  if(Wii.getButtonPress(B)){
    r = p = HALF_PULSE_TIME;
    if(Wii.getButtonPress(PLUS)){
      // arm
      currentTh = 1000;
      y = 2000;
    }else if(Wii.getButtonPress(MINUS)){
      // disarm
      currentTh = y = 1000;
    }
  }
}

そんなこんなで普通に買ったら数万円するプロポを
2千円そこそこのWiiリモコンで実現できました。

・・・と言いたいところですが、そんなに上手くは行きません。
WiiリモコンのBluetoothの通信可能距離はせいぜい10m程度なので
ちょっと飛ばしたらあっという間に通信が切れて制御不能になります。

また10m以内でも頻繁に通信切れが発生してかなり危険なので
実用性は皆無だと考えた方が良いでしょう。

双葉のプロポを馬鹿にすることなかれ。

次回はMultiWii側について説明します。
気が向いたら。

このエントリーをはてなブックマークに追加

スポンサーサイト
  1. 2016/06/23(木) 22:16:10|
  2. Quadcopter
  3. | コメント:8

WiiリモコンとArduinoでQuadcopterを飛ばす(5):安全装置

【ご注意】
ここで紹介する方法は飽くまで個々の技術を組み合わせた例です。
周囲に人や建物がある場所で飛ばすのはお奨めしません。
Wiiリモコンのセンサ等はラジコンに用いることを想定されておらず、
操作不能に陥り想定外の場所に暴走したり墜落することがあります。
安易な飛行は非常に危険ですので重々ご注意ください。


前回、Arduino から MultiWii への入力方法を説明しました。
今回は Wii リモコンをプロポにして Arduino を受信機として動作させ、MultiWii への入力と結合します。

・・・と言いたいところですが、安易に結合すると大惨事になるので、まずは安全装置を作ります。

Wii リモコンと Arduino の接続

以前紹介した「Wiiリモコン+Arduino+LEGO でラジコンを作る」と全く同じです。
過去ログをご参照ください。

安全装置その1:概要

Wiiリモコンの通信可能距離はせいぜい10m程度が限界です。
つまり、Quadcopterが10m以上遠くに飛んで行くと制御不能になります。
と言うか、暴走して明後日の方向に飛んで行きます。
そこで、Wiiリモコンからの操作を受け付ける前に安全装置を組み込んでおきます。

安全装置と言っても、MultiWii の FAILSAFE モードを起動するだけです。
FAILSAFE モードでは墜落しない程度にモーター出力を落として不時着を狙います。

MultiWii の FAILSAFE モードは、MultiWii の config.h で「#define FAILSAFE」の行の
コメントを外せば使えます。
MultiWii 動作中に FAILSAFE モードに落とすには前回説明した PPM 信号を
すべて LOW にすればOKです。

安全装置その1:実装

「ふーん、じゃぁ、Wiiリモコンの信号が切れたら PPM を LOW にすれば良いのね?」
と考えてしまいますが、そのように実装してテスト飛行すると大事故になります。


と言うのも、Arduino と Wii リモコンの間の Bluetooth が切れると、
Wii リモコンライブラリは最後に受信した値を吐き続けます。
そのため、切断されたのか同じ信号を受信しているのかの区別がつきません。

この問題を私なりに考えた結果
「Wiiリモコンの加速度センサ系の値(roll, pitch)は常に変動しているはず。
その値が同一のまま一定時間が経過したら通信が切れているのでは?」
という結論に達しました。

これを実装すると次のようになります。
roll と pitch の現在値を直前の値と比較し続け、同一の値が WII_TIMEOUT 以上
連続したら activate フラグを false にして PPM を LOW にします。
#include <Wii.h>
#include <usbhub.h>

USB Usb;
USBHub Hub1(&Usb);
BTD Btd(&Usb);
WII Wii(&Btd,PAIR);

float pp = 0.0;
float pr = 0.0;
unsigned int cc = 0;
#define WII_TIMEOUT 500

void loop() {
  Usb.Task();
  if(! Wii.wiimoteConnected || Wii.getButtonPress(HOME)){ activate = 0; return; }
  if( millis() > uptime ){
    // check connecition
    if( pp == Wii.getPitch() && pr == Wii.getRoll() ){ cc ++ ; }else{ cc = 0; }
    pp = Wii.getPitch();
    pr = Wii.getRoll();
          : (中略)
    uptime = millis() + SPAN;    
  }
  if ( cc > WII_TIMEOUT / SPAN ){ activate = 0; return; }
  activate = 1;
    : (中略)  
}
void isr_sendPulses() {
  digitalWrite(PIN_PPM, LOW);
  if( ! activate ){ return; }
    : (中略)  
}

安全装置その2:概要

そもそも通信が切れるかどうかに関係なく、危険な状態になったら不時着させたいケースがあります。
例えば、操縦し切れなくてとにかく着陸させたい場合です。
通信が切れなくても FAILSAFE にさせる操作が必要です。

安全装置その2:実装

この実装は非常に簡単です。
Wii リモコンのどれかのボタンを不時着ボタンとして割り当てて FAILSAFE を起動させるだけです。
下記の実装例では Wii リモコンの HOME ボタンを押すといきなり FAILSAFE になります。
void loop() {
  Usb.Task();
  if(! Wii.wiimoteConnected || Wii.getButtonPress(HOME)){ activate = 0; return; }
        : (後略)

安全装置その3:概要

FAILSAFE はモーター出力を落としてゆっくり着陸するのでかなり風に流されるケースがあります。
いまにも人や物に突っ込みそうな場合にはそんな悠長なことは言っていられません。

このようなケースでは Quadcopter を破壊してでも飛行を中断する必要があるため
強制的にモーターを停止する「撃墜モード」を用意しておきます。
高さがある場合は確実に Quadcopter が壊れますが、操作が遅れると被害が拡大するので
迷わず撃墜できる練習を積んでおくのも重要です。
その一方、上空で撃墜すると鉄の塊が降ってくる点が解決できないドローンの危険性の1つです。

安全装置その3:実装

撃墜モードについても先ほどと同様に Wii リモコンにモーター停止用のボタンを割り当てます。
ここではBボタンとマイナスボタンの同時押しを割り当てています。

ボタン1つだと何かに当たって誤動作する恐れがあります。
上空で撃墜モードが誤動作すると非常に危険なため、
Bボタン(リモコンの裏)とマイナス(表の小さいボタン)の同時押しという
偶然ではありえない組み合わせを割り当てました。

モーター停止には PPM で Throttle と Yaw を 1000 にします。
これは MultiWii に実装されている特殊コマンドで Disarm を意味します。
ちなみに、Throttle=1000, Yaw=2000 にすると Arm です。
void loop() {
          :(中略)
  if(Wii.getButtonPress(B)){
           :(中略)
    if(Wii.getButtonPress(PLUS)){
      // arm
      currentTh = 1000;
      y = 2000;
    }else if(Wii.getButtonPress(MINUS)){
      // disarm
      currentTh = y = 1000;
    }
  }

  setPulse(r,p,currentTh,y,a1,a2);
}


3重の安全装置を組み込みましたが、これでも安全対策は不十分です。
屋外飛行の前のデバッグ方法についても後日説明します。

このエントリーをはてなブックマークに追加

  1. 2015/10/04(日) 00:31:50|
  2. Quadcopter
  3. | コメント:0

WiiリモコンとArduinoでQuadcopterを飛ばす(4):Arduinoを受信機にする

【ご注意】
ここで紹介する方法を使って、周囲に人や建物がある場所で飛ばすのはお奨めしません。
Wiiリモコンのセンサ等はラジコンに用いることを想定されておらず、
操作不能に陥り想定外の場所に暴走したり墜落することがあります。
十分に安全な対策を取ってください。
・・・と言うより、実際に飛ばすのはお奨めしません。


前回、ざっと組み立てましたが、MultiWii や Arduino が空っぽなので中身を作っていきます。
MultiWii をフライトコントローラーとして動作させる方法は
他のサイトにも詳しく載っていますので先に Arduino の方について説明します。

今回の Arduino の用途

MultiWii はフライトコントローラーとして Quadcopter の姿勢制御に使用します。
通常、プロポからの電波をプロポの受信機で受信し、制御信号として MultiWii に入力します。

・・・が、今回はプロポの代わりに Wii リモコンを使用しますので、
MultiWii に入力できるような Wii リモコンは存在しません。
そこで、Arduino UNO を Wii リモコン受信機として動作させます。

MultiWii への入力信号

MultiWii への入力信号には次の7チャンネルあり、前半の4チャンネルが必須です。
これらにアナログ(PWM)入力することが多いようです。
  • Roll
    進行方向を軸にした回転
    つまり、右旋回/左旋回のための傾き制御

  • Pitch
    進行方向に垂直で地面に水平な方向(つまり左右方向)を軸にした回転
    つまり、機首の上げ下げ

  • Yaw
    地面に垂直な方向を軸にした回転
    つまり、旋回無しでの方向転換

  • Throttle
    推力
    つまり、Roll, Pitch が中央値の場合は上昇/下降

  • AUX1, AUX2, AUX3
    追加の制御信号
    例えば、高度計での自動制御 ON/OFF やカメラのシャッター制御など

Arduino UNO には PWM 出力ピンが6本ありますのでこれで、、、と言いたいところですが、駄目です。

と言うのも、Wii リモコンとの通信用に USB Bluetooth ドングルを使います。
そのために USB Host Shield を使う訳ですが、USB Host Shield は Arduino UNO の
PWM 出力6本のうちの3本を潰しますので MultiWii と接続するためのピンが足りません。

なお、USB Bluetooth ドングルと USB Host Shield の利用方法について、
詳しくはArduino+Wiiリモコンでラジコンを作る回で説明しています。

信号線1本でMultiWiiに入力する

Arduino UNO のピンが足りないのでどうするかと言うと PPM という方式を使って
信号線+電力線+GNDの3本だけで全ての信号を送ります。

PPM信号についてはこのあたりのサイトが分かりやすいです。
ワンチップマイコンによるラジコン信号のデコード
魔法の大鍋
特に魔法の大鍋さんは PS3 リモコンで MultiWii を制御していてなかなか熱いです。

これらのサイトで説明されていることをざっくりまとめると、各チャンネルの値をパルス幅で
表現して1本の信号線上で順番に送信する方式が PPM です。

ArduinoでPPM信号をMultiWiiに送る

Arduino の Timer を使って正確に割り込みすると PPM 信号を生成することが可能です。
具体的には次のようなコードになります。
#include <TimerOne.h>

#define CHANNELS 6

#define ROLL     0
#define PITCH    1
#define THROTTLE 2
#define YAW      3
#define AUX1     4
#define AUX2     5

#define MIN_PULSE_TIME  1000 // 1000us
#define MAX_PULSE_TIME  2000 // 2000us

#define SYNC_PULSE_TIME  3050 // 3000us

#define PIN_PPM 5

unsigned int pw[CHANNELS];

void setPulse(unsigned int r, unsigned int p, unsigned int t, unsigned int y, unsigned int a1, unsigned int a2){
  pw[ROLL] = r;
  pw[PITCH] = p;
  pw[THROTTLE] = t;
  pw[YAW] = y;
  pw[AUX1] = a1;
  pw[AUX2] = a2;
}

void setup() {
  pinMode(PIN_PPM, OUTPUT);
  pinMode(PIN_CAMERA, OUTPUT);
  digitalWrite( PIN_CAMERA, LOW );

  Serial.begin(38400);
 
  activate = 0;
  
  // Start timer with sync pulse
  Timer1.initialize(SYNC_PULSE_TIME);
  Timer1.attachInterrupt(isr_sendPulses);
  isr_sendPulses();
}

// Sync pulse first
volatile int currentChannel = 0;

void isr_sendPulses() {
  digitalWrite(PIN_PPM, LOW);
  if( ! activate ){ return; }

  if (currentChannel == CHANNELS) {
    // After last channel
    Timer1.setPeriod(SYNC_PULSE_TIME);
    currentChannel = 0; // Will be 0 on next interrupt
  } else {
    if(pw[currentChannel] < MIN_PULSE_TIME){ pw[currentChannel] = MIN_PULSE_TIME; }
    if(pw[currentChannel] > MAX_PULSE_TIME){ pw[currentChannel] = MAX_PULSE_TIME; }
    Timer1.setPeriod(pw[currentChannel]);
    currentChannel++;
  }

  digitalWrite(PIN_PPM, HIGH);
}
setPulse 関数に Roll, Pitch, Yaw, Throttle 等の値を渡して一旦バッファに保持します。
上記のコードでは省略していますが、loop 関数内で Wii リモコンからの信号を受け、
setPulse 関数で更新していきます。

setPulse とは別に、起動時に setup 関数内で Timer1 をセットします。
こうすることで SYNC_PULSE_TIME 後に割り込みが発生して isr_sendPulse 関数が呼ばれます。

isr_sendPulse 関数内では、setPulse でバッファにセットされた値に応じて Timer1 の
割り込み間隔を上書きしてパルス幅を制御しています。

本当は PIN_PPM を LOW にする時間を PPM の仕様に合わせた時間幅にしないと
いけない気がしますが、何故か上記のコードでも MultiWii は正しい値を
受け取ってくれているのでそのままにしています。

MultiWii と Arduino UNO の接続

MultiWii の PPM ピンはデフォルトで D2 なので、次のように接続します。
  • Arduino PIN_PPM → MultiWii D2
  • Arduino GND → MultiWii GND
  • Arduino Vin → MultiWii 5V

MultiWii 5V と Arduino 5V なんじゃないの?と思うかもしれませんが、
MultiWii 側にバッテリーを繋ぐと MultiWii から 5V が給電され、
その状態で Arduino に USB 等を接続すると Arduino 5V にも給電されて
どちらかに逆流が発生してしまって駄目なのではないかと思います。(たぶん)

Arduino Vin と MultiWii 5V を接続することで、Arduino の USB ケーブルを
外しても MultiWii から給電されて Arduino にはバッテリー不要になります。

実は、本当にこんな使い方で合っているのか自信がないのですが、一応、これで動作しています。

オマケ:カメラの制御

MultiWii にもカメラに録画開始用パルスを送る機能があるのですが、
せっかく Arduino を使っているので、MultiWii 無しでカメラを
リモート制御してみます。

とは言え、Go Pro はそこそこの値段がしますし、
コンデジだと重すぎて不安定になります。

そこで、Walkera というラジコン用のカメラを使います。
中国製で動作は怪しいですが恐ろしく安い上に10g程度の超軽量です。

コネクタが Walkera 専用になっていますが、途中で切って
信号線3本を Arduino のデジタルピンと 5V, GND にそれぞれ繋げばOKです。

Arduino からカメラを制御する

このカメラは1.5秒前後のパルスを信号として受け取ります。
静止画モードの場合はパルス受信で1枚撮影。

これはメーカーから公開された仕様ではなく私がイイカゲンに試して
見つけただけなので正確ではないかもしれませんが。

Arduino からこのパルスを送りたいのですが、前述の通り PPM 信号で
タイマーを1つ使っていますのでこの程度のことに割り込みを使うのは避けて
次のようにしました。
#define PIN_CAMERA 2

unsigned long camtrig = 0;
#define CAMERA_PULSE 1500

void setup() {
  pinMode(PIN_CAMERA, OUTPUT);
}

void loop() {
  if( 0 < camtrig && camtrig < millis() ){
    camtrig = 0;
    digitalWrite( PIN_CAMERA, LOW );
  }
  if( camtrig == 0 && Wii.getButtonPress(TWO) ){
    camtrig = millis() + CAMERA_PULSE;
    digitalWrite( PIN_CAMERA, HIGH );
  }
}
Wii リモコンの2番ボタンを押すと 1.5 秒程度のパルスを発信します。
loop 内で時間を測っているだけなので不正確ですが
一応カメラは受け取ってくれるようです。

なお、1.5秒分 delay した方が簡単そうに見えますが、
delay は内部で Timer を使っていて PPM の
割り込みと干渉しがちなのでお奨めしません。

これで撮影するとこんな感じになります。
アクションカメラではないので画質はガタガタ。

マルチコプターが問題になるよりもずっと前に撮った映像ですが、
最近はこんな公園内で飛ばすのはNGかもしれませんね。。。


ちょっと長くなったので今回はここまで。
次回以降で Wii リモコンのボタンと制御チャンネルの割り当てや
MultiWii の設定を行っていきます。

このエントリーをはてなブックマークに追加

  1. 2015/08/26(水) 23:08:31|
  2. Quadcopter
  3. | コメント:2
次のページ
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。