執筆者: 瀕死
最終更新: 2024/07/17
瀕死と申す者です。 前回、RP2040開発ボード関係の解説をすると書きましたが、 マイコンが届いていなかったので別の解説をします。 誠に申し訳ございません。
本稿では、PS2とコントローラーの通信の仕様を確認する。
そして、Raspberry Pi Picoの互換ボードであるRP2040開発ボードの1つを用いて、 PS2のコントローラーを作成した方法を紹介する。
SPI通信とRP2040のProgrammable IOについて理解していることが必要である。
PS2のコントローラーとの通信仕様について説明する。
PS2とのコネクタは以下のような形状である。
この方向から見たとき、左から
と本稿では名付ける。
*のついたラインはSPI通信の信号線であり、対応は以下の通りである。
本稿での名前 | SPIにおける名前 |
---|---|
DAT | MISO |
CMD | MOSI |
ATT | SS |
CLK | SCLK |
PS2との通信は主にPS2をマスタとしたSPI通信である。
SPIの設定は以下の通りである。
唯一違う点は、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等への応答も確認されるのため、エラーで終わる。
デバイスモードは、
で、命令特有の応答は不明点が多いが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の通信仕様の解説と、ポップンコントローラーの修理を紹介しました。 ポップンコントローラーについては(おそらく)もう一度記事にする予定ですので、 気長にお待ちください。
この人が書いた記事