はじめに

モチベーション

周囲に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のコントローラーをばらして遊んでます。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.