/*--------------------------------------------------------------------------
SFCパッド → USB変換(+メガドライブミニ対応)
スーパーファミコン用パッドをPCおよび、メガドライブミニで使えるようにUSBデバイスに変換します。
[参考資料]
このスケッチ作成には以下のURLを参考にしています。
Arduino Joystick Library
https://github.com/MHeironimus/ArduinoJoystickLibrary
スーパーファミコンコントローラの解析
https://sites.google.com/site/hardware007laboratory/home/denshi-kousaku/sfc_cont
Arduino Project: Super Nintendo Entertainment System (SNES)
https://jfrmilner.wordpress.com/2016/07/17/arduino-project-super-nintendo-entertainment-system-snes-controllergamepad-to-windowslinux-retropie-usb/
2020.5.24 BLUE ( qze12045@nifty.com )
-----------------------------------------------------------------------------*/
#include <Joystick.h>
#include <Psx.h> // Includes the Psx Library
// コンパイルオプション
#define __Debug 0 // 1:シリアルデバッグON
#define __IF_SEGA 1 // 1:SEGA純正パッドを模擬する、0:サードパーティ製を模擬する
#define __BugFix 1 //メガドライブミニに接続時に方向キーが入りっぱなしになる場合に1にする
#define DELAY_TIME 10 // 遅延時間(10μs)<--5より小さくするとPSX動作がNG
#define SCAN_TIME 10 // パッド読み取りスキャン時間(10ms) <-- 7だと100回/秒程度。2以下(200回以上/秒)はお勧めできない
//Super Famicom コントローラ制御ピン定義
#define SF_CLOCK 8 // PB4/Pin8
#define SF_LATCH 10 // PB6/Pin10
#define SF_DATA 16 // PB2/Pin16
#if __BugFix
// 方向キー移動量(MEGA DRIVE mini向け)
#define AXIS_MIN 0 // アナログ方向キー最小値
#define AXIS_MAX 511 // アナログ方向キー最大値
#define AXIS_CENTER 255 // アナログ方向キーセンター位置
#else
// 方向キー移動量(PC向け)
#define AXIS_MIN 0 // アナログ方向キー最小値
#define AXIS_MAX 255 // アナログ方向キー最大値
#define AXIS_CENTER 127 // アナログ方向キーセンター位置
#endif
// Joystick PAD番号定義
#if __IF_SEGA
//SEGA 純正パッド模擬(VID=0x0CA3、PID=0x0024)
#define PAD_Y 0 // Yボタン
#define PAD_B 1 // Bボタン
#define PAD_A 2 // Aボタン
#define PAD_X 3 // Xボタン
#define PAD_Z 4 // Zボタン
#define PAD_C 5 // Cボタン
#define PAD_MODE 8 // MODEボタン
#define PAD_START 9 // STARTボタン
#else
//DragonRise製パッド模擬(VID=0x0079、PID=0x0011)
#define PAD_X 0 // Xボタン
#define PAD_A 1 // Aボタン
#define PAD_B 2 // Bボタン
#define PAD_Y 3 // Yボタン
#define PAD_C 4 // Cボタン
#define PAD_Z 5 // Zボタン
#define PAD_MODE 8 // MODEボタン
#define PAD_START 9 // STARTボタン
#endif
// 接続パッド種別
enum{
tMD = 0 , // MegaDrive PAD
tPSX , // Play Station PAD
tPCE , // PC Engine/TG16 PAD
tSFC , // Super Famicom/Super Nintendo Entertainment System PAD
tMAX
};
// パッドボタン定義(ここに定義されたタイプのみ有効)
enum{
pUP = 0 , // UP
pDOWN , // DOWN
pLEFT , // LEFT
pRIGHT , // RIGHT
pA , // A
pB , // B
pC , // C
pSTART , // START
pMODE , // MODE
pX , // X
pY , // Y
pZ , // Z
pSEL , // SELECT(PSX/PCE)
pIII , // III(PCE 6B)
pMAX
};
// 機種別ボタンチェック値(該当Bit位置をマスクする値)
const unsigned int pad_mask[tMAX][pMAX] =
{
// UP ,DOWN ,LEFT ,RIGHT ,A ,B ,C ,START ,MODE ,X ,Y ,Z ,SELECT ,III
{ 0x0001 ,0x0002 ,0x0004 ,0x0008 ,0x0040 ,0x0010 ,0x0020 ,0x0080 ,0x0800 ,0x0400 ,0x0200 ,0x0100 ,0x0000 ,0x0000 },//MEGA DRIVE(tMD)
{ 0x0008 ,0x0002 ,0x0001 ,0x0004 ,0x0200 ,0x0400 ,0x1000 ,0x0010 ,0x4000 ,0x0100 ,0x0800 ,0x2000 ,0x0080 ,0x0000 },//PLAY STATION(tPSX)
{ 0x0001 ,0x0004 ,0x0008 ,0x0002 ,0x0040 ,0x0020 ,0x0010 ,0x0080 ,0x0040 ,0x0200 ,0x0400 ,0x0800 ,0x0040 ,0x0100 },//PC ENGINE/TG16(tPCE)
{ 0x0010 ,0x0020 ,0x0040 ,0x0080 ,0x0001 ,0x0100 ,0x0800 ,0x0008 ,0x0004 ,0x0002 ,0x0200 ,0x0400 ,0x0000 ,0x0000 } //Super Famicom/SNES(tSFC)
};
// パッドボタン定義とJoystickライブラリ送信値変換用
const int pad_map[pMAX] =
{ -1,-1,-1,-1,PAD_A,PAD_B,PAD_C,PAD_START,PAD_MODE,PAD_X,PAD_Y,PAD_Z,PAD_MODE,PAD_A};
//PAD状態を前回と最新状態で比較して、変化があった場合にコードを送信する
unsigned int cur_sts[tMAX]; // 前回のボタン状態を保持する
// Joystickライブラリ定義
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_JOYSTICK,
10, 0, // Button Count, Hat Switch Count
true, true, false, // X, Y, and Z Axis
false, false, false, // Rx, but no Ry or Rz
false, false, // No rudder or throttle
false, false, false); // No accelerator, brake, or steering
/////////////////////////////////////////////////
// 方向キー制御処理(機種共通)
// 入力
// type :機種種別(tMD、tPSX、tPCE)
// state = 方向キー状態
// 戻り
// true=ボタン状態変化あり、false=変化なし
/////////////////////////////////////////////////
bool set_axis(int type ,unsigned int state )
{
int axis = AXIS_CENTER;
bool change = false;
///////////////////
// Y軸制御
///////////////////
// 上下キー状態チェック用マスク値取得
unsigned int mask = pad_mask[type][pUP]|pad_mask[type][pDOWN];
//上下キー状態変化あり
if (( cur_sts[type] & mask )!=( state & mask ))
{
change = true;
// 上キーを離している
if (state & pad_mask[type][pUP])
{
// 下キーを離している
if (state & pad_mask[type][pDOWN])
{
// Y軸をセンターに戻す
axis = AXIS_CENTER;
}
else
{
// 下キーを押下している
// Y軸を最大
axis = AXIS_MAX;
}
}
// 上キーを押下している
else
{
// Y軸を最小
axis = AXIS_MIN;
}
// Y軸値セット
Joystick.setYAxis(axis);
#if __Debug
///////////////////////////////////////////////////////////////////////////////
Serial.print("Joystick.setYAxis(");
Serial.print(axis,DEC);
Serial.print(")\n");
///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
// 方向キー状態を最新に変更
cur_sts[type] = (cur_sts[type] & ~mask)|(state & mask);
}
///////////////////
// X軸制御
///////////////////
// 左右キー状態チェック用マスク値取得
mask = pad_mask[type][pLEFT]|pad_mask[type][pRIGHT];
// 左右キー状態変化あり
if (( cur_sts[type] & mask )!=( state & mask ))
{
change = true;
// 左キーを離している
if (state & pad_mask[type][pLEFT])
{
// 右キーを離している
if (state & pad_mask[type][pRIGHT])
{
// X軸をセンターに戻す
axis = AXIS_CENTER;
}
// 右キーを押下している
else
{
// X軸を最大
axis = AXIS_MAX;
}
}
// 左キーを押下している
else
{
// X軸を最小
axis = AXIS_MIN;
}
// X軸値セット
Joystick.setXAxis(axis);
#if __Debug
///////////////////////////////////////////////////////////////////////////////
Serial.print("Joystick.setXAxis(");
Serial.print(axis,DEC);
Serial.print(")\n");
///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
// 方向キー状態を最新に変更
cur_sts[type] = (cur_sts[type] & ~mask)|(state & mask);
}
// 方向キー状態を最新に変更
// cur_sts[type] = (cur_sts[type] & 0xfff0)|(state & 0xf);
return change;
}
/////////////////////////////////////////////////
// パッド制御処理(機種共通)
// 入力
// type :機種種別(tMD、tPSX、tPCE)
// state = 方向キー状態
// button = ボタン定義
// 戻り
// true=ボタン状態変化あり、false=変化なし
/////////////////////////////////////////////////
bool set_button(int type ,unsigned int state ,int button )
{
// ボタン状態チェック用マスク値取得
unsigned int mask = pad_mask[type][button];
// ボタン状態変化あり
if (( cur_sts[type] & mask )!=( state & mask ))
{
// Joystickボタン値取得
int pad = pad_map[button];
// ボタン押下?
if( !(state & mask) )
{
#if __Debug
///////////////////////////////////////////////////////////////////////////////
Serial.print(pad,DEC);
Serial.print(" Press\n");
///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
// ボタン押下セット
Joystick.pressButton(pad);
}
// 離した
else
{
#if __Debug
///////////////////////////////////////////////////////////////////////////////
Serial.print(pad,DEC);
Serial.print(" Release\n");
///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
// ボタン離す
Joystick.releaseButton(pad);
}
// ボタン状態を変更する
cur_sts[type] = (cur_sts[type] & ~mask)|(state & mask);
// 変化あり
return true;
}
// 変化なし
return false;
}
//////////////////////////////////////
// 初期化
//////////////////////////////////////
void setup() {
// put your setup code here, to run once:
//SFC制御クロック信号初期化(OUTPUT:HIGH)
pinMode(SF_CLOCK, OUTPUT);
digitalWrite(SF_CLOCK, HIGH);
//SFC制御ラッチ信号初期化(OUTPUT:LOW)
pinMode(SF_LATCH, OUTPUT);
digitalWrite(SF_LATCH, LOW);
//SFC制御データ信号初期化(INPUT_PULLUP)
pinMode(SF_DATA, INPUT_PULLUP);
// Joystick Library初期化
Joystick.begin(false);
// PAD方向キー初期化
Joystick.setXAxisRange(AXIS_MIN, AXIS_MAX);
Joystick.setYAxisRange(AXIS_MIN, AXIS_MAX);
Joystick.setXAxis(AXIS_CENTER);
Joystick.setYAxis(AXIS_CENTER);
// パッド状態初期化
cur_sts[tSFC]= 0xffff;
#if __Debug
Serial.begin(38400);
#endif //__Debug
}
//////////////////////////////////////
// メインループ
//////////////////////////////////////
void loop() {
// put your main code here, to run repeatedly:
unsigned int state = 0;
////////////////////////////////////////////
//SUPER FAMICOMコントローラ状態読み出し
////////////////////////////////////////////
// LATCH信号HIGH出力
digitalWrite(SF_LATCH, HIGH);
delayMicroseconds(12);
// 12μ秒後、LATCH信号LOWに戻す
digitalWrite(SF_LATCH, LOW);
delayMicroseconds(6);
// ボタン状態読出し(16Bit)
for(int i = 0; i < 16; i++)
{
// CLOCK信号LOW出力
digitalWrite(SF_CLOCK, LOW);
delayMicroseconds(6);
// ボタン状態を(1Bitづつ)読み出す
state |= digitalRead(SF_DATA) << i;
// CLOCK信号をHIGHに戻す
digitalWrite(SF_CLOCK, HIGH);
delayMicroseconds(6);
}
//ボタン状態変化フラグクリア
bool change = false;
//方向キー制御
change |= set_axis(tSFC,state);
// 各ボタン制御
change |= set_button(tSFC,state,pA);
change |= set_button(tSFC,state,pB);
change |= set_button(tSFC,state,pC);
change |= set_button(tSFC,state,pSTART);
change |= set_button(tSFC,state,pMODE);
change |= set_button(tSFC,state,pX);
change |= set_button(tSFC,state,pY);
change |= set_button(tSFC,state,pZ);
// Joystick情報送信
if(change)
{
#if __Debug
///////////////////////////////////////////////////////////////////////////////
Serial.print("SFC :");
Serial.print(state,BIN);
Serial.print("\n");
Serial.print("Joystick.sendState()\n");
///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
Joystick.sendState();
}
//次回ポーリング開始待ち
delay(SCAN_TIME);
}