Wii 在多年前是個熱門的遊戲明星產品,透過無線搖桿跟電腦螢幕上的人物進行互動遊戲,當時推出時吸引很多人來玩,就連平常不玩遊戲的我,也買了一部 Wii 遊戲機當作健身用。後來看到有網路上的高手將 Arduino 連接 Wii 的搖桿手把,讀取搖桿的訊號,可以當作控制周邊設備用,這個實作就來看看搖桿有哪些功能,並控制一個伺服馬達 MG996R,下一篇再來實作控制機器手臂。
WiiChuck介面卡是一種小型PCB,主要是能插入 Nintendo Wii Nunchuck的連接器中,用來連接搖桿上的 4 根電線。Nunchuck本身是一個利用I2C進行通訊,只需 4 條線-GND、VDD(3.3v)、SDA 和 SCL。SDA 和 SCL只需連接 Arduini Uno 上對應的 I2C 引腳(A4 和 A5)即可。Wii Nunchuck具有 2 軸操縱桿、兩個按鈕和 3 軸 ±2g 加速度計,任何具有I2C功能的設備都可以與其通信。與WiiChuck連接的引腳與按鍵代碼,請參考下圖:
WiiChuck介面卡是一種小型PCB,主要是能插入 Nintendo Wii Nunchuck的連接器中,用來連接搖桿上的 4 根電線。Nunchuck本身是一個利用I2C進行通訊,只需 4 條線-GND、VDD(3.3v)、SDA 和 SCL。SDA 和 SCL只需連接 Arduini Uno 上對應的 I2C 引腳(A4 和 A5)即可。Wii Nunchuck具有 2 軸操縱桿、兩個按鈕和 3 軸 ±2g 加速度計,任何具有I2C功能的設備都可以與其通信。與WiiChuck連接的引腳與按鍵代碼,請參考下圖:
[材料]
- Arduino Uno x1
- WiiChuck 轉接板 x1
- Wii 擴充搖桿 x1
- 麵包板 x1
- 伺服馬達 MG996R x1
- 18650電池 x2
- 18650電池座 x1
- 可調變壓轉換器 x1
- 排線 x n 條
[接線圖]
Aruino Uno | Wii Nunchuck | MG996R伺服馬達 |
---|---|---|
3V3 | + | N/A |
5V | N/A | 外接5V正極 |
GND | - | 外接5V負極 |
A4(SDA) | D(Data) | N/A |
A5(SCL) | C(Clock) | N/A |
D7 | N/A | 控制Pin |
[程式]
Arduino 和 Nunchunk 通訊時,會先送出一個交互協定的訊號,一開始送 2 個位元「0x40」和「0x00」,每次從 Nunchuck 要資料時,會先送一個 Byte「0x00」,Nunchuck 會回傳一個 6 位元的資料。#include <Wire.h> #include <Servo.h> #define TWI_FREQ 400000L Servo servo1; // 從nunchuck回傳的資料是一個 6位元的值 #define BUFFERSIZE 6 uint8_t outbuf[BUFFERSIZE]; int bufPos = 0; long refreshTime = 1000 / 30; // 更新時間,以為秒計 millisecs long lastTime = 0; void setup() { Serial.begin (57600); Wire.begin (); servo1.attach(7); nunchuck_init (); Serial.println("\r\n"); Serial.print ("Start Wii Nunchuk demo\n"); Serial.print ("joy_x\tjoy_y\tacc_x\tacc_y\tacc_z\tz_but\tc_but\r\n"); } void loop() { long now = millis(); // get the current time // 最後一次取得資料到這次取得的時間大於更新時間 if ( now - lastTime >= refreshTime) { lastTime = now; //儲存現在時間 Wire.requestFrom (0x52, BUFFERSIZE); // request data from the nunchuck // 將收到的資料放到 buffer while (Wire.available () && bufPos < BUFFERSIZE) { outbuf[bufPos] = nunchuk_decode_byte (Wire.read()); bufPos++; } if (bufPos == BUFFERSIZE) { // 收到 buffer 滿了 printNunchuckData(); } // 更新 buffer 位址 bufPos = 0; send_zero(); } } void nunchuck_init() { Wire.beginTransmission (0x52); Wire.write (0x40); Wire.write (0x00); Wire.endTransmission (); } void send_zero() { Wire.beginTransmission (0x52); Wire.write (0x00); Wire.endTransmission (); } void printNunchuckData() { int joy_x_axis = outbuf[0]; int joy_y_axis = outbuf[1]; int accel_x_axis = outbuf[2] * 2 * 2; int accel_y_axis = outbuf[3] * 2 * 2; int accel_z_axis = outbuf[4] * 2 * 2; int z_button = 0; int c_button = 0; // byte outbuf[5] 包含 z 和 c 按鍵的 bits,也包含加速度的值 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; } // 顯示搖桿資料,顯示搖桿上方方向鍵的 X, Y座標 Serial.print(joy_x_axis, DEC); Serial.print("\t"); Serial.print(joy_y_axis, DEC); Serial.print("\t"); // 顯示X,Y,Z三軸的加速度值 Serial.print(accel_x_axis, DEC); Serial.print("\t"); Serial.print(accel_y_axis, DEC); Serial.print("\t"); Serial.print(accel_z_axis, DEC); Serial.print("\t"); // 顯示前方的兩個按鍵狀態 Serial.print(z_button, DEC); Serial.print("\t"); Serial.print(c_button, DEC); Serial.print("\t"); // 使用搖桿 X軸,控制伺服馬達,依照實測的 X 值範圍設定 map 值,轉成介於 0 到 180 的值 int value = map(joy_x_axis, 25, 220, 180, 0); // 顯示傳給 Servo1 的值 Serial.print(value); Serial.print("\r\n"); servo1.write(value); } char nunchuk_decode_byte (char x) { x = (x ^ 0x17) + 0x17; return x; }
張貼留言