2013年2月11日

iPodをArduinoでコントロール

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

コントロールと言っても音楽やメディア(YouTube、ストリーミング等)の再生停止、送り戻し、音量の調節くらいですが、今更ながらDockコネクタ経由でやってみたり。
#時代はLightningとか言わない
一応、iOSデバイス(iPhone/iPad)も制御できますが、あくまでもメディア(音楽・動画)のコントロールのみなのでタイトルを「iPod」としました。

使用したのはArduino Pro mini 3.3V、スイッチ、抵抗(1kΩ*3、1MΩ*1)、Dockコネクタのbreakoutボードの4つ。
5V系を使う場合はTXからの出力を抵抗分圧等で3.3VにレベルシフトしてあげればOK。
(ただし、Arduinoへの電源供給は別途しなければならない)


とりあえず、再生停止、曲送り、曲戻しの3ボタンを用意。
Dockコネクタとの接続はこちら(http://pinouts.ru/CellularPhones-A-N/iPhone_connector_pinout.shtml)を参考に、ArduinoのTXをDockコネクタの13ピン、VCCを18ピン、GNDを11ピンに。
Dockコネクタの21ピン(Accessory Indicator)は1MΩ経由でGNDに接続しました。(500kΩでも問題なし)

iPod(iPhone/iPad含む)のシリアル通信に関しては既に調べ尽くされているので、その情報を参考にしました。
https://instruct1.cit.cornell.edu/courses/ee476/FinalProjects/s2007/awr8_asl45/awr8_asl45/index.html
http://web.student.tuwien.ac.at/~e0026607/ipod_remote/ipod_ap.html

シリアル通信には複数のモードがあり、最も簡単な制御モード(iPodへの一方行通信)がmode2、曲名やアルバム名なども取得できる制御モード(iPodとの双方向通信)がmode4となっている模様。
接続直後はmode0となっているので、そこにmode2への切り替えコードを叩いてあげるとmode2の制御コマンドを受け付けるようになってくれます。
と言うわけで、プログラムは上記ページを参考にしながら以下の様な感じで。
#include <Bounce.h>

#define BUTTON1_PIN 3
#define BUTTON2_PIN 4
#define BUTTON3_PIN 5

boolean state[3];

Bounce button1 = Bounce( BUTTON1_PIN,20 );
Bounce button2 = Bounce( BUTTON2_PIN,20 );
Bounce button3 = Bounce( BUTTON3_PIN,20 );

byte mode2[] =      {
  0xFF, 0x55, 0x03, 0x00, 0x01, 0x02, 0xFA};
byte playStop[] =   {
  0xFF, 0x55, 0x03, 0x02, 0x00, 0x01, 0xFA};
byte prevTrack[] =   {
  0xFF, 0x55, 0x03, 0x02, 0x00, 0x10, 0xEB};
byte nextTrack[] =   {
  0xFF, 0x55, 0x03, 0x02, 0x00, 0x08, 0xF3};
byte buttonReleased[] = {
  0xFF, 0x55, 0x03, 0x02, 0x00, 0x00, 0xFB};

void setup() {
  pinMode(BUTTON1_PIN, INPUT);
  pinMode(BUTTON2_PIN, INPUT);
  pinMode(BUTTON3_PIN, INPUT);
  Serial.begin(19200);
  for (int i = 0; i < 7; i++) {
    Serial.write(mode2[i]);
  }
}

void loop() {
  button1.update();
  if ((button1.read() == HIGH)&&(state[0] == 0)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(playStop[i]);
    }
    state[0] = 1;
  }
  if ((button1.read() == LOW)&&(state[0] == 1)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(buttonReleased[i]);
    }
    state[0] = 0;
  }
  button2.update();
  if ((button2.read() == HIGH)&&(state[1] == 0)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(prevTrack[i]);
    }
    state[1] = 1;
  }
  if ((button2.read() == LOW)&&(state[1] == 1)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(buttonReleased[i]);
    }
    state[1] = 0;
  }
  button3.update();
  if ((button3.read() == HIGH)&&(state[2] == 0)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(nextTrack[i]);
    }
    state[2] = 1;
  }
  if ((button3.read() == LOW)&&(state[2] == 1)) {
    for (int i = 0; i < 7; i++) {
      Serial.write(buttonReleased[i]);
    }
    state[2] = 0;
  }
}
Bounceライブラリを使うとなんか冗長的な感じになってしまうのがアレですが、とりあえず仕組みは理解できたので今回はこれで良しとします。

これで何を作りたいのかと言うと、iOSデバイス上での音楽再生を停止させるスリープタイマーの外部インターフェース版。
普通はiOSデバイス単体でスリープタイマーを設定出来るようになっていますが、そういう設定が面倒くさい人(父)の為に、ボタンを「物理的に」何回か押せば簡単にタイマーが設定出来る装置を作ろうかと。

色々と資料を見ているとmode4も面白そうなので、次はmode4で取得した情報をLCDに表示させたりしてみようと思っているところ。

2013年1月24日

Arduino DUEが届いた

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

供給が追いつかず、どこも品切れ状態が続いているArduino DUE、やっと購入でき、本日届きました。
右の写真は、奥から順にArduino UNO、Arduino MEGA 2560、Arduino DUEの3兄弟(?)

DUEを購入するついでにWireless Proto Shieldも購入。


新しいShieldは、パターンを追ってみるとICSP、VCC&GNDピンの両方から電源を引いてる様なので変な3段重ねをしなくてもよさそう。

2013年1月22日

ウォシュレット実況botの無線化

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

ウォシュレット実況botトイレットペーパー使用量bot を合体して無線化しました。
今まではトイレから直接送信(有線LAN)していましたが、隣の部屋からLANケーブルを引くのも滑稽なのでXBeeで無線化することに。
上の写真はトイレに設置する送信側で、Pro mini 3.3V、スイッチサイエンスのXBeeピッチ変換基板XBee(S2/ZB)、赤外線受信モジュール、ロータリエンコーダ、Li-poで構成。
下の写真は別室に設置する受信側で、Arduino UNOEthernet Shield、XBee Shieldで構成。


古いXBee Shieldは電源がICSPから取ってあるらしく、Ethernet Shieldと3段重ねにするとXBee Shieldに電源供給されません。(Ethernet ShieldにICSPピンヘッダが無い)


この様な細工で電源供給して、ピンのカサ増しの為にピンヘッダを間に入れています。

プログラムは、ウォシュレットbotとトイレットペーパーbotを合体した後、赤外線受信&エンコーダのカウント&シリアル送信部をトイレ設置機に、シリアル受信&Twitterへの投稿部を別室設置機にという具合です。

ちなみに、XBee(S2/ZB)ピッチ変換基板XBee USBアダプタは、電子工作コンテスト2012の はんだづけカフェ賞 でスイッチサイエンスさんから頂いた物です。
#初めてのXBeeがこういう形で使うことになるとは...

2013年1月19日

ふぁぼマウスを作ってみた

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

Sun Microsystems製の3ボタンマウス(Model Type 5)が廃棄されていたので、ボタン部分を活かしてtwitter(web版)を快適に閲覧する為の装置を作ってみました。
※タイトルにふぁぼ"マウス"とありますが、マウス本来の機能(カーソル操作)は出来ません。

このマウス、専用マウスパッドが必要な光学式&接続がMini-DIN3ピンなのでDOS機には使えず、そのままでは歴史的資料価値しかありません。


バラすとこんな感じで、ボタン部にはオムロン製のマイクロスイッチが3つ使用されている。
マイコン・光学周りは用途が微妙だったので、マイクロスイッチのみ元の基板から取り外して、マウスの幅を合わせてカットしたCサイズ基板に移植。


こちらを参考にして、ブレッドボード上でV-USBの回路を組んで動作チェック。
3.6Vのツェナーダイオードが手元に無かったのでVfが3.6Vの青色LEDを使用、68Ωの抵抗もなかったので100Ωの抵抗を使用。
MacBook Air、MacBook Pro Retinaでは問題なく認識したので、たぶん大丈夫のはず...


これをマイクロスイッチを移植した基板に実装。(裏面のはんだモリモリは気にしたら負け)

Arduino IDE 1.0以降に対応したV-USBライブラリをこちらからダウンロード、適宜libraryフォルダに投入。
プログラムはこんな感じで。
#include "UsbKeyboard.h"

#define BUTTON_L 12
#define BUTTON_C 11
#define BUTTON_R 10

int lastState = LOW;
int lastStateC = LOW;
unsigned long pushTime = 0;

void setup() {
  pinMode(BUTTON_L, INPUT);
  pinMode(BUTTON_C, INPUT);
  pinMode(BUTTON_R, INPUT);

  TIMSK0 &= !(1<TOIE0);
  cli();
  usbDeviceDisconnect();
  delayMs(250);
  usbDeviceConnect();
  sei();
}

void loop() {
  UsbKeyboard.update();
  
  //中央ボタンの処理
  if( (digitalRead(BUTTON_C) == HIGH) && (lastStateC==LOW) ) {
    lastStateC = HIGH;
    pushTime = millis(); //長押し計測開始
    delay(70);
  } 
  else if( (digitalRead(BUTTON_C) == LOW) && (lastStateC==HIGH) ) {
    if( (millis() - pushTime) > 500 ){ //500ミリ秒以上長押しの場合
      UsbKeyboard.sendKeyStroke(KEY_T); //Tを送信
      delay(200);
      UsbKeyboard.sendKeyStroke(88); //Enterを送信
      lastStateC = LOW;
    }
    else{
      UsbKeyboard.sendKeyStroke(KEY_F); //Fを送信
      delay(70);
      lastStateC = LOW;
    }
  }

  //左ボタンの処理
  else if( (digitalRead(BUTTON_L) == HIGH) && (lastState==LOW) ) {
    delay(30);
    if( (digitalRead(BUTTON_R) == HIGH) ) { //右ボタンも押されている場合
      UsbKeyboard.sendKeyStroke(55); //ピリオドを送信
      lastState = HIGH;
      delay(70);
    }
    else {
      UsbKeyboard.sendKeyStroke(KEY_J); //Jを送信
      lastState = HIGH;
      delay(70);
    }
  } 
  else if( (digitalRead(BUTTON_L) == LOW) && (lastState==HIGH) ) {
    lastState = LOW;
    delay(100);
  }

  //右ボタンの処理
  else if( (digitalRead(BUTTON_R) == HIGH) && (lastState==LOW) ) {
    delay(30);
    if( (digitalRead(BUTTON_L) == HIGH) ) { //左ボタンも押されている場合
      UsbKeyboard.sendKeyStroke(55); //ピリオドを送信
      lastState = HIGH;
      delay(70);
    }
    else {
      UsbKeyboard.sendKeyStroke(KEY_K); //Kを送信
      lastState = HIGH;
      delay(70);
    }
  } 
  else if( (digitalRead(BUTTON_R) == LOW) && (lastState==HIGH) ) {
    lastState = LOW;
    delay(100);
  }
}

void delayMs(unsigned int ms) {
  for( int i=0; i<ms; i++ ) {
    delayMicroseconds(1000);
  }
}
twitterのショートカットと各ボタンが対応しており、左ボタンで下のツイートへ移動(Jキー)、右ボタンで上のツイートへ移動(Kキー)、左右同時押しで更新(ピリオドキー)、真ん中ボタン1回押しでfav(Fキー)、真ん中ボタン長押し(0.5秒)でRT(Tキー)という感じ。

2013年1月12日

トイレットペーパー使用量報告bot

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

ウォシュレットでスッキリした後はお尻を拭かなければなりません。

という訳で、お家のトイレからプライバシーが吹き飛ぶガジェット第二弾は、Arduino + Ethernet Shield + ロータリエンコーダでトイレットペーパーの使った長さをツイートする装置。(上記写真)

実際のツイートはこんな感じ。
という具合に、長さを±1cmくらいの誤差でつぶやきます。

使用したのはArduino UNO、Ethernet Shield、ロータリエンコーダ(マウスから入手)の3つ。


故障気味のマウスを分解、マウスのホイール部分を取り出します。


これをArduinoに接続。
写真では試験的にMEGA2560を使ってますが、実際はUNOで事足ります。

ロータリエンコーダはA、B、2つのパルスの位相が1/4周期ずれて出力されるので、このパルスをそれぞれカウントすることで回転方向と回転量を導出することが出来る。
このパルスを検出してカウントするためにattachInterruptを使いますが、このとき外部割り込みに使用できるピンはデジタル2番、3番ピン。
エンコーダは、端子が出ている方の面を手前にして左からA、B、GNDとなっている(メーカによって異なる)ので、AをArduinoのデジタル2番ピン、Bを3番ピンに接続します。
更に、ホイールの表面がトイレットペーパーの表面に当たるように、紙巻器に固定します。


長さの計測開始は約1cmからで、計測終了&ツイートはトイレットペーパーの引き出しが終わってから3秒後(3秒間エンコーダが回転しなかったら終了と見なす)としました。

プログラムは、こちらを参考にしつつ、以下のような感じで。
//Copyright 2012 bildr
//Released under the MIT License - Please reuse change and share
//modified by Kei.Y - Additional portions license is CC0

#include <SPI.h>
#include <Ethernet.h>
#include <Twitter.h>

byte mac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //Ethernet ShieldのMACアドレス
byte ip[] = { 192, 168, 1, 123 }; //IPアドレス
Twitter twitter("/* 取得したトークン */");
char msg[256];

int encoderPin1 = 2;
int encoderPin2 = 3;

volatile int lastEncoded = 0;
volatile long encoderValue = 0;

long lastencoderValue = 0;
long encoderValueTemp = 0;
long lastUse = 0;
int stopCount = 0;

int lastMSB = 0;
int lastLSB = 0;

void setup() {
  Ethernet.begin(mac, ip); 
  Serial.begin (9600);

  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);

  digitalWrite(encoderPin1, HIGH);
  digitalWrite(encoderPin2, HIGH);

  attachInterrupt(0, updateEncoder, CHANGE); 
  attachInterrupt(1, updateEncoder, CHANGE);

}

void loop(){ 
  if(encoderValue > lastUse && (encoderValue - lastUse)>5){
    if(encoderValue == encoderValueTemp){
      stopCount++;
      if(stopCount > 3 ){
        sprintf(msg,"おしりを拭きました(´ε`;)フキフキ 長さ%dcm",(int)((encoderValue - lastUse)/5.4));
        postTweet();
        lastUse = encoderValue;
        stopCount = 0;
      }
    }
    encoderValueTemp = encoderValue;
  }
  delay(1000);
}


void updateEncoder(){
  int MSB = digitalRead(encoderPin1);
  int LSB = digitalRead(encoderPin2);

  int encoded = (MSB << 1) |LSB;
  int sum  = (lastEncoded << 2) | encoded;

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded;
}

void postTweet(){
  Serial.println("connecting ...");
  if (twitter.post(msg)) {
    int status = twitter.wait();
    if (status == 200) {
      Serial.println("OK.");
    }
    else {
      Serial.print("failed : code ");
      Serial.println(status);
    }
  } 
  else {
    Serial.println("connection failed.");
  }
}
使用用途として、トイレットペーパー使用量を他人と比較したり、無駄づかい防止・節約の為の意識付けなどがありますが、プライバシーも一緒に流れていく事に変わりはありません。

追記:ウォシュレット実況botと合体させて無線化しました。
ウォシュレット実況botの無線化
http://eleclog.quitsq.com/2013/01/toilet-tweet-bot-wireless.html


追記 2016.01.03 : 実際のツイートを開発用アカウントからメインアカウントのツイートに差し替えしました。