きっかけ
今更ですが2019年9月5日ニンテンドーダイレクトの発表で
- Switchでスーパーファミコンのゲームができる!
- Switchで使えるスーパーファミコンコントローラ 発売!
せっかくなら実機のスーファミコントローラでゲームやりたいよなーと思いました.とはいえいきなりSwitchに接続できるコントローラを作るのは難しそうだったので,とりあえず有線でPCに接続できるようにしました.
そして完成形はこれ.PCにもスマホにも行けるよ(ROM吸出しは自前でしましょう)
以降スーパーファミコンは英語名のSNES(Super Nintendo Entertainment System)で略称します.
全体像
こんな感じで,めんどいことは Arduino に任せればいい!
手順はこう.
- ArduinoをHIDデバイス(キーボードみたいなん)にする
- PCにArduinoをゲームパッドとして登録
- コントローラからボタンの情報をどうにかして受け取る
- PCにキーボード入力として情報を送る
- 快適なゲームライフ!!!
まず1については ATmega32u4 を搭載してる Arduino Leonardo か Arduino micro を使えば問題ない.Arduino Uno など ATmega16u2 でもジャンパを短絡させてあげるとHID化できた気がする(要確認)
2と4についても自作キーボードなどしてる人は結構いるので,調べれば情報ころがってる大丈夫.
問題は3.この記事の本題です.
原理を知る
If all of this is already well known, then sorry for the waste of net bandwidth...
もしあなたがSNESについて十分に知っているなら,ネット帯域幅を無駄遣いして申し訳ない...
この章の説明と図は Super Nintendo Entertainment System: pinouts & protocol を参考にしている.
ピン配置
まず本体との接続部分を見てみよう.
接続部には7つのピンがある.これらのうちPin5とPin6の2つは使用されておらず,実際ケーブルを出してみるとワイヤは5本しか入っていない.白いケーブルが+5VのVCCで茶色のケーブルがGround,黄色が Clock で,オレンジがLatch,そして赤のケーブルにボタンの情報が流れてくる.
それぞれの役割をまとめると以下.
ピン番号 | 名前 | 説明 | ケーブル内のワイヤの色 |
---|---|---|---|
1 | VCC | +5Vの電源 | 白 |
2 | Clock | クロック | 黄 |
3 | Latch | ラッチ | オレンジ |
4 | Data | シリアルデータ | 赤 |
5 | N/A | 未使用 | - |
6 | N/A | 未使用 | - |
7 | GND | グラウンド | 茶 |
ここで名前(VCCとか)はこの記事内での呼び名で正式ではない.またワイヤの色は違うことがあるらしい(?)ので違う場合は教えてください.(自分のSNESは表の通りでした.)
ここまで来れば,Arduinoとの接続は例えば以下のようにしたらよいことがわかる.
つまり VCC(+5V) を Arduino から供給してあげて,あとのClock,Latch,Data を Arduino 側のピンにそれぞれ入力すればいい.ここまでで Arduino と SNES の配線は完了した.これから確認したいのは,SNES がどのようにボタン情報を本体に送信しているかだ.
通信プロトコル
まず概要図を見よう.
見慣れない人は既に嫌な気持ちかもしれないが,詳しく見ていこう.
Latch
Latchはとてもシンプルだ.1/60 秒(SNES は 60 Hz)ごとに 12 us だけ電圧が Low から High に変化している.注意なのはLatchはSNES本体からコントローラへの信号であるということ.「今から次のフレームがはじまるよ」とコントローラに教えているイメージ.基本的には Low .
Clock
Clock はLatchが High から Low に下がった時から,6 us 後に High から Low に下がり,その6us後に Low から High に上がり,その 6 us 後に…というように周期的に電圧が変化している.Clock もSNES本体からコントローラへ送られる.そしてこの周期は16回繰り返される.(図中に赤線で示した.)このClock の周期に合わせて,コントローラは各ボタンの情報を Data に反映していく.つまりClock が立ち上がるタイミングでコントローラ側はボタン情報を反映し,Clock の立ち下りで本体側でボタンが押されているかを判定する.16 回の繰り返しが終わったら,その後は次のフレームまで High のままで変化しない.
Data
そして Data がボタンの情報を扱う.Data は当然コントローラから本体に送られる.先ほどClock の16回の周期に合わせてボタンの情報を送ると説明した.送る順番は以下の表にまとめた通り.
Clock | 対応するボタン |
---|---|
1 | B |
2 | Y |
3 | Select |
4 | Start |
5 | ↑(Up) |
6 | ↓(Down) |
7 | ←(Left) |
8 | →(Right) |
9 | A |
10 | X |
11 | L |
12 | R |
13 | なし |
14 | なし |
15 | なし |
16 | なし |
ボタンを押しているときは Low ,押していないときは High になる.13~16は送信する情報がないので常に High となり,16周期終わると次のフレームまでずっと Low.
以上のようなプロトコルでコントローラは情報を送信している.ここまでわかれば Arduino のプログラムがかける!!
作ってみる
ハード
ピン配置で説明したようにいい感じに配線する.(Geek な感じでかっこいい)
初めからはんだ付けするのは怖いので下図にようにぶっ刺して,テストするとよい感じ.
実はコントローラに収めるのになかなか苦労してて,ケース削ったり有り合わせのもの(画鋲の上の部分とか)でガタつきを抑えたりした(適当).
あと**木工用ボンドで Arduino を固定するのはおすすめしない.**加水分解をして長期的にはショートの原因になる.ちゃんとした絶縁ボンド(これとか)を買いましょう.私は木工用ボンドで固定したので日がたつと音速でボタンが連打されたり,接続がすぐ途切れたり….
ブログにはこういう工夫が書かれていないことも多いが,やってみると結構大変.
ソフト
疑似コードはこんな感じでさっきのプロトコルにそってコードを書いた.
// コントローラのボタン押下状態を取得
data getControllerData(void){
// Latch for 12us
digitalWrite(DATA_LATCH, High );
delayMicroseconds(12);
digitalWrite(DATA_LATCH, Low );
// 6us待ってからClock 開始
delayMicroseconds(6);
for(int i = 0; i < 16; i++){
digitalWrite(DATA_CLOCK, Low );
delayMicroseconds(6);
// ここでボタンの情報を得る
if(i <= 11){
buttons[i] = digitalRead(DATA_SERIAL);
}
digitalWrite(DATA_CLOCK, High );
delayMicroseconds(6);
}
// 全ボタンがoff状態のものを取得
data controllerData = getBlankDataForController();
if(buttons[0] == 0){
controllerData.B = 1;
}
if(buttons[1] == 0){
controllerData.Y = 1;
}
// ...各ボタン代入
return controllerData;
}
実際にこれを書いてもゲームで使用するにはちょっとつらい挙動をする.これはAruidnoの実行速度の問題でdigitalWrite
とdigitalRead
を実行するのに2~3usかかってしまうからだと思われる.(Arduino micro の ATmega32u4 は 16MHz と十分速そうそうだが,digitalWrite
などは44サイクル程度かかるらしくて少しつらい.高速化で数サイクルまで抑えられるようだが,試していない…)
とりあえず動けばいいやの人は以下のリポジトリを拝借すればいいと思う.
(完走した感想)
結局市販のやつがいいという結論なんですが,自作すると楽しい!!!(そのうち無線化したいなあ)
説明が間違っているところは教えてください!
参考文献
Arduino-SNES-Controller
:すごい人が作ったGitHub ソースコード
Super Nintendo Entertainment System: pinouts & protocol
:ピン配置と通信プロトコル
スーパーファミコンコントローラの解析
:実際にデジアナで波形を確認している(きれいな波形!!)