/*--------------------------------------------------------------------
  Sega CD Transfer Compatible Dumper for Arduino
  Arduino用 Sega CD Transfer対応 ダンプ処理(XMODEM送信)
 About this:
  You can dump via USB serial communication using Sega CD Transfer.
  Use the terminal software to receive the dump file via XMODEM communication.
  (COM Setting: 115200BPS,8bit,none,0,1)
  Sega CD Transferを使ってメガCDのダンプデータをUSB経由で読み出します。
  Teraterm等のターミナルソフトを使い、XMODEM通信でダンプファイルを受信します。
  (COM通信設定: 115200BPS,8bit,none,0,1)
 NOTE:
   This sketch file is for use with Arduino Leonardo/Arduino Pro Micro only.
   Please refer to the official page of sparkfun for how to use Pro Micro.
    https://learn.sparkfun.com/tutorials/pro-micro--fio-v3-hookup-guide/
 注意:
  このスケッチは、Arduino Leonardo/Pro Micro(ATmega 32U4搭載)専用です。
  メガドライブ側の読み出しを最速で行うためポート(PinD)アクセスを利用しており、
  他マイコンと互換性がありません。
[Reference]
 Sega CD Transfer Suite
   https://www.retrodev.com/transfer.html
 UCON64
   https://ucon64.sourceforge.io/
 I/O ポートの制御
   http://rio.la.coocan.jp/lab/driver24/004hardware.html
 XMODEM通信
   https://s-suzuki.hatenadiary.org/entry/20150104/1420390724
 
 2020.8.2 BLUE( qze12045@nifty.com )
--------------------------------------------------------------------*/
//For Debug
#define __XmodemTest    0       // 1:XMODEM test(send dummy data)
 
// Sega CD Transfer Control
#define     ERRWAIT     9000    // ERR WAIT
#define     ERR         0x10    // Error bit mask
 
#define WAIT_ERRLo(x) for(int i=0 ; i<x ; i++ ){if(!(PIND & ERR)) break;}
#define WAIT_ERRHi(x) for(int i=0 ; i<x ; i++ ){if(PIND & ERR) break;}
 
//  Sega CD Transfer Ports
#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_ERR      4       // PD4/Pin4 Error信号
#define     MD_SEL      6       // PD7/Pin6 Select信号
#define     MD_ALF      5       // PC6/Pin5 Auto Line Feed信号ポート番号
 
// XMODEM Parameter
#define     _BAUDRATE   115200  // 通信速度 115200Bps
#define     _TIMEOUT    1000    // 通信タイムアウト(1秒)
#define     _RETRY      60      // ACK/NAK受信リトライ回数
#define     _BLKSIZE    128     // 通信パケットサイズ(128Byte)
#define     _BLKRETRY   100     // ブロック送信リトライ回数
 
// XMODEM code
#define     SOH         0x01    // ブロック先頭
#define     EOT         0x04    // 通信終了
#define     ACK         0x06    // 通信正常
#define     NAK         0x15    // 通信開始/パケット再送付要求
#define     CAN         0x18    // 通信中断
 
// XMODEM packet
typedef struct xmodem {
    char    soh;                // SOH
    char    blk;                // Block Number
    char    _blk;               // ~Block Number(BLock番号補数)
    char    data[_BLKSIZE];     // data / 通信データ
    char    checksum;           // Checksum
} XMODEM;
 
XMODEM  buf;                    // send pachet / 送信バッファ
int     xmodem_phese = 0;       // 通信フェーズ(0:初回NAK待ち、1:データ送信中、2:EOT送信前、3:完了)
char    block;                  // ブロック番号
unsigned short  check_sum;      // チェックサム
 
// dump data buffer
char    data_buf[_BLKSIZE];     // ダンプデータ用バッファ
 
//////////////////////////////////////
// serial read (1byte)
// Received code (Timeout:-1)
//////////////////////////////////////
char recv_c(void)
{
    char code;
    for (int i=0 ; i<_RETRY ;i++)
    {
        Serial.readBytes(&code, 1);
        if ( (code != ACK) && (code!=NAK) )
        {
            // retry
            continue;
        }
        else
        {
            if((!xmodem_phese)&&(code!=NAK))
            {
                // retry
                continue;
            }
            return code;
        }
    }
    return -1;
}
 
//////////////////////////////////////
// initialize XMODEM
//////////////////////////////////////
void xmodem_init(void)
{
    // initial phase
    xmodem_phese = 0;
    // set first
    block = 1;
    // set timeout
    Serial.setTimeout(_TIMEOUT);
}
 
//////////////////////////////////////
// send XMODEM
//////////////////////////////////////
int xmodem_send(char *data)
{
char status;
    switch(xmodem_phese)
    {
    // first NAK
    case 0:
        if (recv_c()==NAK)
        {
            xmodem_phese = 1;
        }
        else
        {
            xmodem_phese = 2;
            return 1;
        }
    // XMODEM:Send Packet
    case 1:
        buf.soh = SOH;
        buf.blk = block;
        buf._blk = ~block;
        check_sum = 0;
        for( int i ; i < _BLKSIZE ; i++ )
        {
            buf.data[i] = data[i];
            check_sum += data[i];
        }
        buf.checksum = (char)(check_sum&0xff);
        
        for( int i ; i < _BLKRETRY ; i++ )
        {
            Serial.write((const char*)&buf, sizeof(buf));
            status = recv_c();
            // ACK(normal?)
            if (status == ACK)
            {
                block++;
                return 0;
            }
            // NAK(retry?)
            if (status == NAK)
            {
                continue;
            }
            // other(CAN?)
            else
                break;
        }
        // retry over(XMODEM:error)
        xmodem_phese = 2;
        return 1;
    default:
        break;
    }
    return 0;
}
 
//////////////////////////////////////
// XMODEM:finish
//////////////////////////////////////
void xmodem_end(void)
{
    if ( xmodem_phese == 3 )
    {
        return;
    }
    for( int i ; i < 5 ; i++ )
    {
        // send EOT
        Serial.write(EOT);
        char status = recv_c();
        // ACK(normal?)
        if( status == ACK )
        {
            break;
        }
        // NAK(retry?)
        if( status == NAK )
        {
            continue;
        }
    }
    xmodem_phese = 3;
    return;
}
 
//////////////////////////////////////
// Sega CD transfer:dump 1byte
//////////////////////////////////////
unsigned char byte_dump(void)
{
#if __XmodemTest
static unsigned char d = 0;
    return d++;
#endif //__XmodemTest
 
unsigned char   data=0;
 
    noInterrupts(); //Disable Interrupts
 
    // Auto Line Feed = Hi
    digitalWrite(MD_ALF, HIGH );
    // Wait ERR=Hi
    WAIT_ERRHi(ERRWAIT);
 
    data = PIND & 0xf;
 
    // Auto Line Feed = Lo
    digitalWrite(MD_ALF, LOW );
    // Wait  ERR=Lo
    WAIT_ERRLo(ERRWAIT);
 
    // Auto Line Feed = Hi
    digitalWrite(MD_ALF, HIGH );
    // Wait ERR=Hi
    WAIT_ERRHi(ERRWAIT);
 
    data |= (PIND & 0xf)<<4;
 
    // Auto Line Feed = Lo
    digitalWrite(MD_ALF, LOW );
    //  Wait ERR=Lo
    WAIT_ERRLo(ERRWAIT);
 
    interrupts(); //Enable Interrupts
 
    return data;
}
 
//////////////////////////////////////
// read dump size
//////////////////////////////////////
unsigned char read_dump_size(void)
{
#if __XmodemTest
static unsigned char d = 0;
    return d++;
#endif //__XmodemTest
    // Sega CD Transfer connect check
    while (PIND & ERR){}
    return byte_dump();
}
 
//////////////////////////////////////
// initialize
//////////////////////////////////////
void setup() {
// put your setup code here, to run once:
 
    // initialize port(INPUT_PULLUP)
    pinMode(MD_D0, INPUT_PULLUP);
    pinMode(MD_D1, INPUT_PULLUP);
    pinMode(MD_D2, INPUT_PULLUP);
    pinMode(MD_D3, INPUT_PULLUP);
    pinMode(MD_ERR, INPUT_PULLUP);
    pinMode(MD_SEL, INPUT_PULLUP);
 
    // Auto Line Feed(OUTPUT)
    pinMode(MD_ALF, OUTPUT);
    // Auto Line Feed = Lo
    digitalWrite(MD_ALF, LOW );
 
    Serial.begin(_BAUDRATE);
    // Arduino Leonardo/Arduino Pro Micro only.
    while (!Serial){}
}
 
//////////////////////////////////////
// main loop
//////////////////////////////////////
void loop() {
// put your main code here, to run repeatedly:
 
long dump = 0;  // count of dump data
long size = 0;  // size of dump data
int index = 0;  // index of data_buf[]
 
    Serial.print("Sega CD Transfer Compatible Dumper for Arduino.\r\n");
    Serial.print("Waiting for Respons from Genesis/MegaDrive...\r\n");
    
    size = (long)read_dump_size();
 
    Serial.print("Link Archved.  Dumping ");
    Serial.print(size,DEC);
    Serial.print(" Megabits...\r\n");
 
    size = (size+1)<<16;
    
    Serial.print("\r\nPlease start receiving XMODEM protocol.\r\n");
    xmodem_init();
 
    while (dump < size)
    {
        data_buf[index++] = byte_dump();
        if( index >= _BLKSIZE )
        {
            if( xmodem_send(data_buf)!=0 )
            {
                xmodem_end();
            }
            index = 0;
        }
        dump++;
    }
    xmodem_end();
 
    Serial.print("Dump data send finished.\r\n\r\n");
}