0
0

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 1 year has passed since last update.

ORANGE picoAdvent Calendar 2023

Day 8

ORANGE pico のゲームコントローラ連携を試す

Posted at

ORANGE pico のゲームコントローラ連携を試してみた。

ゲームコントローラの入手

ORANGE pico 対応のゲームコントローラはaitendoにページがあるが、残念ながら在庫切れとなっている。

似たコントローラがAliExpressで売られていたので、以下を購入した。

Controller For NES Classic Edition Mini For Nintendo Entertainment System Controller Gamepad Joystick with 1.8m built-in Cable

コントローラとORANGE pico を接続するには、公式にはWiiChunkアダプターを使用することになっている。
これもaitendoでは在庫切れだが、オレンジピコショップで入手できた。

ゲームコントローラの状態を読み取るプログラム その1

以下のプログラムは、ゲームコントローラを初期化した後、ボタンの状態を読み取って画面に表示する。
さらに、ゲームコントローラの初期化および読み取りを行う関数の実行中はPORT1にHIGHを出力し、処理中であることがわかるようにする。

10 ioctrl 1,0:out 1,0
20 cpeek &H81AA:cpoke asc("^"),-1:cpeek &H81AB:cpoke asc("v"),-1
30 cpeek &H81A9:cpoke asc("<"),-1:cpeek &H81A8:cpoke asc(">"),-1
40 cls:ix=5:iy=2:lx=5:rx=25:by=5
50 for i=0 to 15
60 locate lx,by+i:print format$("%2d",i);
70 locate lx+3+7,by+i:print 0;
80 locate rx,by+i:print format$("%2d",16+i)
90 locate rx+3+7,by+i:print 0;
100 next
110 locate lx+3,by:print "^";
120 locate lx+3,by+1:print "v";
130 locate lx+3,by+2:print "<";
140 locate lx+3,by+3:print ">";
150 locate lx+3,by+4:print "A";
160 locate lx+3,by+5:print "B";
170 locate lx+3,by+6:print "X";
180 locate lx+3,by+7:print "Y";
190 locate lx+3,by+8:print "L";
200 locate lx+3,by+9:print "R";
210 locate lx+3,by+10:print "START";
220 locate lx+3,by+11:print "SELECT";
230 pstatus=0
240 out 1,1
250 ires=button(-1)
260 out 1,0
270 locate ix,iy:print "INIT ";ires;
280 out 1,1
290 status=button(0)
300 out 1,0
310 diff=status^pstatus
320 for i=0 to 15
330 if (diff>>i)&1 then locate lx+3+7,by+i:print (status>>i)&1;
340 if (diff>>(i+16))&1 then locate rx+3+7,by+i:print (status>>(i+16))&1;
350 next
360 pstatus=status
370 locate 0,by+16
380 goto 280

このプログラムは、CC0 1.0 でライセンスする。

まずはゲームコントローラを接続せずに実行してみると、初期化のために呼び出した関数は 0 を返し、全ボタンが押されている判定となった。

ゲームコントローラを接続しない状態での実行結果

WiiChunkアダプタを経由して ORANGE pico とゲームコントローラを接続し、さらにロジックアナライザも接続した。

接続の様子

すると、初期化関数は変わらず 0 を返し、ボタンの状態は0と1が点滅していた。
コントローラーのボタンを押すと、状態は1で安定した。

ロジックアナライザのキャプチャ結果を見ると、ボタンの取得処理がすぐに終わっているときと時間がかかっているときがあることがわかる。

通信の様子 (全体)

詳しく見てみると、取得処理がすぐに終わっているときはNACKが返っており、取得に失敗しているようである。

通信の様子 (失敗)

取得に成功しているときは、1回の取得に約10msかかっている。

通信の様子 (成功)

取得処理は、以下のように行われているようである。

  1. 7ビットアドレス 0x520x00 の1バイトを送信し、ストップコンディションを送信する
    取得の様子 1
  2. 10ms待機する
    取得の様子 2
  3. 7ビットアドレス 0x52 から8バイトを受信し、ストップコンディションを送信する
    取得の様子 3

受信した内容とボタンの判定の関係は、後で解析を行う。

取得に成功した際はストップコンディションを送信してSCLをHIGHにしているのに対し、取得に失敗した際はストップコンディションを送信せずSCLがLOWのままになっている、という点にも注目したい。

初期化処理は、間隔をあけた数回の通信からなる。

初期化の様子 (全体)

初期化処理は、以下のように行われた。

  1. 7ビットアドレス 0x520x40 0x00 の2バイトを送信し、ストップコンディションを送信する
    初期化の様子 1
  2. 15ms待機する
    初期化の様子 2
  3. 7ビットアドレス 0x52 への送信を試み、NACKが返ってきた
    初期化の様子 3
  4. 15ms待機する
    初期化の様子 4
  5. 7ビットアドレス 0x520xFB 0x00 の2バイトを送信し、ストップコンディションを送信する。続いて7ビットアドレス 0x52 への送信を試み、NACKが返ってきた
    初期化の様子 5
  6. 10ms待機する
    初期化の様子 5
  7. 7 ビットアドレス 0x52 からの受信を試み、NACKが返ってきた
    初期化の様子 6

ゲームコントローラを接続していない状態での初期化処理も、それぞれ送信する場面でNACKが返って送信が行われない以外は同様であった。

ゲームコントローラの状態を読み取るプログラム その2

先ほどのプログラムではボタンの状態の取得に成功するときと失敗するときがあった。
これは高速でポーリングしているからかもしれないと考え、1秒間隔で状態を取得するようにしてみた。
初期化と状態の読み取り、およびそれらの結果の出力だけを行うシンプルなプログラムである。

10 print button(-1)
20 pause 1000
30 print button(0)
40 goto 20

このプログラムは、CC0 1.0 でライセンスする。

さらに、コマンド

i2c 2,1

を実行し、トレース出力を有効化した。
この状態でプログラムを実行すると、以下の出力が得られた。

[S][a4][A][40][A][00][A][P]
[S][a4][S][a4][A][fb][A][00][A][P]
[S][a4][A][5c][A][78][S][a5]0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5]-1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5]-1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4]-1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5]-1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5]-1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0

最初のプログラムでは失敗していた初期化処理最後の読み込みに成功し、ボタンの状態の取得同様の8バイトを読み込んでいる。
ボタンの状態の取得に成功した場合は (ボタンを押していないので) 0 が返っており、失敗した場合は -1 が返っているようである。
状態の取得に失敗する場合は、送信に失敗する場合と、送信は成功して受信に失敗する場合があるようである。
1秒間隔というゲームにはあまり役立たなそうな低速でのポーリングでも取得失敗が発生するため、ポーリング間隔の問題ではなさそうであることがわかった。

Wiiコネクタ経由での接続

ボタンの状態の取得に失敗することがある原因としてWiiChunkアダプタとコントローラの接触の問題を疑い、かわりにaitendoのWiiコネクタwith基板経由で接続してみることにした。

そのため、まずは端子の関係を調べた。
まず、Wiiコネクタ(オス)にWiiChunkアダプタを接続し、接続関係を調べた。
その結果、Wiiコネクタの端子とWiiChunkアダプタの端子の関係は以下のようになっていることがわかった。

Wiiコネクタの端子とWiiChunkアダプタの端子の関係

次に、Wiiコネクタ(オス)を変換基板に接続したWiiコネクタ(メス)に接続し、端子の対応関係を調べた。
その結果、変換基板に書かれている番号とWiiChunkアダプタの端子の関係は以下のようになっていることがわかった。

WiiChunkアダプタ 変換基板
- 6
+ 1
D 3
C 4

これに沿って ORANGE pico に接続し、実験を行った。

Wiiコネクタ経由でのコントローラの接続

しかし、取得の失敗は無くならなかった。
原因としては、たとえば以下が考えられるが、現時点ではわからない。

  • もともと取得失敗が発生しうるものである
  • 今回入手したコントローラが低品質である
  • 製造・輸送・保管など何らかの問題で、ケーブルなどが断線しかけている

Arduinoを用いたプロトコルの解析

これまでの調査により、以下のことがわかっている。

  • 初期化時に 0x40 が送信される
  • ボタンの取得時に8バイトが読み込まれ、その値は 0x00 0x00 0x00 0x00 0xff 0xff 0x00 0x00 が多い

これらの情報をもとに、Arduino UNO 用の以下のプログラムを作成した。

#include <Wire.h>

const int peripheral7bitAddress = 0x52;

unsigned char receiveBuffer[32];
unsigned char sendBuffer[8];

int phase = -3;

void setup() {
  Wire.begin(peripheral7bitAddress);
  // disable internal pull-up
  pinMode(SDA, INPUT);
  pinMode(SCL, INPUT);
  Wire.onReceive(receiveHandler);
  Wire.onRequest(requestHandler);
}

void loop() {
}

void receiveHandler(int numBytesRead) {
  (void)numBytesRead;
  int receivedSize = 0;
  while (Wire.available()) {
    int c = Wire.read();
    if (receivedSize < 32) receiveBuffer[receivedSize++] = c;
  }
  if (receivedSize > 0 && receiveBuffer[0] == 0x40) {
    // assuming initialization
    phase = -3;
  }
}

void requestHandler() {
  switch (phase) {
    case -3:
      // all 0x00
      for (int i = 0; i < 8; i++) sendBuffer[i] = 0x00;
      break;
    case -2:
      // all 0xff
      for (int i = 0; i < 8; i++) sendBuffer[i] = 0xff;
      break;
    case -1:
      // reversed
      for (int i = 0; i < 8; i++) sendBuffer[i] = 0xff;
      sendBuffer[4] = sendBuffer[5] = 0x00;
      break;
    default:
      // normal
      for (int i = 0; i < 8; i++) sendBuffer[i] = 0x00;
      sendBuffer[4] = sendBuffer[5] = 0xff;
      if (1 <= phase && phase <= 8) sendBuffer[4] &= ~(1 << (phase - 1));
      if (9 <= phase && phase <= 16) sendBuffer[5] &= ~(1 << (phase - 9));
  }
  for (int i = 0; i < 8; i++) {
    Wire.write(sendBuffer[i]);
  }
  phase++;
  if (phase > 16) phase = -3;
}

このプログラムは、CC0 1.0 でライセンスする。

このプログラムを書き込んだArduino (互換機) を以下のように ORANGE pico に接続した。

Arduino (互換機) ORANGE pico
GND GND
A4 SDA
A5 SCL

ORANGE pico で2番目のプログラムを実行してトレース結果を得ると、以下のようになった。

[S][a4][A][40][A][00][A][P]
[S][a4][A][f0][A][55][A][P]
[S][a4][A][fb][A][00][A][P]
[S][a4][A][5c][A][78][A][66][A][61][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][ff][A][ff][A][ff][A][ff][A][ff][A][ff][A][ff][A][ff][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][ff][A][ff][A][ff][A][ff][A][00][A][00][A][ff][A][ff][N][P]
3135
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][fe][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][fd][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][fb][A][ff][A][00][A][00][N][P]
1024
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][f7][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ef][A][ff][A][00][A][00][N][P]
2048
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][df][A][ff][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][bf][A][ff][A][00][A][00][N][P]
2
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][7f][A][ff][A][00][A][00][N][P]
8
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][fe][A][00][A][00][N][P]
1
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][fd][A][00][A][00][N][P]
4
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][fb][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][f7][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][ef][A][00][A][00][N][P]
16
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][df][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][bf][A][00][A][00][N][P]
32
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][ff][A][7f][A][00][A][00][N][P]
0
[S][a4][A][00][A][P]
[S][a5][A][00][A][00][A][00][A][00][A][00][A][00][A][00][A][00][N][P]
3135

初期化処理において送信失敗が発生せず、きれいなデータが得られた。
初期化処理の最後の読み込みが8バイトではなく6バイトになっている。

「3135」を十六進数で表すと「0xc3f」であり、以下のことが推測できる。

  • 以下のビットが「0」の場合はそれぞれ対応する数値を取得結果に加算する。「1」の場合は何もしない。
  • それ以外のビットは取得結果に影響を与えない。
バイト (0-origin) ビット 加算する値 対応するボタン
5 0b00000001 1 方向キー上
4 0b01000000 2 方向キー下
5 0b00000010 4 方向キー左
4 0b10000000 8 方向キー右
5 0b00010000 16 Aボタン
5 0b00000100 32 Bボタン
4 0b00000100 1024 STARTボタン
4 0b00010000 2048 SELECTボタン

公式のコマンド一覧で「未使用」となっているXボタン、Yボタン、Lボタン、Rボタンについては、周辺機器デバイスを独自に開発しても、押されていると認識させるのは不可能であるか、少なくとも容易ではないようである。

ここで、初期化処理が返す 0 は初期化時のボタンの状態 (押されていない) であるという仮説を立てた。
そこで、上記の Arduino プログラムの

    // assuming initialization
    phase = -3;

    // assuming initialization
    phase = 3;

に変更し、最初の読み込みでボタンが押された状態を返すようにしてみた。
結果は、変わらず初期化では 0 が返され、ボタンの状態が反映されるわけではなさそうであった。

まとめ

  • ゲームコントローラのボタン情報の取得には、成功した場合1回につき10ms強かかる。
  • ゲームコントローラの初期化処理は、成功しても失敗しても 0 を返す。
  • ゲームコントローラのボタン情報の取得処理は、失敗すると -1 を返す。
  • ゲームコントローラのボタン情報の取得時には、I2Cの7bitアドレス 0x52 から8バイトを読み込み、そのうち一部のビットがボタン情報に変換される。

ボタン情報の取得失敗時に返される -1 をそのままボタン情報として扱ってしまうと誤動作の原因となるので、-1 が返ってきた場合はかわりに前回取得に成功したボタン情報を用いるなどの工夫をするとよさそうである。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?