Xbox 360ワイヤレスコントローラーアダプターを自作する

DIY Xbox 360 Wireless Controller Adapter
Xbox360のワイヤレスコントローラーは受信機を使えばパソコンでも使えるのですが、互換品でもそこそこの値段がします。ですが、電子工作の知識があればアダプターを格安で自作することができます。

まずはXbox360本体を用意します。ジャンク品であればフリマサイトなどで1000円ほど(送料含まず)で買えるでしょう。ハードオフ系のリサイクルショップでは500円で売っていることもあります。
上蓋、下蓋の爪を剥がし、徐々に分解してきます。
電源ボタンの基板を引き抜きます。ここに無線コントローラーの受信機が組み込まれています。
コネクターが邪魔だと思うのであれば、取り除くのも良いでしょう。
続いてUSBケーブルを結線します。100円均一のUSBケーブルの端子を剥いてはんだ付けするのですが、気をつけたいのが電源。基板が受け入れることのできる電圧は3.3Vため、5Vの電源を直付けするとすぐに壊れます。
ここでは3.3vツェナーダイオード(1N4728A)をかませることで減圧しています。より安定した電圧を期待したいのであれば、レギュレーターを使うのも良いでしょう。
下図のように、USB端子を引き出すことさえできれば、とりあえずパソコンでデバイスとして認識することはできます。が、これだけでは実用的ではありません。コントローラーをレシーバーとペアリングする手段がないからです。
制御基板にはシリアル通信を行うためのピンがあり、ここに電気信号を送ることで、電源ボタンランプの制御や、ペアリングモード開始の指示を実行できます(信号の詳細はこちらのWikiを参照)。
それでは、基板の電源ボタンをペアリングボタン代わりにして、開始信号を送るためのマイコンを付け足してみることにしましょう。ここではマイコンにArduino Pro Miniの互換品を使っていますが、プログラムはそこまで複雑ではないので、ATMega328Pの搭載されたArduino UNOでもかまいません。

電源ピンをArduinoのD2ピンに、その電源ピンの隣のピンをD3ピンに、5つあるうちの中央のピンをD4ピンに接続します。また、10kΩのプルアップ抵抗をシリアルピンに追加しておきます。
こちらはArduinoのArduinoのスケッチ例です。通電すると、まずは電源オンのLEDアニメーションを行い、電源ボタンが押されたことを検出するとペアリングモードに入るための信号を送ります。それら以外の状態では、ボタンが押されるまでスリープモードに入ります。
xbox.ino
/*
  Arduino code to communicate with xbox 360 RF module.
  https://www.electromaker.io/project/view/xbox-360-rf-module-controlled-with-an-arduino-1
  GNU General Public License, version 3 or later (GPL3+)
*/

#include <avr/sleep.h>

#define sync_pin 2 // 電源ボタンをペアリングボタン代わりにする
#define data_pin 3 // データ線
#define clock_pin 4 // クロック線

int led_cmd[10] =          {0,0,1,0,0,0,0,1,0,0}; // LED初期化
int led_timer_red_1[10] =  {0,0,1,0,1,1,1,0,0,0}; // ライト1を赤(下位4ビットに従う)
int led_timer_red_2[10] =  {0,0,1,0,1,1,1,1,0,0}; // ライト1,2を赤
int led_timer_red_3[10] =  {0,0,1,0,1,1,1,1,0,1}; // ライト1,2,4を赤
int led_timer_red_4[10] =  {0,0,1,0,1,1,1,1,1,1}; // ライト1,2,3,4を赤
int led_red_off[10] =      {0,0,1,0,1,1,0,0,0,0}; // すべてのライトをオフ

int anim_cmd[10] =         {0,0,1,0,0,0,0,1,0,1}; // 起動アニメーション
int sync_cmd[10] =         {0,0,0,0,0,0,0,1,0,0}; // ペアリングの実行
int turn_off_cmd[10] =     {0,0,0,0,0,0,1,0,0,1}; // 接続したコントローラーの電源を切る

volatile boolean sync_pressed = 0;
int sync_hold_time = 0;
boolean turn_off_controllers = false;

void sendData(int command[])
{
  pinMode(data_pin, OUTPUT);
  digitalWrite(data_pin, LOW);    // データ転送の開始
  
  int previous_clock = 1;  
  for(int i = 0; i < 10; i++)
  {
    while (previous_clock == digitalRead(clock_pin)){} // クロック変化の検出
    previous_clock = digitalRead(clock_pin);
    
    // クロックの立ち下がりでデータビットを送る
    digitalWrite(data_pin, command[i]);

    while (previous_clock == digitalRead(clock_pin)){} // クロックの立ち上がりの検出
    previous_clock = digitalRead(clock_pin);
  }
  
  digitalWrite(data_pin, HIGH);
  pinMode(data_pin, INPUT);
  
  delay(50);
}

void setHeldLEDs(int held_time)
{
  if(held_time >= 1000)
  {
    sendData(led_timer_red_4);
  }
  else if(held_time >= 750)
  {
    sendData(led_timer_red_3);
  }
  else if(held_time >= 500)
  {
    sendData(led_timer_red_2);
  }
  else if(held_time >= 250)
  {
    sendData(led_timer_red_1);
  }
  else
  {
    sendData(led_red_off);
  }
}

void initLEDs()
{
  sendData(led_cmd);
  sendData(anim_cmd);
}

void wakeUp()
{
  sync_pressed = 1;
}

void sleepNow()
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // スリープモード
  sleep_enable(); // スリープの有効
  attachInterrupt(0, wakeUp, LOW);
  sleep_mode();
  sleep_disable(); // スリープの無効
  detachInterrupt(0); // PIN2割り込みの無効
}

void setup() 
{
  Serial.begin(9600);
  pinMode(sync_pin, INPUT);
  digitalWrite(sync_pin,HIGH);
  pinMode(data_pin, INPUT);
  pinMode(clock_pin, INPUT);
  delay(2000);

  initLEDs();
}

void loop()
{
  // ペアリングボタンが押されていなければスリープ
  if(!sync_pressed)
  {
    sleepNow();
  }
  
  delay(200);
  
  if(sync_pressed)
  {    
    Serial.print("Sync held time (ms): ");
    Serial.println(sync_hold_time, DEC);
    
    setHeldLEDs(sync_hold_time);
    
    // 1000ms経過したらユーザーがキャンセルしたと見なす
    if(sync_hold_time >= 1000)
    {
      turn_off_controllers = true;
      sync_hold_time = 1000;
    }
    
    // 同期ボタンを離せば初期化
    if (digitalRead(sync_pin))
    {
      setHeldLEDs(0); 
      
      if(turn_off_controllers)
      {
        Serial.println("Turning off controllers.");
        sendData(turn_off_cmd);
        
        turn_off_controllers = false;
      }
      else
      {
        Serial.println("Syncing controllers.");
        sendData(sync_cmd);  
      }
      
      // 処理が完了すれば値をリセット
      sync_hold_time = 0;
      sync_pressed = false;
    }
    else
    {    
      sync_hold_time += 200;
    }
  }
}
プログラミングしたArduinoが正常に稼働してれば、電源を通したときに、基板のLEDがXbox360本体をが起動したときのようくるくると点滅します。
ここまでくれば、あとはWindowsパソコンにドライバーをインストールするだけです。ただし、市販品のレシーバーとはハードウェアとしては完全に同一ではないため、導入にはひと工夫が必要です。詳しくは過去記事をご覧ください。
2020/10/05