/*--------------------------------------------------------------------------
 メガドライブパッド+プレイステーションパッド+PCEパッド+SFCパッド
  → USB変換(+メガドライブミニ/プレイステーションクラッシック対応)
 
 メガドライブ用、プレイステーション用(デジタルパッド)、PCエンジン用パッド
 および スーパーファミコン用パッドを、PCおよびメガドライブミニ等で使えるように
 USBデバイスに変換します。
 
 注意:
 このスケッチは、Arduino Leonardo/Pro Micro(ATmega 32U4)専用です。
 パッド側のピン読み出しを最速で行うためポート(PinD、PinF)を直接アクセスしているため、
 他マイコンとピン位置の互換性がありません。
 メガドライブ用パッドと他社用(プレイステーション、PCE、SFC)を同時に接続ができますが、
 全て同じ1コン側として認識されます。いずれかのパッドを2コン側として利用はできませんので
 ご注意ください。(同時に操作すると動作がバッティングします)
[参考資料]
 このスケッチ作成には以下のURLを参考にしています。
 
 JoystickMD
 https://github.com/NibblesLab/JoystickMD
 
 セガ メガドライブ 6ボタンパッドの読み取り方
 http://applause.elfmimi.jp/md6bpad.html
 
 メガドライブミニ を ジョイスティック で遊ぶ ジョイスティックアダプターの作成
 https://tounosumura302.netlify.app/posts/201910191643/
 
 Arduino Joystick Library
 https://github.com/MHeironimus/ArduinoJoystickLibrary
 
 Arduino PSX Library
 https://playground.arduino.cc/Main/PSXLibrary/
 
 SONY PLAYSTATIONR CONTROLLER INFORMATION
 https://gamesx.com/controldata/psxcont/psxcont.htm
 
 プレイステーション デュアルショック で痺れちゃう
 https://applause.elfmimi.jp/dualshock.shtml
 
 NEC PC Engine - Tech Wiki 
 https://console5.com/wiki/NEC_PC_Engine
 
 TurboGrafx/Duo/PC-Engine Controller Data
 https://gamesx.com/controldata/turbocont.htm
 
 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/
 
 スーパーファミコンコントローラの解析
 https://sites.google.com/site/hardware007laboratory/home/denshi-kousaku/sfc_cont
 
 2020.6.21 BLUE ( qze12045@nifty.com )
-----------------------------------------------------------------------------*/
#include <Joystick.h>
 
// コンパイルオプション
#define     __Debug     0       // 1:シリアルデバッグON
#define     __FFBtest   0       // 1:デュアルショック振動テスト
 
#define     __HORI_ADP      1   // 1:HORI PS4-100模擬(VID=0x0F0D、PID=0x00EE)
#define     __ELECOM_ADP    0   // 1:ELECOM USB変換アダプタ模擬(VID=0x0925、PID=0x8888)
#define     __SEGA_ADP      0   // 1:SEGA メガドラミニ用パッド模擬(VID=0x0CA3、PID=0x0024)
 
#define     __BugFix    0       //メガドライブミニに接続時に方向キーが入りっぱなしになる場合に1にする
 
#define     DELAY_TIME  6       // 遅延時間(6μs)<--5より小さくするとPSX動作がNG
#define     SCAN_TIME   10      // パッド読み取りスキャン時間(10ms) <-- 7だと100回/秒程度。2以下(200回以上/秒)はお勧めできない
 
//PSXコントローラー制御ピン定義
#define     PS_DATA     7       // データ信号
#define     PS_CMD      8       // Command送信
#define     PS_ATT      9       // セレクト信号
#define     PS_CLK      10      // 制御クロック信号
 
// PSX送受信コマンド種別
enum{
    PS_READ         =   0   ,   // パッド状態読出し(READ_DATA)
    PS_CONFIG_MODE          ,   // コンフィグモード遷移(CONFIG_MODE_ENTER)
    PS_CONFIG_EXIT          ,   // コンフィグモード終了(CONFIG_MODE_EXIT)
    PS_SET_MODE             ,   // 動作モード/モード変更可否設定(SET_MODE_AND_LOCK)
    PS_QUERY                ,   // 機器種別/動作モード取得(QUERY_MODEL_AND_MODE)
    PS_VIBRATION_ENABLE     ,   // 振動モード許可(VIBRATION_ENABLE)
    PS_CMD_MAX
};
 
#define PS_ID_MAX       2       // PSXコマンドID長(2バイト)
#define PS_DATA_MAX     30      // PSXコマンドデータ最大長(30バイト以下)
 
typedef struct Psxid {
    byte    id;                 // 接続機器ID
    byte    config;             // 初期化要求フラグ(0x5a=通常、0=再初期化あり)
} PSXID;
 
// PSX送信コマンドテーブル
const byte ps_cmd[PS_CMD_MAX][PS_ID_MAX] =
{
 // PLAYSTATION Command Table
 { 0x42 ,0x00 },                // パッド状態読出し(READ_DATA)
 { 0x43 ,0x00 },                // コンフィグモード遷移(CONFIG_MODE_ENTER)
 { 0x43 ,0x00 },                // コンフィグモード終了(CONFIG_MODE_EXIT)
 { 0x44 ,0x00 },                // 動作モード/モード変更可否設定(SET_MODE_AND_LOCK)
 { 0x45 ,0x00 },                // 機器種別/動作モード取得(QUERY_MODEL_AND_MODE)
 { 0x4D ,0x00 }                 // 振動モード許可(VIBRATION_ENABLE)
};
 
// メガドライブパッド用ピン番号
#define     MD_D0       3       // PD0/Pin3
#define     MD_D1       2       // PD1/Pin2
#define     MD_D2       1       // PD2/Pin1(RX1)
#define     MD_D3       0       // PD3/Pin0(TX1)
#define     MD_D4       4       // PD4/Pin4
#define     MD_D5       6       // PD7/Pin6
#define     MD_SEL      5       // Select信号ポート番号
 
// PCEコントローラ制御ピン定義
#define     PC_D0       18      // PD0/Pin18
#define     PC_D1       19      // PD1/Pin19
#define     PC_D2       20      // PD2/Pin20
#define     PC_D3       21      // PD3/Pin21
#define     PC_SEL      15      // Select信号
#define     PC_EN       14      // /Enable信号
 
//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 __HORI_ADP
  // HORI PS4-100模擬(VID=0x0F0D、PID=0x00EE)
  #define       PAD_A       1   // A / ×ボタン
  #define       PAD_B       2   // B / ○ボタン
  #define       PAD_C       5   // C / R1ボタン
  #define       PAD_X       0   // X / □ボタン
  #define       PAD_Y       3   // Y / △ボタン
  #define       PAD_Z       4   // Z / L1ボタン
  #define       PAD_MODE    8   // MODE / SELECTボタン
  #define       PAD_START   9   // STARTボタン
  #define       PAD_L2      6   // - / L2ボタン(感圧:X回転/右)
  #define       PAD_R2      7   // - / R2ボタン(感圧:Y回転/右)
  #define       PAD_L3      10  // - / L3ボタン
  #define       PAD_R3      11  // - / R3ボタン
#elif __ELECOM_ADP
  // ELECOM USB変換アダプタ模擬(VID=0x0925、PID=0x8888)
  #define       PAD_A       2   // A / ×ボタン
  #define       PAD_B       1   // B / ○ボタン
  #define       PAD_C       6   // C / R1ボタン
  #define       PAD_X       3   // X / □ボタン
  #define       PAD_Y       0   // Y / △ボタン
  #define       PAD_Z       7   // Z / L1ボタン
  #define       PAD_MODE    9   // MODE / SELECTボタン
  #define       PAD_START   8   // STARTボタン
  #define       PAD_L2      4   // - / L2ボタン
  #define       PAD_R2      5   // - / R2ボタン
  #define       PAD_L3      10  // - / L3ボタン
  #define       PAD_R3      11  // - / R3ボタン
#elif __SEGA_ADP
  // SEGA 純正パッド模擬(VID=0x0CA3、PID=0x0024)
  #define       PAD_A       2   // Aボタン
  #define       PAD_B       1   // Bボタン
  #define       PAD_C       5   // Cボタン
  #define       PAD_X       3   // Xボタン
  #define       PAD_Y       0   // Yボタン
  #define       PAD_Z       4   // Zボタン
  #define       PAD_MODE    8   // MODEボタン
  #define       PAD_START   9   // STARTボタン
  #define       PAD_L2      6   // - / L2ボタン
  #define       PAD_R2      7   // - / R2ボタン
  #define       PAD_L3      10  // - / L3ボタン
  #define       PAD_R3      11  // - / R3ボタン
#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 / R1
    pSTART              ,   // START
    pMODE               ,   // MODE / SELECT
    pX                  ,   // X / □
    pY                  ,   // Y / △
    pZ                  ,   // Z / L1
    pSEL                ,   // SELECT(PSX/PCE)
    pIII                ,   // III(PCE 6B)
    pL2                 ,   // L2(PSX)
    pR2                 ,   // R2(PSX)
    pL3                 ,   // L3(PSX)
    pR3                 ,   // R3(PSX)
    pMAX
};
 
// 機種別ボタンチェック値(該当Bit位置をマスクする値)
const unsigned int pad_mask[tMAX][pMAX] =
{
 //  UP   ,DOWN   ,LEFT   ,RIGHT  ,A      ,B      ,C      ,START  ,MODE   ,X      ,Y      ,Z      ,SELECT ,III    ,L2     ,R2     ,L3     ,R3(MEGA DRIVE用ボタン名)
 //  UP   ,DOWN   ,LEFT   ,RIGHT  ,×     ,○     ,R1     ,START  ,SELECT ,□     ,△     ,L1     ,SELECT ,III    ,L2     ,R2     ,L3     ,R3(PLAY STATION用ボタン名)
 { 0x0001 ,0x0002 ,0x0004 ,0x0008 ,0x0040 ,0x0010 ,0x0020 ,0x0080 ,0x0800 ,0x0400 ,0x0200 ,0x0100 ,0x0000 ,0x0000 ,0x0000 ,0x0000 ,0x0000 ,0x0000},//MEGA DRIVE(tMD)
 { 0x0010 ,0x0040 ,0x0080 ,0x0020 ,0x4000 ,0x2000 ,0x0800 ,0x0008 ,0x0001 ,0x8000 ,0x1000 ,0x0400 ,0x0001 ,0x8000 ,0x0100 ,0x0200 ,0x0002 ,0x0004},//PLAY STATION(tPSX)
 { 0x0001 ,0x0004 ,0x0008 ,0x0002 ,0x0040 ,0x0020 ,0x0010 ,0x0080 ,0x0040 ,0x0200 ,0x0400 ,0x0800 ,0x0040 ,0x0100 ,0x0000 ,0x0000 ,0x0000 ,0x0000},//PC ENGINE/TG16(tPCE)
 { 0x0010 ,0x0020 ,0x0040 ,0x0080 ,0x0001 ,0x0100 ,0x0800 ,0x0008 ,0x0004 ,0x0002 ,0x0200 ,0x0400 ,0x0000 ,0x0000 ,0x0000 ,0x0000 ,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_L2,PAD_R2,PAD_L3,PAD_R3};
 
//PAD状態を前回と最新状態で比較して、変化があった場合にコードを送信する
unsigned int cur_sts[tMAX]; // 前回のボタン状態を保持する
 
// Joystickライブラリ定義
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_JOYSTICK,
  12, 1,                    // Button Count, Hat Switch Count
  true, true, true,         // X, Y, and Z Axis
  false, false, true,       // Rx, but no Ry or Rz
  false, false,             // No rudder or throttle
  false, false, false);     // No accelerator, brake, or steering
 
PSXID psx_id;               // PSX機種ID情報
byte psx_data[PS_DATA_MAX]; // PSX受信データ(最新値)
byte cur_psx_data[4];       // PSX受信データ(前回のアナログ値4バイト)
 
bool psx_config_flag = false;
byte psx_mode;              // 0:デジタル、1:アナログ、2ジョグコン、3:デュアルショック
#if __Debug
    ///////////////////////////////////////////////////////////////////////////////
void disp_psx(void)
{
    Serial.print("ID:0x");
    Serial.print(psx_id.id,HEX);
    Serial.print(" 0x");
    Serial.print(psx_id.config,HEX);
    
    int len = (psx_id.id&0xf)<<1;
    len=(len == 0xf) ? 2 : len;
    
    for ( int i=0; i<len ;i++)
    {
        Serial.print(" 0x");
        Serial.print(psx_data[i],HEX);
    }
    Serial.print("\n");
}
    ///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
 
/////////////////////////////////////////////////
// プレイステーションパッド送受信処理
// 入力: cmd :送付コマンド値
// 戻り: true=読出し正常、false=失敗(未接続など)
/////////////////////////////////////////////////
byte ps_trx(int cmd)
{
    byte data = 0;
    
    // 8Bit送受信処理
    for (int i = 0 ; i < 8 ; i++ )
    {
        //コマンド出力(1bit)
        digitalWrite(PS_CMD,(cmd &(1<<i)) ? HIGH : LOW );
        // Clock信号 LOW出力
        digitalWrite(PS_CLK, LOW);
        //4μs以上待つ
        delayMicroseconds(DELAY_TIME);
        //データ読出し(1bit)
        data |= digitalRead(PS_DATA)<<i;
        
        // Clock信号 HIGH出力
        digitalWrite(PS_CLK, HIGH);
        //4μs以上待つ
        delayMicroseconds(DELAY_TIME);
    }
    return data;
}
 
/////////////////////////////////////////////////
// プレイステーションパッド送受信
// 入力: type :コマンド種別(PS_READ 他)
// 出力構造体:PSXID psx_id、psx_data[]
// 戻り: true=正常、false=失敗
/////////////////////////////////////////////////
bool psx_cmd(int type)
{
byte send[PS_DATA_MAX];
bool sts = false;
    
    //送信データ初期化
    memset(send,0,PS_DATA_MAX);
    //送信データ編集
    switch(type)
    {
    case PS_CONFIG_MODE:
        send[0]=0x01;
        break;
    default:
        break;
    }
    //デバイス選択
    digitalWrite(PS_ATT, LOW);
    //コマンド送付
    ps_trx(0x01);
    psx_id.id = ps_trx(ps_cmd[type][0]);
    psx_id.config = ps_trx(ps_cmd[type][1]);
    
    // 受信IDが読めている場合(=0xff以外なら)、パッド状態を読み出す
    if ( psx_id.id != 0xff )
    {
        //データ長取り出し
        int data_len = (psx_id.id & 0xf)<<1;
        //パッド状態データ読出し
        for (int i = 0 ; i < data_len ; i++ )
        {
             psx_data[i]= ps_trx(send[i]);
        }
        //モード状態変更検知
        if ( psx_id.config == 0 )
        {
            //パッド未初期化とする
            psx_config_flag = false;
        }
        // アナログ→デジタル変化検知(アナログモードでデジタルパッドを検知した)
        if (( psx_mode>0 ) && ( type==PS_READ ) && ( psx_id.id==0x41 ))
        {
            //パッド未初期化とする
            psx_config_flag = false;
        }
        //正常完了
        sts = true;
    }
    else
    {
        //パッド未初期化とする
        psx_config_flag = false;
    }
    //デバイスアクセス終了
    digitalWrite(PS_ATT, HIGH);
    delayMicroseconds(DELAY_TIME);
#if __Debug
    ///////////////////////////////////////////////////////////////////////////////
    if(type!=PS_READ)
    {
        Serial.print("MODE");
        Serial.print(psx_mode,HEX);
        Serial.print(":CMD(");
        Serial.print(type,DEC);
        Serial.print(")0x");
        Serial.print(ps_cmd[type][0],HEX);
        Serial.print(" ");
        disp_psx();
    }
    ///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
 
    return sts;
}
 
/////////////////////////////////////////////////
// プレイステーションパッド初期化
// 入力
//  type :機種種別(tMD、tPSX、tPCE)
//  state = 方向キー状態
// 戻り
//  true=ボタン状態変化あり、false=変化なし
/////////////////////////////////////////////////
bool psx_config(void)
{
bool sts;
    //デジタルモードとする(初期値)
    psx_mode = 0;
 
    //コンフィグモード遷移(CONFIG_MODE_ENTER)
    if (sts = psx_cmd(PS_CONFIG_MODE))
    {
        //コマンド受理待ち
        delay(1);
        // 機器種別/動作モード取得(QUERY_MODEL_AND_MODE)
        sts |= psx_cmd(PS_QUERY);
        delay(1);
        // アナログモードである
        if((psx_id.id==0xf3)&&(psx_data[2]==0x01))
        {
            //アナログモード設定
            psx_mode = 1;
            
            //デュアルショックまたは、ジョグコンである
            if((psx_data[3]==0x01)||(psx_data[3]==0x02))
            {
                //ジョグコン・デュアルショックモード設定
                psx_mode += psx_data[3];
                
            }
        }
        // コンフィグモード終了(CONFIG_MODE_EXIT))
        sts |= psx_cmd(PS_CONFIG_EXIT);
    }
    
    return sts;
}
 
/////////////////////////////////////////////////
// 方向キー制御処理(機種共通)
// 入力
//  type :機種種別(tMD、tPSX、tPCE)
//  state = 方向キー状態
// 戻り
//  true=ボタン状態変化あり、false=変化なし
/////////////////////////////////////////////////
bool set_axis(int type ,unsigned int state )
{
    bool change = false;
#if __ELECOM_ADP || __HORI_ADP
    ///////////////////
    // HAT Switch制御
    ///////////////////
    int HatSts = -1;
    
    // 方向キーチェック用マスク値作成
    unsigned int mask = (pad_mask[type][pUP]|pad_mask[type][pDOWN]|pad_mask[type][pLEFT]|pad_mask[type][pRIGHT]);
    
    // 状態変化あり
    if (( cur_sts[type] & mask )!=( state & mask ))
    {
        change = true;
        
        // KEY押下状態比較用データ作成
        unsigned int key = (~state) & mask;
        
        // 上キー押下
        if (pad_mask[type][pUP] == key)
        {
            HatSts = 0;
        }
        // 上+右キー押下
        else if ((pad_mask[type][pUP] | pad_mask[type][pRIGHT]) == key)
        {
            HatSts = 45;
        }
        // 右キー押下
        else if (pad_mask[type][pRIGHT] == key)
        {
            HatSts = 90;
        }
        // 右+下キー押下
        else if ((pad_mask[type][pDOWN] | pad_mask[type][pRIGHT]) == key)
        {
            HatSts = 135;
        }
        // 下キー押下
        else if (pad_mask[type][pDOWN] == key)
        {
            HatSts = 180;
        }
        // 左+下キー押下
        else if ((pad_mask[type][pDOWN] | pad_mask[type][pLEFT]) == key)
        {
            HatSts = 225;
        }
        // 左キー押下
        else if (pad_mask[type][pLEFT] == key)
        {
            HatSts = 270;
        }
        // 左+上キー押下
        else if ((pad_mask[type][pUP] | pad_mask[type][pLEFT]) == key)
        {
            HatSts = 315;
        }
        // HAT Switch制御
        Joystick.setHatSwitch(0, HatSts);
        
        // ボタン状態を変更する
        cur_sts[type] = (cur_sts[type] & ~mask)|(state & mask);
    }
#else
    int axis = AXIS_CENTER;
    
    ///////////////////
    // 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);
    }
#endif //__ELECOM_ADP || __HORI_ADP
    
    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:
 
    // メガドライブパッド読み取り用ポート初期化(INPUT_PULLUP)
    pinMode(MD_D0, INPUT_PULLUP);
    pinMode(MD_D1, INPUT_PULLUP);
    pinMode(MD_D2, INPUT_PULLUP);
    pinMode(MD_D3, INPUT_PULLUP);
    pinMode(MD_D4, INPUT_PULLUP);
    pinMode(MD_D5, INPUT_PULLUP);
 
    // MD Select信号出力用ポート初期化(OUTPUT)
    pinMode(MD_SEL, OUTPUT);
 
    //プレイステーション制御用I/O初期化
    //DATA信号初期化
    pinMode(PS_DATA, INPUT_PULLUP);
    //CMD信号初期化
    pinMode(PS_CMD, OUTPUT);
    //ATT信号初期化(HIGH=非選択状態)
    pinMode(PS_ATT, OUTPUT);
    digitalWrite(PS_ATT, HIGH);
    //Clock信号初期化(HIGH=初期値)
    pinMode(PS_CLK, OUTPUT);
    digitalWrite(PS_CLK, HIGH);
 
    // PCEパッド読み取り用ポート初期化(INPUT_PULLUP)
    pinMode(PC_D0, INPUT_PULLUP);
    pinMode(PC_D1, INPUT_PULLUP);
    pinMode(PC_D2, INPUT_PULLUP);
    pinMode(PC_D3, INPUT_PULLUP);
 
    // PCE SEL信号出力用ポート初期化(OUTPUT)
    pinMode(PC_SEL,  OUTPUT);
    // PCE /EN信号出力用ポート初期化(OUTPUT:HIGH)
    pinMode(PC_EN,  OUTPUT);
    // /EN=HIGH(パッド無効)セット
    digitalWrite(PC_EN, HIGH);
 
    //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);
 
    // アナログ軸データ初期化
    Joystick.setXAxisRange(AXIS_MIN, AXIS_MAX);
    Joystick.setYAxisRange(AXIS_MIN, AXIS_MAX);
    Joystick.setZAxisRange(AXIS_MIN, AXIS_MAX);
    Joystick.setRzAxisRange(AXIS_MIN, AXIS_MAX);
    Joystick.setXAxis(AXIS_CENTER);
    Joystick.setYAxis(AXIS_CENTER);
    Joystick.setZAxis(AXIS_CENTER);
    Joystick.setRzAxis(AXIS_CENTER);
    //前回軸データ初期化
    memset(cur_psx_data,AXIS_CENTER,4);
 
    // パッド状態初期化
    cur_sts[tMD] = 0xffff;
    cur_sts[tPSX]= 0xffff;
    cur_sts[tPCE]= 0xffff;
    cur_sts[tSFC]= 0xffff;
 
#if __Debug
    Serial.begin(38400);
#endif //__Debug
}
 
//////////////////////////////////////
// メインループ
//////////////////////////////////////
void loop() {
// put your main code here, to run repeatedly:
    unsigned int cur_state = 0;
 
    /////////////////////////////////
    // MEGA DRIVE パッド状態読み出し
    /////////////////////////////////
    bool Pad6B = LOW;   //6Bパッド未検出
    unsigned int state = 0xffff;
    int sel = 0;
    // PAD情報取得(読取りシーケンス7回実行)
    for(  int i = 0 ; i < 7 ; i++ )
    {
        // Select信号出力
        digitalWrite(MD_SEL, !sel ? LOW : HIGH );
        delayMicroseconds(DELAY_TIME);
 
        // パッド状態読み出し
        cur_state = PIND;
 
        // 6Bパッド検出?
        if ( !sel && !(cur_state & 0xf) )
        {
            // 6Bパッド検出セット
            Pad6B = HIGH;
        }
        // 6Bパッド未検出
        if(Pad6B == LOW)
        {
            // パッド情報(3B/6B共通)を格納する
            if (sel)
                state = (state & 0xfc0)|((cur_state & 0x1f)|(cur_state & 0x80)>>2);
            else
                state = (state & 0xf3f)|(((cur_state & 0x10)<<2)|(cur_state & 0x80));
        }
        else
        {
            if (sel)
                // 6Bパッド情報を格納する
                state = (state & 0xf0ff)|((cur_state & 0xf)<<8);
        }
        // Select状態(Low/High)を反転する
        sel ^= 0x01;
    }
    ////////////////////////////////
    // Joystick制御
    ////////////////////////////////
    bool change = false;
    //方向キー制御
    change |= set_axis(tMD,state);
    // 各ボタン制御
    change |= set_button(tMD,state,pA);
    change |= set_button(tMD,state,pB);
    change |= set_button(tMD,state,pC);
    change |= set_button(tMD,state,pSTART);
    // 6Bパッド検出
    if(Pad6B)
    {
        change |= set_button(tMD,state,pMODE);
        change |= set_button(tMD,state,pX);
        change |= set_button(tMD,state,pY);
        change |= set_button(tMD,state,pZ);
    }
    // Joystick情報送信
    if(change)
    {
#if __Debug
    ///////////////////////////////////////////////////////////////////////////////
        Serial.print("MD :");
        Serial.print(state,BIN);
        Serial.print("\n");
        Serial.print("Joystick.sendState()\n");
    ///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
        Joystick.sendState();
    }
    
    ////////////////////////////////////////////
    //プレステコントローラ状態読み出し
    ////////////////////////////////////////////
    state = 0xffff;
    change = false;
    
    //パッド状態読み出し
    if (psx_cmd(PS_READ) == true)
    {
        //ボタン状態(デジタル)読み出し
        state = (psx_data[1]<<8)|psx_data[0];
        
        // ノーマル(デジタルモード)
        if(psx_id.id==0x41)
        {
            // 軸を中心に戻す
            memset(&psx_data[2],AXIS_CENTER,4);
        }
        // ネジコン
        if(psx_id.id==0x23)
        {
            //Lボタンデジタル化(閾値より上ならL1押下に変換する)
            if(psx_data[5]>0x20)
            {
                // L1押下(Bit11=OFF)
                state ^=0x0400;
            }
            //Y軸は中央固定
            psx_data[5]=AXIS_CENTER;
            //ねじり値を退避
            byte work=psx_data[2];
            //IIボタンをZ回転(0~255)とする
            psx_data[2]=psx_data[4];
            //ねじり値をX軸、Iボタンは右Z回転(0~255)
            psx_data[4]=work;
        }
        // ジョグコン
        if(psx_id.id==0xe3)
        {
            // JOGダイアル位置取得(16bit)+中央補正(+0x8000)
            unsigned int jog = ((psx_data[3]<<8)|psx_data[2])+(0x8000+AXIS_CENTER);
            // 最小チェック
            if (jog<=0x8000)
            {
                jog = AXIS_MIN;
            }
            // 最大チェック
            else if (jog>=(0x8000+AXIS_MAX))
            {
                jog = AXIS_MAX;
            }
            //JOG値をX軸に割り当てる(0~255)
            psx_data[4] = (byte)(jog & 0xff);
            //Y軸は中央固定
            psx_data[5]=AXIS_CENTER;
            
            //Z回転、右Z回転は中央値
            psx_data[2]=AXIS_CENTER;
            psx_data[3]=AXIS_CENTER;
        }
        // アナログ/デュアルショック他
        // アナログ値変化有無チェック
        for (int i=0 ; i < 4 ; i++ )
        {
            // 前回値と変化あり(閾値4)
            if(abs(cur_psx_data[i]-psx_data[i+2])>4)
            {
                change=true;
            }
        }
        // 変化ありならアナログ値更新
        if(change)
        {
            // ADC値セット
            Joystick.setXAxis(psx_data[4]);
            Joystick.setYAxis(psx_data[5]);
            Joystick.setZAxis(psx_data[2]);
            Joystick.setRzAxis(psx_data[3]);
 
            // アナログ受信データ更新
            memcpy(cur_psx_data,&psx_data[2],4);
        }
        //パッド未初期化?
        if (psx_config_flag == false)
        {
            //パッド初期化
            psx_config_flag = psx_config();
        }
    }
    // 方向キー(デジタル)制御
    change |= set_axis(tPSX,state);
    // 各ボタン(デジタル)制御
    change |= set_button(tPSX,state,pA);
    change |= set_button(tPSX,state,pB);
    change |= set_button(tPSX,state,pC);
    change |= set_button(tPSX,state,pSTART);
    change |= set_button(tPSX,state,pMODE);
    change |= set_button(tPSX,state,pX);
    change |= set_button(tPSX,state,pY);
    change |= set_button(tPSX,state,pZ);
    change |= set_button(tPSX,state,pL2);
    change |= set_button(tPSX,state,pR2);
    change |= set_button(tPSX,state,pL3);
    change |= set_button(tPSX,state,pR3);
    
    // Joystick情報送信
    if(change)
    {
#if __Debug
    ///////////////////////////////////////////////////////////////////////////////
        Serial.print("PSX :");
        Serial.print(state,BIN);
        Serial.print("\n");
        
        disp_psx();
        Serial.print("Joystick.sendState()\n");
    ///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
        Joystick.sendState();
    }
 
    ////////////////////////////////////////////
    //PCEコントローラ状態読み出し
    ////////////////////////////////////////////
    state = 0xffff;
    // 6Bパッド未検出
    Pad6B = LOW;
    // 6ボタン対応PAD情報取得(読み取りシーケンスを2回実施)
    for( int i = 0 ; i < 2 ; i++ )
    {
        //6B検出フラグ初期化(シーケンス毎にクリア)
        bool detect6b = LOW;
        
        // 読出しシーケンス開始(/EN=L)
        digitalWrite(PC_EN, LOW );
        
        // SEL=Hi
        digitalWrite(PC_SEL, HIGH );
        delayMicroseconds(DELAY_TIME);
        //パッド情報読出し(方向キーが入る)
        unsigned int cur_state = PINF & 0xf0;
        
        //6ボタンPAD検出(D0~D3全てLOW)?
        if (!cur_state)
        {
            // 6ボタンPAD検出セット(パッド情報は保存しない)
            detect6b = HIGH;
            Pad6B = HIGH;
        }
        else
        {
            //方向キー状態を格納
            state = (state & 0xff0)|(cur_state>>4);
        }
        //SEL=LOW
        digitalWrite(PC_SEL, LOW );
        delayMicroseconds(DELAY_TIME);
        //パッド情報読出し(ボタン情報が入る)
        cur_state = PINF & 0xf0;
        
        //シーケンスHiで6Bパッド検出
        if( detect6b )
        {
            // 6ボタン状態を格納
            state = (state & 0x0ff)|(cur_state<<4);
        }
        else
        {
            // 標準ボタン状態をに格納
            state = (state & 0xf0f)|cur_state;
        }
        // 読出しシーケンス終了(/EN=H)
        digitalWrite(PC_EN, HIGH );
    }
    change = false;
    //方向キー制御
    change |= set_axis(tPCE,state);
    // 各ボタン制御
    change |= set_button(tPCE,state,pB);
    change |= set_button(tPCE,state,pC);
    change |= set_button(tPCE,state,pSTART);
    // 6Bパッド未検出
    if(!Pad6B)
    {
        // 3BパッドはSELECTをAボタン指定
        change |= set_button(tPCE,state,pA);
    }
    else
    {
        // 6Bパッドボタン指定
        change |= set_button(tPCE,state,pX);
        change |= set_button(tPCE,state,pY);
        change |= set_button(tPCE,state,pZ);
        // 6BパッドはIIIをAボタンに割当
        change |= set_button(tPCE,state,pIII);
        // 6BパッドはSELECTボタンをMODEボタンに割当
        change |= set_button(tPCE,state,pSEL);
    }
    // Joystick情報送信
    if(change)
    {
#if __Debug
    ///////////////////////////////////////////////////////////////////////////////
    Serial.print("PCE :");
    Serial.print(state,BIN);
    Serial.print("\n");
    Serial.print("Joystick.sendState()\n");
    ///////////////////////////////////////////////////////////////////////////////
#endif //__Debug
        Joystick.sendState();
    }
    
    ////////////////////////////////////////////
    //SUPER FAMICOMコントローラ状態読み出し
    ////////////////////////////////////////////
    state = 0;
    // LATCH信号HIGH出力
    digitalWrite(SF_LATCH, HIGH);
    delayMicroseconds(DELAY_TIME*2);
    // 12μ秒後、LATCH信号LOWに戻す
    digitalWrite(SF_LATCH, LOW);
    delayMicroseconds(DELAY_TIME);
    // ボタン状態読出し(16Bit)
    for(int i = 0; i < 16; i++)
    {
        // CLOCK信号LOW出力
        digitalWrite(SF_CLOCK, LOW);
        delayMicroseconds(DELAY_TIME);
        // ボタン状態を(1Bitづつ)読み出す
        state |= digitalRead(SF_DATA) << i;
        // CLOCK信号をHIGHに戻す
        digitalWrite(SF_CLOCK, HIGH);
        delayMicroseconds(DELAY_TIME);
    }
    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);
}