Wii ヌンチャクと通信してみた(2)
Wii ヌンチャクのジョイスティックの実験として arduino から LED を制御してみました。また、Wii ヌンチャクの 3 軸加速度センサーの実験として processing アプリ上の 3 次元物体をモーションコントロールする制御を行ってみました。どんな風に動くのかをムービーにしたのでごらんください。
Wii ヌンチャクのハードウェア仕様
Wii ヌンチャクのハードウェア仕様については http://www.wiili.org/index.php/Wiimote/Extension_Controllers/Nunchuk に記載されています. 特徴としては以下です.
- 4pin コネクタ(3.3+V, GND, CLOCK, DATA)
- 通信方法として, メモリマップド I/O or I2C
arduino から Wii ヌンチャクを制御するには I2C を使います. Arduino IDE には I2C 規格の機器を簡単に制御できる Wire ライブラリ が付属しているのでこのライブラリを利用して Wii ヌンチャクを制御することになります. Wire ライブラリ を使用する上でのポイントは以下です.
- Wii ヌンチャクの電源ピンを arduino の 3.3V ピンと接続する
- Wii ヌンチャクの GND ピンを arduino の GND ピンと接続する
- Wii ヌンチャクの DATA ピンを arduino の analog in 4 pin と接続する*1
- Wii ヌンチャクの CLOCK ピンを arduino の analog in 5 pin と接続する*2
I2C 規格の細かいところは wikipedia を参照してください.
arduino のコード
processing で扱いやすいように多少修正していますが http://www.windmeadow.com/node/42?page=1 にあるコードをほぼそのまま流用しました. ポイントは以下です.
#include <Wire.h> #include <string.h> #undef int #include <stdio.h> uint8_t outbuf[6]; // array to store arduino output int cnt = 0; int ledPin = 13; int topLed = 7; int rightLed = 6; int bottomLed = 5; int leftLed = 4; void showDirection() { int x = outbuf[0]; int y = outbuf[1]; if (x < 100) { digitalWrite(leftLed, HIGH); } else if (100 <= x && x < 180) { digitalWrite(leftLed, LOW); digitalWrite(rightLed, LOW); } else { digitalWrite(rightLed, HIGH); } if (y < 100) { digitalWrite(bottomLed, HIGH); } else if (100 <= y && y < 180) { digitalWrite(bottomLed, LOW); digitalWrite(topLed, LOW); } else { digitalWrite(topLed, HIGH); } } void setup () { pinMode(topLed, OUTPUT); pinMode(rightLed, OUTPUT); pinMode(bottomLed, OUTPUT); pinMode(leftLed, OUTPUT); beginSerial (19200); Serial.print ("Finished setup\n"); Wire.begin (); // join i2c bus with address 0x52 nunchuck_init (); // send the initilization handshake } void nunchuck_init () { Wire.beginTransmission (0x52); // transmit to device 0x52 Wire.send (0x40); // sends memory address Wire.send (0x00); // sends sent a zero. Wire.endTransmission (); // stop transmitting } void send_zero () { Wire.beginTransmission (0x52); // transmit to device 0x52 Wire.send (0x00); // sends one byte Wire.endTransmission (); // stop transmitting } void loop () { Wire.requestFrom (0x52, 6); // request data from nunchuck while (Wire.available ()) { outbuf[cnt] = nunchuk_decode_byte (Wire.receive ()); // receive byte as an integer cnt++; } // If we recieved the 6 bytes, then go print them if (cnt >= 5) { print (); showDirection(); } cnt = 0; send_zero (); // send the request for next bytes delay (50); } // Print the input data we have recieved // accel data is 10 bits long // so we read 8 bits, then we have to add // on the last 2 bits. That is why I // multiply them by 2 * 2 void print () { int joy_x_axis = outbuf[0]; int joy_y_axis = outbuf[1]; int accel_x_axis = outbuf[2]; int accel_y_axis = outbuf[3]; int accel_z_axis = outbuf[4]; int z_button = 0; int c_button = 0; // byte outbuf[5] contains bits for z and c buttons // it also contains the least significant bits for the accelerometer data // so we have to check each bit of byte outbuf[5] if ((outbuf[5] >> 0) & 1) z_button = 1; if ((outbuf[5] >> 1) & 1) c_button = 1; if ((outbuf[5] >> 2) & 1) accel_x_axis += 2; if ((outbuf[5] >> 3) & 1) accel_x_axis += 1; if ((outbuf[5] >> 4) & 1) accel_y_axis += 2; if ((outbuf[5] >> 5) & 1) accel_y_axis += 1; if ((outbuf[5] >> 6) & 1) accel_z_axis += 2; if ((outbuf[5] >> 7) & 1) accel_z_axis += 1; Serial.print (joy_x_axis, DEC); Serial.print (","); Serial.print (joy_y_axis, DEC); Serial.print (","); Serial.print (accel_x_axis, DEC); Serial.print (","); Serial.print (accel_y_axis, DEC); Serial.print (","); Serial.print (accel_z_axis, DEC); Serial.print (","); Serial.print (z_button, DEC); Serial.print (","); Serial.print (c_button, DEC); Serial.print("\r\n"); } // Encode data to format that most wiimote drivers except // only needed if you use one of the regular wiimote drivers char nunchuk_decode_byte (char x) { x = (x ^ 0x17) + 0x17; return x; }
processing のコード
processing 1.0.1 に付属している Perspective というサンプルアプリを流用して以下の変更を加えています.
- arduino から送信されたデータを受信するようにシリアルイベントをハンドリング
- 流用元のサンプルアプリではマウスの X/Y 位置を元に画面内の物体を動かしていたコードを Wii ヌンチャクの 3 軸加速度センサーの X/Y 値を元に画面内の物体を動かすように修正
import processing.serial.*; int lf = 10; Serial port; float x = 0.0; float y = 0.0; void setup() { size(640, 360, P3D); println(Serial.list()); port = new Serial(this, Serial.list()[1], 19200); port.bufferUntil(lf); background(0); noStroke(); } void draw() { lights(); background(0); float cameraY = height/2.0; float fov = x/float(width) * PI/2; float cameraZ = cameraY / tan(fov / 2.0); float aspect = float(width)/float(height); if (mousePressed) { aspect = aspect / 2.0; } perspective(fov, aspect, cameraZ/10.0, cameraZ*10.0); translate(width/2+30, height/2, 0); rotateX(-PI/6); rotateY(PI/3 + y/float(height) * PI); box(45); translate(0, 0, -50); box(30); } void serialEvent(Serial p) { String myString = p.readStringUntil(lf); if (myString != null) { int values[] = int(split(trim(myString), ',')); if (values.length < 6) return; // 立ち上がり時の不安定なデータは捨てる inspect(values); x = map(values[2], 50, 210, 0, width); y = map(values[3], 70, 170, 0, height); // format of received data // 0: joy x // 1: joy y // 2: accel x // 3: accel y // 4: accel z // 5: button z // 6: button c } } void inspect(int[] values) { for (int i = 0; i < values.length; i++) print(i + ": " + values[i] + "\t"); println(); }
まとめと課題
今回の実験のまとめです.
- Wii ヌンチャクを制御するには I2C を使う
- arduino で I2C 機器を制御するには Wire ライブラリ を使う
実際に動かしてみると, Wii ヌンチャクの 3 軸加速度センサーの値が静止状態においても微妙にふらつくのがわかりました. 今後はこのふらつきをいい感じに制御するのが課題になります.