ゆるく楽しむプログラミング

Wii Classic Controller

Wiiリモコンのオプションのヌンチャク/クラッシクコントローラ/モーションセンサーはI2C接続のようです。
ヌンチャクをArduinoで使うスケッチを参考にクラッシクコントローラをArduino/RaspberryPi Picoで使える
WiiControllerクラスを作ってみました。

Wii Classic Controllerの接続

ヌンチャクと同じコネクタなので同じピオン配置の筈です。
ヌンチャクの接続図から、こうなっていると思われます。

※RaspberryPi Picoはmbedベースのボードライブラリを使用しています。


Wii Classic Controllerの解析

I2C_Scannerではアドレスを取得できませんでしたので、アドレスと初期化手順(?)はヌンチャクのスケッチから丸パクリしました。
アナログスティック2本とアナログボタン2個なので、少なくとも6個のアナログデータと、ボタンの数分の15ビットのデータが送られている筈です。
ヌンチャクのスケッチを真似て6バイトを受信、ビット列を表示した状態でスティックを操作し、変化したビットを並べ替えてスティックとデータの対応を調べました。

その結果得たデータフォーマットが▼です。


ヌンチャクのスケッチベースでArduinoでは問題なく動作しましたが、RaspberryPi Picoでは意味不明なデータが入りました。
意味不明なデータを観察してみると、定期的に0が入っている事が分かりましたので、受信データを6バイトから8バイトに変更。
初期化コマンド(?)の送信タイミングにも問題があるようなのでI2Cアドレスにダミーアクセスを入れて、バスの利用可否を確認するように修正する事でRaspberryPi Picoでも動作するようになりました。

-- Wii Classic Controller データ読み取りスケッチ --
WiiController.zip ▼下のリストのZIPファイル:読み込んだデータをシリアルモニタに表示しています。
WiiControllerDemo.zip 参考動画で使用している有機発光ダイオード SSD1306にコントローラの状態を表示するスケッチ
 ※adafruteのSSD1306ライブラリ(ArduinoIDEのライブラリ管理から検索してインストールできます)を使用しています。

※ <>を全角<>に置き換えています。コピペの際は注意してください。
--- WiiClassicController.h
/*
 * Wii Classic Controller for Arduino 
 * 
 */
#ifndef WiiClassicController_H
#define WiiClassicController_H

#include <Arduino.h>

class WiiClassicController
{
  public:
    uint8_t analogLX; // Left Side Anarog Stick X  - 6Bit
    uint8_t analogLY; // Left Side Anarog Stick Y  - 6Bit
    uint8_t analogRX; // Right Side Anarog Stick X - 5Bit
    uint8_t analogRY; // Right Side Anarog Stick Y - 5Bit
    uint8_t analogLB; // Left Side Anarog Buttob   - 5Bit
    uint8_t analogRB; // Right Side Anarog Button  - 5Bit
    uint16_t pushButton ;

    void init();
    void scan();

    inline bool isLeft() { return pushButton & 0b0000001000000000 ;}  // PAD LEFT
    inline bool isRight(){ return pushButton & 0b0000000010000000 ;}  // PAD RIGHT
    inline bool isUp()   { return pushButton & 0b0000000100000000 ;}  // PAD UP
    inline bool isDown() { return pushButton & 0b0000000001000000 ;}  // PAD DOWN
    inline bool isLBtn() { return pushButton & 0b0000000000100000 ;}  // BUTTON L
    inline bool isRBtn() { return pushButton & 0b0000000000000010 ;}  // BUTTON R
    inline bool isZLBtn(){ return pushButton & 0b1000000000000000 ;}  // BUTTON ZL
    inline bool isZRBtn(){ return pushButton & 0b0000010000000000 ;}  // BUTTON ZR  
    inline bool isSelectBtn(){ return pushButton & 0b0000000000010000 ;} // BUTTON -
    inline bool isHomeBtn()  { return pushButton & 0b0000000000001000 ;} // BUTTON HOME
    inline bool isStartBtn() { return pushButton & 0b0000000000000100 ;} // BUTTON +
    inline bool isXBtn() { return pushButton & 0b0000100000000000 ;} // BUTTON X
    inline bool isYBtn() { return pushButton & 0b0010000000000000 ;} // BUTTON Y
    inline bool isABtn() { return pushButton & 0b0001000000000000 ;} // BUTTON a
    inline bool isBBtn() { return pushButton & 0b0100000000000000 ;} // BUTTON b
  private:
    uint8_t values[8] ;

    void writeReadyCheck(); 
    void sendByte(byte data, byte location);
};
#endif
※ <>を全角<>に置き換えています。コピペの際は注意してください。
--- WiiClassicController.cpp
/*
 * Wii Classic Controller for Arduino 
 * 
 * Copyright 2021 try3dcg@nifty.com, http://try3dcg.world.coocan.jp/
 *
 */

#include <Arduino.h>
#include <Wire.h>
#include "WiiClassicController.h"

#define CONTROLLER_ADDRESS 0x52

// Init I2C Interface
void WiiClassicController::init() {
  Wire.begin();

  sendByte(0x55, 0xF0);
  sendByte(0x00, 0xFB);

  scan();
}

// SCAN Wii Classic Controller Status
void WiiClassicController::scan() {
  // --- I2C Interface ---
  int count = 0;
  int readLen = Wire.requestFrom(CONTROLLER_ADDRESS, 8);
  while(Wire.available()) {
    values[count] = Wire.read();
    count++;
  }
  sendByte(0x00, 0x00);

  // --- DECODE RECIVE DATA ---
  if (readLen == 8 && count == 8) {
    // Analog data
    analogLX = values[0] & 0b00111111 ;
    analogLY = values[1] & 0b00111111 ;

    analogRX = ((values[0] & 0b11000000) >> 3) | (( values[1] & 0b11000000) >> 5) | (( values[2] & 0b10000000) >> 7);
    analogRY = values[2] & 0b00011111;
  
    analogRB=  values[3] & 0b00011111 ;
    analogLB =  ((values[2] & 0b01100000) >> 2) | ((values[3] & 0b11100000) >> 5)  ;
  
    // Digital data
    ((uint8_t *)&pushButton)[0] = ~values[4] ; // Reverse Logic
    ((uint8_t *)&pushButton)[1] = ~values[5] ;
  }
}

// Wait to I2C Ready
void WiiClassicController::writeReadyCheck() {
  int errCount = 0 ;
  while(errCount < 100) {
    Wire.beginTransmission(CONTROLLER_ADDRESS);
    int ready = Wire.endTransmission(); // Check endTransmission is Nomal end.
    if (ready == 0) {
      break ;
    }
    errCount ++ ;
    delay(10) ;
    break;
  }
}

// SEND BYTE FOR Wii Classic Conrtroller
void WiiClassicController::sendByte(byte data, byte location)
{
  writeReadyCheck() ;

  Wire.beginTransmission(CONTROLLER_ADDRESS);
  Wire.write(location);
  Wire.write(data);
  Wire.endTransmission();

  delay(10);
}
※ <>を全角<>に置き換えています。コピペの際は注意してください。
--- WiiController.ino
#include <Wire.h>
#include "WiiClassicController.h"

#define BAUDRATE 9600
char str[128] ;

uint8_t RX,RY,LX,LY,BL,BR ;
uint16_t BTN ;

WiiClassicController wiiCC ;

void setup() {
  Serial.begin(BAUDRATE);

  wiiCC.init( ) ;
}

void loop() {
  wiiCC.scan( ) ;
  if (LX != wiiCC.analogLX || LY != wiiCC.analogLY || 
      RX != wiiCC.analogRY || RY != wiiCC.analogRY || 
      BL != wiiCC.analogLB || BR != wiiCC.analogLB || BTN != wiiCC.pushButton) {
    LX = wiiCC.analogLX ;
    LY = wiiCC.analogLY ; 
    RX = wiiCC.analogRX ;
    RY = wiiCC.analogRY ;
    BL = wiiCC.analogLB ;
    BR = wiiCC.analogRB ;
    BTN = wiiCC.pushButton ;

    sprintf(str,"L:%3d,%3d R:%3d,%3d B:%3d,%3d B:%04X ",LX,LY,RX,RY,BL,BR,BTN) ;
    Serial.print(str);

    if (wiiCC.isLeft()) { Serial.print("← "); }
    if (wiiCC.isRight()){ Serial.print("→ "); }
    if (wiiCC.isUp())   { Serial.print("↑ "); }
    if (wiiCC.isDown()) { Serial.print("↓ "); }
    if (wiiCC.isLBtn()) { Serial.print("LB "); }
    if (wiiCC.isRBtn()) { Serial.print("RB "); }
    if (wiiCC.isZLBtn()) { Serial.print("ZL "); }
    if (wiiCC.isZRBtn()) { Serial.print("ZR "); }
    if (wiiCC.isSelectBtn()) { Serial.print("- "); }
    if (wiiCC.isHomeBtn()) { Serial.print("HOME "); }
    if (wiiCC.isStartBtn()) { Serial.print("+ "); }
    if (wiiCC.isXBtn()) { Serial.print("X "); }
    if (wiiCC.isYBtn()) { Serial.print("Y "); }
    if (wiiCC.isABtn()) { Serial.print("a "); }
    if (wiiCC.isBBtn()) { Serial.print("b "); }
    Serial.println() ;
  }
}


解説動画