/*--------------------------------------------------------------------------
 PCエンジンパッド→USB変換(メガドライブミニ対応)
 
 メガドライブ用、プレイステーション用パッド(デジタルパッド)、PCエンジン用パッドを
 PCおよび、メガドライブミニで使えるようにUSBデバイスに変換します。
 
 注意:
 このスケッチは、Arduino Leonardo/Pro Micro(ATmega 32U4)専用です。
 パッド側のピン読み出しを最速で行うためポート(PinD)を直接アクセスしているため、
 他マイコンとピン位置の互換性がありません。ご注意ください。
[参考資料]
 このスケッチ作成には以下のURLを参考にしています。
 JoystickMD
 https://github.com/NibblesLab/JoystickMD
 メガドライブミニ を ジョイスティック で遊ぶ ジョイスティックアダプターの作成
 https://tounosumura302.netlify.app/posts/201910191643/
 Arduino Joystick Library
 https://github.com/MHeironimus/ArduinoJoystickLibrary
 
 NEC PC Engine - Tech Wiki 
 https://console5.com/wiki/NEC_PC_Engine
 
 TurboGrafx/Duo/PC-Engine Controller Data
 https://gamesx.com/controldata/turbocont.htm
 
 2020.4.29 BLUE
-----------------------------------------------------------------------------*/
#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) <-- 2以下はお勧めできない
 
// 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信号
 
#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
    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)
};
 
// パッドボタン定義と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
 
// PSXコントローラライブラリ定義
Psx Psx;
 
/////////////////////////////////////////////////
// 方向キー制御処理(機種共通)
// 入力
//  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
    }
    ///////////////////
    // 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] & 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:
 
    // 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)
    pinMode(PC_EN,  OUTPUT);
    // /EN=HIGH(パッド無効)セット
    digitalWrite(PC_EN, HIGH);
 
    // 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[tMD] = 0xffff;
    cur_sts[tPSX]= 0xffff;
    cur_sts[tPCE]= 0xffff;
#if __Debug
    Serial.begin(38400);
#endif //__Debug
}
 
//////////////////////////////////////
// メインループ
//////////////////////////////////////
void loop() {
// put your main code here, to run repeatedly:
    unsigned int cur_state = 0;
    
    ////////////////////////////////////////////
    //PCEコントローラ状態読み出し
    ////////////////////////////////////////////
    bool Pad6B = LOW;   //6Bパッド未検出
    unsigned int state = 0xffff;
    
    // 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 );
    }
    bool 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();
    }
    
    //次回ポーリング開始待ち
    delay(SCAN_TIME);
}