/*--------------------------------------------------------
 メガドライブパッド→USB変換
 
 注意:
 このスケッチは、Arduino Leonardo/Pro Micro(ATmega 32U4)専用です。
 パッド側のピン読み出しを最速で行うためポート(PinD)アクセスを利用しているため、
 他マイコンと互換性がありません。
[参考資料]
 このスケッチ作成には以下の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
 2020.4.20 BLUE
--------------------------------------------------------*/
#include <Joystick.h>
 
// ライブラリ定義
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
 
#define     AXIS_MIN    0   // アナログ方向キー最小値
#define     AXIS_MAX    511 // アナログ方向キー最大値
#define     AXIS_CENTER 255 // アナログ方向キーセンター位置
 
// PADキー番号定義
#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ボタン
 
// メガドライブパッド用ポート番号
#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     SELECT      5   // Select信号ポート番号
 
#define     DELAY_TIME  10  // 遅延時間(10μs/10ms)
 
//メガドライブ3Bパッド定義
enum{
        PAD_UP  =   0   ,   // UP 方向キー
        PAD_DOWN        ,   // DOWN 方向キー
        PAD_LEFT        ,   // LEFT 方向キー
        PAD_RIGHT       ,   // RIGHT 方向キー
        PAD_AB          ,   // A/B Buton
        PAD_DUMMY1      ,   // dummy
        PAD_DUMMY2      ,   // dummy
        PAD_CSTART      ,   // C/Start Buton
        PAD_MAX
};
 
//メガドライブ6Bパッド定義
enum{
        PAD6B_Z =   0   ,   // Z Buton
        PAD6B_Y         ,   // Y Buton
        PAD6B_X         ,   // X Buton
        PAD6B_MODE      ,   // Mode Buton
        PAD6B_MAX
};
 
//メガドライブSelect信号定義
enum{
        SelLo   =   0   ,   // Select = Low
        SelHi           ,   // Select = High
        SelMAX
};
 
const int pad_map[SelMAX][PAD_MAX] =
{
 // D0       D1       D2       D3       D4      -  -    D7
 { PAD_Z   ,PAD_Y   ,PAD_X   ,PAD_MODE ,PAD_A  ,-1,-1 ,PAD_START},//Lo(3B/6B)
 { PAD_UP  ,PAD_DOWN,PAD_LEFT,PAD_RIGHT,PAD_B  ,-1,-1 ,PAD_C    } //Hi(3B)
};
 
//PAD状態を前回と最新状態で比較して、変化があった場合にコードを送信する
bool cur_3B_sts[SelMAX][PAD_MAX];   //PAD状態(3B/6B共通)
bool cur_6B_sts[SelMAX][PAD6B_MAX]; //PAD状態(6B用)
 
int cur_3B_PinD[SelMAX];    // 3B/6B共通PIND状態(LOW/HIGH)
int cur_6B_PinD[SelMAX];    // 6B用PIND状態(LOW/HIGH)
 
bool check_button(int sel ,int pad_num);
bool check_button_6b(int pad_num);
 
//////////////////////////////////////
// パッド制御処理(3B/6B共通)
// sel = LOW/HIGH
// pad_num = 3B PAD定義
//////////////////////////////////////
bool check_button(int sel ,int pad_num)
{
int pad;
int state;
 
    // ボタン状態取得
    state = ((cur_3B_PinD[sel]>>pad_num)&0x01) ? HIGH : LOW;
 
    // ボタン状態変化あり
    if (cur_3B_sts[sel][pad_num] != state)
    {
        // PAD制御
        pad = pad_map[sel][pad_num];
        
        // ボタン押下
        if (state==LOW)
        {
            // 方向キーである
            if ((sel==HIGH)&&(pad_num<=PAD_RIGHT))
            {
                switch(pad)
                {
                case PAD_UP:
                    // Y軸を最小
                    Joystick.setYAxis(AXIS_MIN);
                    break;
                case PAD_DOWN:
                    // Y軸を最大
                    Joystick.setYAxis(AXIS_MAX);
                    break;
                case PAD_LEFT:
                    // X軸を最小
                    Joystick.setXAxis(AXIS_MIN);
                    break;
                case PAD_RIGHT:
                    // X軸を最大
                    Joystick.setXAxis(AXIS_MAX);
                    break;
                default:
                    break;
                }
            }
            else
            {
                // ボタン押下セット
                Joystick.pressButton(pad);
            }
        }
        // ボタン離した
        else
        {
            // 方向キーである
            if ((sel==HIGH)&&(pad_num<=PAD_RIGHT))
            {
                switch(pad)
                {
                case PAD_UP:
                case PAD_DOWN:
                    // Y軸をセンターに戻す
                    Joystick.setYAxis(AXIS_CENTER);
                    break;
                case PAD_LEFT:
                case PAD_RIGHT:
                    // X軸をセンターに戻す
                    Joystick.setXAxis(AXIS_CENTER);
                    break;
                default:
                    break;
                }
            }
            else
            {
                // ボタン離す
                Joystick.releaseButton(pad);
            }
        }
        // カレントのPAD状態を更新する
        cur_3B_sts[sel][pad_num] = state;
        return true;
    }
    
    return false;
}
//////////////////////////////////////
// 6Bパッド制御処理
// pad_num = 6B PAD定義
//////////////////////////////////////
bool check_button_6b(int pad_num)
{
int pad;
int state;
 
    // ボタン状態取得
    state = ((cur_6B_PinD[SelHi]>>pad_num)&0x01) ? HIGH : LOW;
 
    // ボタン状態変化あり
    if (cur_6B_sts[SelHi][pad_num] != state)
    {
        // PAD制御
        pad = pad_map[SelLo][pad_num];
        // ボタン押下
        if (state==LOW)
            Joystick.pressButton(pad);
        // ボタン離した
        else
            Joystick.releaseButton(pad);
        
        // カレントのPAD状態を更新する
        cur_6B_sts[SelHi][pad_num] = state;
        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);
 
    // Select信号出力用ポート初期化(OUTPUT)
    pinMode(SELECT, OUTPUT);
 
    // 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);
}
 
//////////////////////////////////////
// メインループ
//////////////////////////////////////
void loop() {
// put your main code here, to run repeatedly:
 
int  cur_state;
int  sel = 0;
bool change=false;  //JoyStick状態変化なし(通信制御しない)
bool Pad6B = LOW;   //6Bパッド未検出
 
    /////////////////////////////////
    // MEGA DRIVE パッド状態読み出し
    /////////////////////////////////
    
    // PAD情報取得(7シーケンス実施)
    for( int i = 0 ; i < 7 ; i++ )
    {
        // Select信号出力
        digitalWrite(SELECT, (sel==0) ? LOW : HIGH );
        delayMicroseconds(DELAY_TIME);
 
        // パッド状態読み出し
        cur_state = PIND;
 
        // 6Bパッド検出?
        if ( !sel && !(cur_state & 0xf) )
        {
            // 6Bパッド検出セット
            Pad6B = HIGH;
        }
        // 6Bパッド未検出
        if(Pad6B == LOW)
        {
            // パッド情報(3B/6B共通)に格納する
            cur_3B_PinD[sel] = cur_state;
        }
        else
        {
            // 6Bパッド情報を格納する
            cur_6B_PinD[sel] = cur_state;
        }
        // Select状態(Low/High)を反転する
        sel ^= 0x01;
    }
 
    ////////////////////////////////
    // Joystick制御
    ////////////////////////////////
    
    // Startボタン制御
    change |= check_button(SelLo,PAD_CSTART);
    
    // Aボタン制御
    change |= check_button(SelLo,PAD_AB);
 
    // Bボタン制御
    change |= check_button(SelHi,PAD_AB);
 
    // Cボタン制御
    change |= check_button(SelHi,PAD_CSTART);
    
    // 6Bパッド検出
    if(Pad6B)
    {
        // MODEボタン制御
        change |= check_button_6b(PAD6B_MODE);
        
        // Xボタン制御
        change |= check_button_6b(PAD6B_X);
        
        // Yボタン制御
        change |= check_button_6b(PAD6B_Y);
        
        // Zボタン制御
        change |= check_button_6b(PAD6B_Z);
    }
    // UP 方向キー制御
    change |= check_button(SelHi,PAD_UP);
    
    // DOWN 方向キー制御
    change |= check_button(SelHi,PAD_DOWN);
    
    // LEFT 方向キー制御
    change |= check_button(SelHi,PAD_LEFT);
    
    // RIGHT 方向キー制御
    change |= check_button(SelHi,PAD_RIGHT);
 
    // Joystick情報送信
    if(change)
    {
        Joystick.sendState();
    }
    // 次のシーケンスまで待つ(2ms以上)
    delay(DELAY_TIME);
}