2014/07/07 ■ 社内ハッカソンにて潜水艦をハンドパワーで潜航させてみた Xでつぶやくこのエントリーをブックマークに追加このエントリーを含むはてなブックマーク

いま自分が働いている会社社内ハッカソンがありました。「普段使っていない環境(言語・フレームワークなど)を使う」という縛りでエンジニア300人以上(!)が一カ所に集まり黙々とひたすらコードを書くというとてもすてきなイベントです。
去年は「dwango readerを作るとしたら」というテーマだったので「紙テープでフィードが出力されたらおもしろいかなあ」とがんばってみたのですが、ちゃんと完成せず時間切れ。ちょっとくやしかったので、今年はもうちょっとサクッと作れそうなものを手がけてみることにしました。つまり「潜水艦をハンドパワーで潜航させてみる」

…と、5時間一本勝負、やってみたらこんな感じに仕上がりました。
赤外線制御の超小型潜水艦を、Leap Motionで自在に動かせます。手のひらを前に出すと前進、後ろに引くと後進。左右に動かすと左旋回・右旋回。上下に動かすと浮上・潜航です。さっと作ったわりにはけっこうおもしろいです。


実のところ当日朝まで何をやるかは決めかねていました。せっかくならフィジカルなものを絡めたい、でも何やるかはいまいち決まらない。ただ、ひとつだけ「Arduinoをつかってみよう」ということだけは決めていました。Arduino以前から普通にマイコン野郎だったということもあって、なんだかいままで使う機会が微妙になかったのでした。せっかくだから使ってみたい。ハッカソンの趣旨(普段使っていない環境で書くべし)にも合うし。

以降、ハッカソン中に書き込んだツイートをベースに、どんなふうに進めていったかをまとめてみます。
最初は超小型クアッドコプターを制御しようかなーと思っていたのですが、
  • コントロールが無線(2.4GHz)なので直接制御はできず「プロポを改造して制御する」ことになる
  • つまりプロポをぶっ壊す必要があるが正直もっと遊びたい(壊すのが惜しい)
  • 赤外コントロールのヘリコプターの安いやつでも買って(プロポは使わず)直接制御しようかなと思ったがぐぐってみたら既に先達がいるようだ
  • で、どうしようかなーと思いながらビックカメラをうろついていたら、おもしろげな超小型潜水艦はっけん!
というわけで
こいつを制御してみよう…!と決定。これが朝11時ころ。

その後ミーティングをひとつ済ませ、さっそく使えそうな部品や機材をかばんに放り込んでハッカソン会場に移動します。ハッカソン自体は13:30~18:30、5時間でどこまでできるか、いざ勝負です!




まずは潜水艦を開梱してしばらく遊んだあと、たまたま手元にあった赤外リモコン受光モジュールに電源だけつないでやり、出力ピンをオシロに突っ込んでプロポが出している赤外線のパターンを観察しました。14ビットひとかたまりのコードが連続送信されているようです。ざっと見た限りでは特に変なところはなく、わりと素直に制御できそう。

赤外線制御のおもちゃであそぶ、ということ自体もほんとに朝になるまで決めてなかったので、使いやすい赤外LEDも手元にない!(笑)しかたないので(こちらはたまたまあった)チップ赤外LEDに足を付ける作業が必要になりました。時間が余ってれば秋葉原に買いに行ってもいいかなーとは思っていたのですが、まああるものは使いましょう。

と、さきほどまでの赤外LEDの準備が出来てからいざ潜水艦を動かすまで1時間かかってしまいました。赤外リモコンは赤外LEDを38kHzで点滅させ、その38kHzをON/OFFすることで送信パターンをつくるのですが、その38kHzのON/OFFをArduinoでこんなふうに書きました(※私はArduinoさわるの初めてです)

void IR_ON() {
  tone(ir_led, ir_freq);
  digitalWrite(indicate_led, HIGH);
}

void IR_OFF() {
  noTone(ir_led);
  digitalWrite(ir_led, HIGH);
  digitalWrite(indicate_led, LOW);
}

void IR_0() {
  IR_ON();
  delayMicroseconds(400);
  IR_OFF();
  delayMicroseconds(1200);
}

void IR_1() {
  IR_ON();
  delayMicroseconds(1200);
  IR_OFF();
  delayMicroseconds(400);
}

するとどうもパルス幅がdelayMicrosecondsの値通りにならないのです。delay以外の関数呼び出しなどのオーバーヘッドがあるにしてもちょっと許容できない遅れが見られます(場合によっては数百μSec程度遅れている)。うーん、こういうとき生マイコンでポート叩いているとどこに時間がかかっているのかさすがにすぐわかるのですが、Arduinoは初めてなのでよくわからないです。しかたないので、オシロで波形を見ながら希望するタイミングになるようにdelayMicrosecondsの引数を適当に調整することにしました。気持ち悪いのですが(本来はちゃんとボトルネックを確認したうえで実時間処理できていることを保証したい)時間がないし仕方ない。今回初めてArduinoを触ってみて一番引っかかったのがここで、あとはびっくりするくらい簡単でした。こりゃ流行るわ。うん。(ようやく理解した)


遊んでいるうちに「同時押し」のコードを送ろうとしたら潜水艦本体側で受信エラーになってしまったので、あらためて調べてみました。なるほど最初の1はビットカウントだったんですね。ここから制御部分を改修し、UI(Leap Motionによるコントロール)をつくっていきます。

Leap Motionによる『ハンドパワー』制御は正直見せ球というか見た目を派手にするための最後の一押しでしかない(これが本当に使いやすいと思って作っていない)ので「動けばいいや」とLeap Motion v2のサンプルプロジェクトを土台にさっくり仕上げました。細かいジャスチャーや使いやすさ追求を考えるといろいろ調整したりやらなければいけないことはあるのですが、まあ、ハッカソンの成果としてはこんなもんで!

…というわけで、冒頭の動画のように操作できるようになりました。ハッカソン終了までに間に合った!

最後に、参考までに今回書いたArduinoのスケッチを置いておきます。赤外LEDを3番と+5V間に接続してつかいます(3番=LoでLED点灯)。
#define SUBMARINE_UP       (0x01)
#define SUBMARINE_DOWN     (0x02)
#define SUBMARINE_FRONT    (0x04)
#define SUBMARINE_BACK     (0x08)
#define SUBMARINE_LEFT     (0x10)
#define SUBMARINE_RIGHT    (0x20)

int ir_led = 3;
int indicate_led = 9;
int ir_freq = 38000;

char submarine_mode = 0;

void setup() {                
  pinMode(ir_led, OUTPUT);     
  pinMode(indicate_led, OUTPUT);
  Serial.begin(9600);
  submarine_mode = 0;
}

// the loop routine runs over and over again forever:
void loop() {
  CheckSerial();
  IR_SendMode();
}


void IR_ON() {
  tone(ir_led, ir_freq);
  digitalWrite(indicate_led, HIGH);
}

void IR_OFF() {
  noTone(ir_led);
  digitalWrite(ir_led, HIGH);
  digitalWrite(indicate_led, LOW);
}

void IR_0() {
  IR_ON();
  delayMicroseconds(200);
  IR_OFF();
  delayMicroseconds(1100);
}

void IR_1() {
  IR_ON();
  delayMicroseconds(600);
  IR_OFF();
  delayMicroseconds(400);
}

void IR_Send(char ch) {
  int i;
  for(i=0;i<7;i++) {
    if ((ch & 0x40)==0) {
      IR_0();
    } else {
      IR_1();
    }
    ch = ch << 1;
  }
}

void IR_SendPacket(char ch1,char ch2) {
  IR_Send(ch1);
  IR_Send(ch2);
  delayMicroseconds(7500);
  IR_Send(ch1);
  IR_Send(ch2);
  delay(128);
}

void CheckSerial() {
  char ch;
 
  while(Serial.available()>0) {
    ch = Serial.read();
    switch(ch) {
      case 'u':
        submarine_mode |= SUBMARINE_UP;
        break;
      case 'd':
        submarine_mode |= SUBMARINE_DOWN;
        break;
      case 'f':
        submarine_mode |= SUBMARINE_FRONT;
        break;
      case 'b':
        submarine_mode |= SUBMARINE_BACK;
        break;
      case 'l':
        submarine_mode |= SUBMARINE_LEFT;
        break;
      case 'r':
        submarine_mode |= SUBMARINE_RIGHT;
        break;
      default:
        submarine_mode = 0;
    }
  }
}

void IR_SendMode() {
  unsigned char b8 = (unsigned char)submarine_mode;
  b8 = (unsigned char)( ((b8 & 0xAA) >> 1) + (b8 & 0x55) );
  b8 = (unsigned char)( ((b8 & 0xCC) >> 2) + (b8 & 0x33) );
  b8 = (unsigned char)( ((b8 & 0xF0) >> 4) + (b8 & 0x0F) );

  IR_SendPacket(b8<<1,submarine_mode);
}

Arduinoはじめてなのでなんかこれでいいのかもよくわかっていないのですが(少なくともパルスのタイミングのあたりはとても気持ち悪い)、まあ動いているので参考までに。

で、「できたー!まにあったー!」という喜びのまま成果発表したわけですが…


このハッカソンの成果、Arduinoはあるわ水槽はあるわLeap Motionはあるわ(外部モニタはぶら下げてるわ)で結構デモ環境が大掛かりなのです。
成果発表後少しいろいろな人にデモし、その後片付けに手間取っている間にハッカソン後のパーティタイムがあらかた終わってしまいピザやら寿司やらに全くありつけない事態になるとはまったく想像していなかったのでした。とほほー。