LoginSignup
3
4

More than 5 years have passed since last update.

Xbox360のコントローラをばらして*Piに繋げる

Last updated at Posted at 2017-11-08

はじめに

モチベーション

周囲にRaspberry Piを勧めておきながらOrange Piで遊んでる今日このごろです。安価かつマルチコアでCPUパワー高めなのと、その気になればUSBデバイスとして動作させる事ができるのが魅力です。

今回はXbox One Xの発売を記念して、360のコントローラをばらし、Orange Piに繋げてみました。360のコントローラは中古で安価に入手できる上にLEDもついているためLチカで遊べます。またホリのファイティングスティックEX2とその亜種は市場に大量に出回っており、1000円前後で入手可能。三和やセイミツのスティック・ボタンを使ったコントローラと比べれば見劣りするかもしれませんが、数万円クラスのコントローラとの比較である事を考えると、コストパフォーマンスは抜群です。また、A/Bボタンの故障が多く報告されていますので、どうせなら千石電商にでも足を運び、三和のボタンに交換しても良いかと思います。ボタン全部交換しても1000円ちょっと。個人制作では難しいケース代だと思っても安いのではないかと。

この記事のカバーする範囲と今後の展望

この記事ではひとまずコントローラをOrange PiのGPIOに繋ぎ、コントローラの値の読み取り、LEDの制御を行えるようにします。

ここまででは実用性はありませうんが、次のステップとして、uinputを使ってLinux Input Subsystemにジョイスティックとして/dev/js*へ公開します。これによりジョイスティックに対応しているLinuxアプリからコントローラが利用可能となります。Orange Piをコントローラ内に格納して、Retrorange Piあたりを動かすと、ちょっとしたコントローラ型のゲーム機の完成です。

さらにはUSB Gadget Driverを通じてOrange PiをUSB HIDデバイスとして動作させ、PC等に繋いだ時にUSBのゲームコントローラとして認識できるようにしたいと思います。(記事を書き続ける気力が続くことに)乞うご期待。

コントローラの調査

解体

media-20171106.jpg
底板を外して裏返した写真になります。中央のメイン基板にMS製のUSBコントローラが載ってます。今回は360への接続はひとまず置いとして、この基板は取っ払います。ただし、A/Bボタンが直接はんだ付けされているため、ボタンからの切り離しに苦労するかと思います。コツとしては環境に優しくない鉛フリーではない半田を流し込んであげて、古い半田を溶かしやすくします。基板の再利用を考えないのなら、力づくで外すのもありですが、高確率でA/Bボタンも壊れるので覚悟しましょう。逆にボタン交換するつもりなら力づくでGo!

配線

写真で見て本体から下に出ている配線は今回は不要なので無視。本体に繋がるケーブルも不要です。残る配線は

  • スティックから四方に2本ずつ伸びる8本の配線
  • ボタンを支えている基板からの8パラの配線
  • 上方左右の基板から出ている4パラずつの配線
  • 上方中央の基板から出ている7パラの配線

になります。

スティックの配線

下側にある2本の配線が、スティックを上に倒した時の信号を伝える線になります。ほかも上側の配線が下入力の情報、右側左側がそれぞれ左入力、右入力と、上下左右が逆に対応するので注意が必要です(が、スティックの倒れ方を考えると自然に理解できるかと思います)。

スティックやボタンの配線ペアは、GNDとPull-upされた線の対になります。ボタンが押されるとGNDにショート、Pull-up側から電圧を読み取ることでON/OFFを判断できます。スティック・ボタン側の役割はショートさせる事なので、基本どっちがどっちでもかまわないのですが、元の配線で言えば、下に向かって伸びている白い線が上入力のPull-up、右から出てる白い対になる線がGNDです。他の3方向も同様の並びになります。

ボタンの配線

見ての通り、各ボタンから2つの端子が出ており、この端子間をショートさせるのがボタンの約目です。CNB_2としてコネクタ番号が振られていますが、1から8までLT/RT/Y/X/Y/RT/LTのボタン順にPull-up/GNDが交互に接続されています。メイン基板に直接繋がっていたA/Bボタンの配線は、オリジナルでは写真でみて左側がPull-up、右側がGNDでした。

1 LR Pull-up
2 LR GND
3 RT Pull-up
4 RT GND
5 Y Pull-up
6 Y GND
7 X Pull-up
8 X GND

上方左右の基板

左側がSTART/RB、右側がLB/BACKの基板です。

START/RB LB/BACK
1 START Pull-up BACK Pull-up
2 START GND BACK GND
3 RB Pull-up LB Pull-up
4 RB GND LB GND be

上方中央の基板

XboxロゴボタンとLEDの配線です。基板を外すと裏側にご丁寧にも回路図が書かれていました。
001.jpg
という事で、配線は以下の通り。

1 LED3(右下)
2 LED1(左下)
3 LED4(右上)
4 LED2(左上)
5 V3.3
6 SW3 Pull-up
7 SW3 GND

LEDは共通電源に繋がっており、各LED端子をGNDに落とすと対応するLEDが点灯します。普段はPull-upに設定しておき、点灯時に0出力で良いでしょう。

制作

IMG_0931.JPG
配線中の様子はこんな感じ。元の線材を使えるところは使いつつ、ピンソケットに一度繋いでます。GNDも途中で全部まとめちゃっても良かったんですが、一応ピン端子として個別に残しつつソケット内で合流させました。このソケットからOrange PiやRaspberry Piのピンヘッダに繋げることでGPIOから制御します。ここから先はブレッドボード感覚ですね。

今回のテストにはOrange Pi Zeroを使いました。ピンヘッダとGPIO名、配線の関係は以下の通り。端子が足りなくてLB/RBは今回省略。いずれPi Oneに繋ぐときには復活させたい。

1 PA10 B 2 GND
3 PA13 X 4 PA14 BACK
5 PA02 Y 6 PA16 START
7 GND 8 PA15 上
9 PA18 Xbox 10 3.3V
11 PA19 LED1 12 PA03 下
13 GND 14 PA00 左
15 PA07 LED2 16 PA01 右
17 PG07 LED3 18 GND
19 PG06 LED4 20 PA06 A
21 GND 22 PA11 LT
23 5V 24 PA12 RT
25 5V 26 3.3V

入力確認

以前書いたOrange Pi Zero Armbian導入後の設定とmmapなLチカの方法を取ります。ざっくり5m秒間隔で入力を確認するプログラムは次の通り。

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#define GPIO_PA_BASE 0x01c20000
#define GPIO_PA_OFFSET 0x0800

struct pio_cfg {
  volatile uint32_t cfg[4];
  volatile uint32_t dat;
  volatile uint32_t drv[2];
  volatile uint32_t pul[2];
};

char pad_state[] = "@@@@@@@@@@@@@";

void update(int data) {
  char state[13];
  // 各ビットが0なら対応するボタンが押されている
  state[ 0] = (data & (1 << 14)) ? '0' : '1';
  state[ 1] = (data & (1 << 16)) ? '0' : '1';
  state[ 2] = (data & (1 << 15)) ? '0' : '1';
  state[ 3] = (data & (1 <<  3)) ? '0' : '1';
  state[ 4] = (data & (1 <<  0)) ? '0' : '1';
  state[ 5] = (data & (1 <<  1)) ? '0' : '1';
  state[ 6] = (data & (1 <<  6)) ? '0' : '1';
  state[ 7] = (data & (1 << 11)) ? '0' : '1';
  state[ 8] = (data & (1 << 12)) ? '0' : '1';
  state[ 9] = (data & (1 << 10)) ? '0' : '1';
  state[10] = (data & (1 << 13)) ? '0' : '1';
  state[11] = (data & (1 <<  2)) ? '0' : '1';
  state[12] = (data & (1 << 18)) ? '0' : '1';
  // 入力内容が変わってたらコピーをとって画面に表示
  if (memcmp(pad_state, state, 13)) {
    memcpy(pad_state, state, 13);
    puts(pad_state);
  }
 }

int main(int argc, char** argv) {
  // 例によってメモリマップドI/Oをmmapで直接触りに行く
  int mem = open("/dev/mem", O_RDWR | O_SYNC);
  if (mem < 0) {
    perror("open /dev/mem");
    return -1;
  }

  int prot = PROT_READ | PROT_WRITE;
  size_t size = sysconf(_SC_PAGE_SIZE);
  char *pa_base = mmap(NULL, size, prot, MAP_SHARED, mem, GPIO_PA_BASE);
  struct pio_cfg* pa = (struct pio_cfg*)&pa_base[GPIO_PA_OFFSET];

  // ここまでの処理でpaが指す先はI/Oレジスタになる。
  // そう言えば前回も忘れてたけど、volatile付けるべきかも。

  // データシートを眺めつつ設定を正しく入れる。
  // 今回はANDとORを使いGPIOに見えてて入力に使うピンだけを選んで設定。
  pa->cfg[0] &= 0x00ff0000;  // PA07-PA00: input [7:6,3:0]
  pa->cfg[0] |= 0x10000000;  // PA07-PA00: output [7]

  pa->cfg[1] &= 0x000000ff;  // PA15-PA08: input [15:10]

  pa->cfg[2] &= 0xffff00f0;  // PA21-PA16: input [19:18,16]
  pa->cfg[2] |= 0x00001000;  // PA21-PA16: output [19]

  pa->pul[0] &= 0x000f0f00;  // PA15-PA00: pull-* disabled [15:10,7:6,3:0]
  pa->pul[0] |= 0x55505055;  // PA15-PA00: pull-up [15:10,7:6,3:0]

  pa->pul[1] &= 0xffffff0c;  // PA21-PA16: pull-* disabled [19:18,16]
  pa->pul[1] |= 0x00000051;  // PA21-PA16: pull-up [19:18,16]

  for (;;) {
    update(pa->dat);
    usleep(5000);  // 5msec
  }

  return 0;
}

配線が間違えていなければ正しく入力が取れているはず。LEDの制御は出力になりますが、前回の記事も参考にすれば簡単にLチカできます。

まとめ

Xbox One Xが買えなかったので360のコントローラーをばらして遊んでます。

3
4
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
3
4