トップ画像
PS2のコントローラーを既存の回路基板を用いずに作成する

執筆者: 瀕死

最終更新: 2024/07/17

はじめに

瀕死と申す者です。 前回、RP2040開発ボード関係の解説をすると書きましたが、 マイコンが届いていなかったので別の解説をします。 誠に申し訳ございません。

目的

本稿では、PS2とコントローラーの通信の仕様を確認する。

そして、Raspberry Pi Picoの互換ボードであるRP2040開発ボードの1つを用いて、 PS2のコントローラーを作成した方法を紹介する。

SPI通信とRP2040のProgrammable IOについて理解していることが必要である。

目次

  • PS2のコントローラーを既存の回路基板を用いずに作成する
    • はじめに
    • 目的
    • 目次
    • 通信仕様の確認
      • 低レイヤ部分
      • 高レイヤ部分
    • 開発
      • ソフトウェア部分
      • ハードウェア部分
    • 実践
    • おわりに
    • 参考文献

通信仕様の確認

PS2のコントローラーとの通信仕様について説明する。

低レイヤ部分

PS2とのコネクタは以下のような形状である。

この方向から見たとき、左から

  • DAT*
  • CMD*
  • 7V
  • GND
  • 3V3
  • ATT*
  • CLK*
  • (ピンはないorあっても使わない)
  • ACK

と本稿では名付ける。

*のついたラインはSPI通信の信号線であり、対応は以下の通りである。

本稿での名前

SPIにおける名前

DAT

MISO

CMD

MOSI

ATT

SS

CLK

SCLK

PS2との通信は主にPS2をマスタとしたSPI通信である。

SPIの設定は以下の通りである。

  • mode3 (CPOL 1, CPHA 1)
  • LSBファースト

唯一違う点は、ACKの存在である。 ACK線には、SPIでそれぞれの1byte通信の最後のクロックから100us以内に 2us以上LOWにする必要がある。 それ以外ではHI-Z(ハイインピーダンス)にする。

正常に通信できている場合の波形を以下に示す。

高レイヤ部分

PS2との通信はデジタルパッドの場合、5byteで構成される。

通信の最初の3byteはヘッダーで、 今回のようなデジタルパッドの模倣が目的の場合は以下のように通信する必要がある。

1

2

3

CMD(<- PS2)

0x01

0x42

0x00

DAT(-> PS2)

0xFF

0x41

0x5A

1byte目は、 CMD: 0x01固定、DAT: Don't care

2byte目は、 CMD: 命令内容、DAT: 上位ニブル: デバイスモード / 下位ニブル: 命令特有の応答(不明)

命令はかなり種類があるが、 デジタルパッドの模倣が目的であれば、PS2は0x42への応答のみでも動作する。

ただし、PS2模倣のライブラリのサンプルプログラムをそのまま使うと0x43等への応答も確認されるのため、エラーで終わる。

デバイスモードは、

  • 0x4: デジタル
  • 0x7: デュアルショック
  • 0xf: エスケープ

で、命令特有の応答は不明点が多いが0x1を送るとよい。

3byte目は、どちらも固定であるとされている。

残りの2byteはデジタルパッドにおいては命令に応じたデータを送る。 命令: 0x42に対する応答は以下のとおりである。

1

2

CMD(<- PS2)

0x00

0x00

DAT(-> PS2)

ボタン状態1

ボタン状態2

ボタン状態は離れた状態: 1、押している状態: 0で1bitで表現する。

ボタン状態1では、上位bitから、 ←↓→↑ START 1固定 1固定 SELECT

ボタン状態2では、上位bitから、 □×○△ R1 L1 R2 L2

である。

開発

実際にRaspberry Pi Pico互換のRP2040開発ボード(以下開発ボード)によって、 コントローラーを作成する。

また、開発ボードへのボタン接続用の回路を作成する。

ソフトウェア部分

基本的にはSPI通信をProgrammable IOで実装しただけであるが、 ACK送信の処理も行う。

実装にはPicoMemcardのプロジェクトを大きく参考にさせていただいた。

.define PUBLIC PIN_DAT 5
.define PUBLIC PIN_CMD 6
.define PUBLIC PIN_SEL 7
.define PUBLIC PIN_CLK 8
.define PUBLIC PIN_ACK 9

.program mosi_adp
sel_high_r:
wait 0 gpio PIN_SEL
.wrap_target
wait 0 gpio PIN_CLK
wait 1 gpio PIN_CLK
in pins 1
jmp pin sel_high_r
.wrap

.program miso_adp
.side_set 1 pindirs
sel_high_w:
set pindirs, 0			side 0
wait 0 gpio PIN_SEL		side 0
.wrap_target
pull					side 0
nop						side 1 [5]
set x, 7				side 0 [5]
sendbit:
wait 1 gpio PIN_CLK		side 0
wait 0 gpio PIN_CLK		side 0
out pindirs 1			side 0
jmp pin sel_high_w		side 0
jmp x-- sendbit			side 0
.wrap

初期化処理の部分は省略した。

c言語側では以下のような関数を実装し、ループで実行させているだけである。

uint8_t ps2_transfer(uint8_t data) {
	pio_sm_put_blocking(pio, smMISO, ~data & 0xFF);
	return (pio_sm_get_blocking(pio, smMOSI) >> 24);
}

void connect_ps2(uint8_t btn_data1, uint8_t btn_data2) {
	uint8_t dats[5] = {0xff, 0x41, 0x5A, btn_data1, btn_data2};
	uint8_t cmds[5];

	for (size_t i = 0; i < 5; i++) {
		cmds[i] = ps2_transfer(dats[i]);
		if (cmds[i] != EXPECT_CMDS[i]) {
			// なにかがおかしいとき
      // 転送結果確認のために別コアに転送
			queue_serial_t entry = {i, cmds[0], cmds[1], cmds[2], cmds[3], cmds[4]};
			queue_add_blocking(&serial_queue, &entry);
			return;
		} else {
			// なぜかなにか入れるとアウト
		}
	}
	
  // 転送結果確認のために別コアに転送
	queue_serial_t entry = {4, cmds[0], cmds[1], cmds[2], cmds[3], cmds[4]};
	queue_add_blocking(&serial_queue, &entry);
	return;

}

ハードウェア部分

KiCadで回路図を作成し、JLCPCBに発注した。

そして抵抗等の部品を実装した。

実はプリント基板を作成するのは初めてであるため、解説ができないがご容赦いただきたい。

実践

実はこの計画は部内のポップンコントローラーを修理することである。

元のコントローラーはコネクタでボタンと接続されているという考えられた構成であったため、作業が簡単であった。

ボタンと接続し実際にゲームを行ったところ正常に動作した。

おわりに

今回はPS2の通信仕様の解説と、ポップンコントローラーの修理を紹介しました。 ポップンコントローラーについては(おそらく)もう一度記事にする予定ですので、 気長にお待ちください。

参考文献

取得に失敗しました

2024年度 入部