8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Wii Nunchuckを無線化してモバイルVRに生かす

Last updated at Posted at 2019-07-02

概要

手軽なモバイルVRには一つの欠点がある。
スマホをセットするとスマホが操作できなくなると言う点だ。
これ何とかしたい。
しかも安く何とかしたい。

そこでハードオフで100円のWiiヌンチャクをBTマウスに改造することにした。

出来上がったもの。

finish_wiinunchuck_wireless.jpg

自分用なので細かいことは気にしない主義

Githubにアップしました!
wii-nunchuck-esp32

  • 準備するもの
    • Wii ヌンチャク(白)
    • ESP32
    • USBシリアル変換基盤
    • RaspberryPi3
    • 電子工作の道具たち
      • 熱収縮チューブ
      • 抵抗
      • ユニバ基盤
      • 配線

手順

  • Wiiヌンチャクがi2c通信で使える事を確認する
  • ESP32をブレイクアウトする
  • ESP32にi2c + bluetoothのソフトを書き込む

重要な点

Wiiヌンチャクは白を利用してます!

もう一回書いておく。

Wiiヌンチャクは白を利用してます!

Wiiヌンチャクがi2c通信で使える事を確認する

WiiヌンチャクをHardOffなどで購入する。100円だった。安い。
赤テープは動作未確認のものらしいが壊れていたらもう一つ買うだけなので気にしない。

とりあえずケーブルを切って通信線を出す。

意味
VCC
SDA
SCL
GND
謎。そこはかとなくGNDっぽいけど謎。Wiiヌンチャク側のみに存在する。アース?

wiinunchuck_terminate.jpg

こんな感じ

RaspberryPi3と接続してi2c通信でデータが取れるかを確認する。
接続としては以下の通り

Wii Nunchuck RaspberryPi 3.3v電源
Pin No.3 - SDA 10kΩ通して3.3v入れる
Pin No.5 - SCL 10kΩ通して3.3v入れる
Pin No.9 - GND

RaspberryPiで下記プログラムを実行

main.py
from smbus2 import SMBusWrapper, i2c_msg
import time

def init_wii_nunchuck(i2c, addr):
    print("start init_wii_nunchuck")
    data = [0x40, 0x00]
    msg = i2c_msg.write(addr, data)
    i2c.i2c_rdwr(msg)
    time.sleep(0.5)

def read_wii_nunchuck(i2c, addr):
    print("start read_wii_nunchuck")
    wiiDataReader = i2c_msg.read(addr, 6)
    i2c.i2c_rdwr(wiiDataReader)
    controllerData = list(wiiDataReader)

    joystic_x = controllerData[0]
    joystic_y = controllerData[1]
    buttons = controllerData[5]
    c_button = (buttons >> 1) & 0x01
    z_button = buttons & 0x01
    time.sleep(0.2)
    print("joystic: (x, y) = ({}, {})".format(joystic_x, joystic_y))
    print("button c: {}".format(c_button))
    print("button z: {}".format(z_button))

    terminatorWriter = i2c_msg.write(addr, [0x00])
    i2c.i2c_rdwr(terminatorWriter)
    print("finish read_wii_nunchuck")

def main():
  wii_nunchuck_addr = 0x52
  try:
    # with SMBusWrapper(1) as i2c:
    with SMBusWrapper(1) as i2c:
      init_wii_nunchuck(i2c, wii_nunchuck_addr)
      while(True):
        time.sleep(0.5)
        try:
          read_wii_nunchuck(i2c, wii_nunchuck_addr)
        except OSError as e:
          print("read_wii_nunchuck error: {}".format(e))
          break
  except OSError as e:
    print("init_wii_nunchuck error: {}".format(e))
  except KeyboardInterrupt:
    return
  except Exception as e:
    print("initial_wii_nunchuck error")
    print(e)

if __name__ == "__main__":
  main()
sudo pip3 install smbus2
sudo python3 ./main.py

Wiiヌンチャクを動かしてみてデータがちょこちょこ変わってればOK

image.png

ESP32をブレイクアウトする

esp322.jpg

ESP32を知らない方にざっとESP32を説明すると、
700円ぐらいで買える色々できる奴だ。

そしてブレイクアウトを知らない方にざっとブレイクアウトを説明すると、
マイコンを使えるようにする作業だ。

つまり、ESP32を使える様にする作業だ。
ここで細かく書いてもいいのだが、面倒なので表だけ書く。
気が向いたら回路図書く。

no ESP32 pin 用途
1 1 - GND GND
2 2 - 3V3 3.3V。100uFぐらいのコンデンサ入れる。
3 3 - EN プルアップ。swなりでGNDに落とせるようにしとく。
4 25 - IO0 swなりでGNDに落とせるようにしておく
5 33 - SDA I2C用のSDA。プルアップ。
6 34 - RXD0 ESP32書き込み用のUSBシリアル変換基盤TXにつなぐ。
7 35 - TXD0 ESP32書き込み用のUSBシリアル変換基盤RXにつなぐ。
7 36 - SCL I2C用のSCL。プルアップ。

ESP32にi2c + bluetoothのソフトを書き込む

ラズパイで書き込み環境をセットアップする。
ESP-IDF Programming Guideにそって環境構築する。
環境構築で詰まることは特になかったので割愛。
開発環境が出来たらexampleとか一通り眺める。

Wiiヌンチャク(白)からi2c経由で情報を取得するには下記手順を踏む

  • 最初
    • 0x40,0x00を書き込む
  • 読み込み
    • 6バイト読む
    • 0x00を書き込む

うん。書いててもさっぱりわからないのでコード貼る。けど、全体だと長いので、部分的に説明する。
時間があったらGithubに上げる。
尚、BlueToothデバイスを作成する際には機器名を指定しておいた方が良い。似たのがあると混乱する(した)。
プログラムの中で指定できる。

これがメインタスクの初回で一回だけ呼ばれる処理。0x40,0x00を書き込む。

static esp_err_t initialize_wii_nunchuck(i2c_port_t i2c_num)
{
    ESP_LOGI(WII_NUNCHUCK_TAG, "initialize_wii_nunchuck");
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, 0x40, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

6バイト読み込み

static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t *data_rd, size_t size)
{
    esp_err_t result = ESP_OK;
    ESP_LOGI(WII_NUNCHUCK_TAG, "i2c_master_read_slave");
    if (size == 0) {
        return ESP_OK;
    }
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | READ_BIT, ACK_CHECK_EN);
    if (size > 1) {
        i2c_master_read(cmd, data_rd, size - 1, ACK_VAL);
    }
    i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    if (ret == ESP_OK) {
        ESP_LOGI(WII_NUNCHUCK_TAG, "i2c_master_read_slave data reading succeed: %d", ret);
    } else {
        result = ESP_ERR_TIMEOUT;
        ESP_LOGE(WII_NUNCHUCK_TAG, "i2c_master_read_slave data reading failure: %d", ret);
    }
    return result;
}

読み込み後に行う0x00の書き込み

static esp_err_t i2c_master_write_slave_terminator(i2c_port_t i2c_num)
{
    esp_err_t result = ESP_OK;
    i2c_cmd_handle_t writeCommand = i2c_cmd_link_create();
    i2c_master_start(writeCommand);
    i2c_master_write_byte(writeCommand, (ESP_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);
    i2c_master_write_byte(writeCommand, 0x00, ACK_CHECK_EN);
    i2c_master_stop(writeCommand);
    esp_err_t writeResult = i2c_master_cmd_begin(i2c_num, writeCommand, 1000 / portTICK_RATE_MS);
    if (writeResult == ESP_OK) {
        ESP_LOGI(WII_NUNCHUCK_TAG, "i2c_master_write_slave_terminator send terminator succeed: %d", writeResult);
    } else {
        result = ESP_ERR_TIMEOUT;
        ESP_LOGE(WII_NUNCHUCK_TAG, "i2c_master_write_slave_terminator send terminator failure: %d", writeResult);
    }
    i2c_cmd_link_delete(writeCommand);
    return result;
}

取ったデータをマウスデータとして送る

int8_t mickeys_x = 0; // Joystick - X
ESP_LOGI(WII_NUNCHUCK_TAG, "send mouse (%d, %d)", data_rd[0], data_rd[1]);
if (data_rd[0] > 160) {
    mickeys_x = 50;
}
if (data_rd[0] < 100) {
    mickeys_x = -50;
}
int8_t mickeys_y = 0; // Joystick - Y
if (data_rd[1] > 160) {
    mickeys_y = -50;
}
if (data_rd[1] < 100) {
    mickeys_y = 50;
}
uint8_t mouse_button = 0x00;
if (c_button_state == 0x02 && (data_rd[5] & 0x02) == 0x00) {
    mouse_button = mouse_button | 0x01;
} else if (c_button_state == 0x00 && (data_rd[5] & 0x02) == 0x02) {
    mouse_button = mouse_button & 0xfe;
}
if (z_button_state == 0x01 && (data_rd[5] & 0x01) == 0x00) {
    mouse_button = mouse_button | 0x02;
} else if (z_button_state == 0x00 && (data_rd[5] & 0x01) == 0x01) {
    mouse_button = mouse_button & 0xfd;
}
c_button_state = data_rd[5] & 0x02;
z_button_state = data_rd[5] & 0x01;

ESP_LOGI(WII_NUNCHUCK_TAG, "Send the mouse event");
// create mouse event
esp_hidd_send_mouse_value(hid_conn_id, mouse_button, mickeys_x, mickeys_y);

書き込み用プログラムができたら、書き込み。(EN, IO0のスイッチ設定変更を忘れずに行う)

make menuconfig
make flash

成功したら、UARTで情報を表示する。(EN, IO0のスイッチ設定変更を忘れずに行う)

main.py
import serial
import time
import threading
import Queue

class SerCom:
  def __init__(self, tty, baud='115200'):
    self.ser = serial.Serial(tty, baud, timeout=0.1)
    self.queue = Queue.Queue()

    self.event = threading.Event()
    self.thread_r = threading.Thread(target=self.recv_)
    self.thread_r.start()

  def recv_(self):
    while not self.event.is_set():
      line = self.ser.readline()
      if len(line) > 0:
        print(line)
        self.queue.put(line)

  def send(self, data):
    self.ser.write(data)

  def stop(self):
    self.event.set()
    self.thread_r.join()

ser = SerCom('/dev/ttyUSB0', '115200')
try:
  while True:
    time.sleep(5)
except:
  ser.stop()

で実行するとこんな感じ

image.png

振り返り

なんだかんだ大変だった。何せ情報がない。

ArduinoとかRaspberryPiとかは結構記事とかチャレンジしている人とかはいる。
さらにWiiヌンチャクを○○してみた系の記事もある。
が、ESP32をESP-IDFで扱うと言う記事自体が割と少ない。
さらにそこからI2Cを使うとかBlueToothマウスを作るとかになるともっと少ない。

何よりみんな成功例しかださないから、あってるかどうか判断つかない。
極端な話、ログの出力方法はあるけど、ログの見方がわかんないって感じだ。

そんなこんなで2回失敗して、3回目でできた。あきらめない事が重要。
自分の作ったコントローラで自分のスマホを操作できたとき、よっしゃ!って声出た。

image.png

レベルアップの瞬間

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?