NFCカードリーダー(RC-S620/S)とリレーを組み合わせ、日本の運転免許証をタッチしないとエンジンがかからない制御をします。動画でご紹介した無免許運転防止・免許不携帯防止システムのソースコードです。

Dependencies:   mbed ATP301x_SPI PwmBeep RCS620_AB

main.cpp

Committer:
hmizuno
Date:
2020-05-29
Revision:
0:8e6f778abf78
Child:
1:d52c0ea6e922

File content as of revision 0:8e6f778abf78:

/**
* DLC(Driver's License Card)スターター Standard
*
* 青mbed用 RTCを使用する標準バージョン
*
* 2020/5/29 Ver 0.9
* 
**/


//公式ヘッダファイル
#include "mbed.h"
#include "stdint.h"
#include "string.h"
#include "Stream.h"
#include "time.h"

//コマンドは別ファイルへ保存
#include "dlcCommand.h"

//自作ライブラリ
#include "ATP301x_SPI.h"
#include "PwmBeep.h"
#include "RCS620S_AB.h"

//TeraTermへデバッグ表示する場合1、しない場合0
#define SHOW_DEBUG 0

//音声合成LSIを使う場合1、使わない場合0
#define USE_ATP301X 1

//警告・メッセージビープ周波数
#define BEEP_FREQ 880

//始動OKビープ周波数
#define BEEP_FREQ_AFTER_READ 1320

//有効期限当日の何時を期限切れとして扱うか(24h表記)
#define KIGENGIRETOSURUJIKOKU_H 12


/****************************/
/* グローバル変数とハードウェア */
/****************************/


//デバッグ用PC(mbed公式サイトからドライバのダウンロードが必要です)
//ドライバ:https://os.mbed.com/docs/mbed-os/v5.15/tutorials/windows-serial-driver.html
Serial pc(USBTX,USBRX);

//カードリーダー
RCS620S_AB rcs620(p28,p27);

//音声合成LSI
ATP301x_SPI atp301x(p11, p12, p13, p14);

//ビープ(圧電スピーカー)出力
PwmBeep beep(p22);

//スターター遮断リレー制御出力
DigitalOut relay(p30);

//警告ブザー入力
InterruptIn ig_start_sw(p23);

//LED
DigitalOut myled1(LED1);
DigitalOut myled2(LED2);
DigitalOut myled3(LED3);
DigitalOut myled4(LED4);

//共通データ要素の中身整理用構造体
//※有効期限しか使いませんが、RFUとして全てのデータを整理しておきます。
typedef struct{
    uint8_t shiyoVer[3];
    uint8_t kofuYYYYMMDD[4];
    uint8_t yukoYYYYMMDD[4];
}mfDataStruct;

/****************************************************************************/

/******************/
/* プロトタイプ宣言 */
/******************/

//制御系
void allowDrive();
void disallowDrive();
void reset();
void announcePleaseTouch();
void igswRiseIrq();
void errorBeep(int);

//送信系
void sendSELECTbyAID(const uint8_t*, bool);

//検査系
bool checkATQB(uint8_t*, int);
bool checkSELECTres(uint8_t*, int);
bool checkREAD_BINARYres(uint8_t*, int);
bool isEfectiveLicenseCard(tm);

//時刻系
void setRTCfromUSBPC();
void setDummyRTC();
bool isSetRTC();
int packedBCDtoInt(uint8_t);
tm packedBCDdata_to_tmStruct(uint8_t*);
mfDataStruct mfData_toStruct(uint8_t*, int);

//表示機能
void debugPrintAllRxData();
void debugPrintATQB(uint8_t*);
void debugPrintMFdata(uint8_t*);

/****************************************************************************/

/************/
/*** main ***/
/************/

int main() {
//起動処理
    disallowDrive();    
    bool canDrive = false;
        
    char atpbuf[ATP_MAX_LEN];
    
    uint8_t* resArray = new uint8_t[rcs620.RETURN_ENVELOPE_SIZE];
    int resLen;
    
    ig_start_sw.mode (PullDown);
    
    //内蔵RTC(リアルタイムクロックモジュール)がリセットされていたら時刻設定モードへ入る
    if(!isSetRTC()){
        //setRTCfromUSBPC();
        
        //とりあえず時計はダミーデータを設定
        setDummyRTC();
    }
    
    while(!canDrive){
        beep.setFreq(BEEP_FREQ);
        reset();
        
        //マイコン起動より早くキーひねると割り込みかからないから初回手動でブザー鳴らす
        if(ig_start_sw.read()){
            beep.turnOn();
        }
        
        ig_start_sw.rise(&igswRiseIrq);
        ig_start_sw.fall(callback(&beep, &PwmBeep::turnOff));
        
        //音声合成「免許証をタッチしてください」
        announcePleaseTouch();
    
    //NFC Type-Bをポーリング
        if(SHOW_DEBUG){pc.printf("Polling NFC-TypeB\r\n");}
        
        rcs620.sendInListPassiveTarget_typeB();
        
        //警報割り込み解除
        ig_start_sw.rise(callback(&beep, &PwmBeep::turnOff));
        beep.turnOff();
        
        if(USE_ATP301X){
            //タッチ音「ポーン」
            atp301x.chimeJ(false);
        }else{
            //ビープ「ピッ」
            beep.oneshotOn(0.1);    
        }
        
        //ポーリングレスポンス読み出し
        rcs620.getCardRes(resArray, &resLen);
        
        debugPrintAllRxData();
        debugPrintATQB(resArray);
        
    //免許証判定STEP1 ATQBをチェック
    
        if(!checkATQB(resArray, resLen)){
            //ビープ「ピーピー」
            errorBeep(2);
            if(USE_ATP301X){
                //音声合成「ATQB応用データ照合失敗」
                atp301x.talk("<ALPHA VAL=ATQB>/o-yo-de'-ta/sho-go-shippai.");
            }
            continue;
        }
        
    //免許証判定STEP2 3つのAIDが存在するかチェック
    
        //DF1があるか確認
        sendSELECTbyAID(dlc_DF1_AID, false);

        rcs620.getCardRes(resArray, &resLen);
        
        if(SHOW_DEBUG){pc.printf("\r\nDF1 AID check All Received Packet = \r\n");}
        debugPrintAllRxData();   
        
        if(!checkSELECTres(resArray, resLen)){
            //ビープ「ピーピーピー」
            errorBeep(3);
            if(USE_ATP301X){
                //音声合成「DF1 照合失敗」
                atp301x.talk("<ALPHA VAL=DF1>/sho-go-shippai.\r\0");
            }
            continue;
        }
        
        //AID2があるか確認
        sendSELECTbyAID(dlc_DF2_AID, false);
        rcs620.getCardRes(resArray, &resLen);
    
        if(SHOW_DEBUG){pc.printf("\r\nDF2 AID check All Received Packet = \r\n");}
        debugPrintAllRxData();
    
        if(!checkSELECTres(resArray, resLen)){
            //ビープ「ピーピーピー」
            errorBeep(3);
            if(USE_ATP301X){
                //音声合成「DF2 照合失敗」
                atp301x.talk("<ALPHA VAL=DF2>/sho-go-shippai.\r\0");
            }
            continue;
        }
    
        //AID3があるか確認
        sendSELECTbyAID(dlc_DF3_AID, false);
        rcs620.getCardRes(resArray, &resLen);
        
        if(SHOW_DEBUG){pc.printf("\r\nDF3 AID check All Received Packet = \r\n");}
        debugPrintAllRxData();
        
        if(!checkSELECTres(resArray, resLen)){
            //ビープ「ピーピーピー」
            errorBeep(3);
            if(USE_ATP301X){
                //音声合成「DF3 照合失敗」
                atp301x.talk("<ALPHA VAL=DF3>/sho-go-shippai.\r\0");
            }
            continue;
        }

    //ここまで来られたらタッチされたカードは免許証!
    //共通データ要素読み出し
        
        //MFを選択
        rcs620.sendInDataExchange_and_RecieveRes(wireless_SELECT_MF,(uint16_t)sizeof(wireless_SELECT_MF));
        rcs620.getCardRes(resArray, &resLen);
    
        if(SHOW_DEBUG){pc.printf("\r\nMF SELECT All Received Packet = \r\n");}
        debugPrintAllRxData();
        
        if(!checkSELECTres(resArray, resLen)){
            //ビープ「ピーピーピーピー」
            errorBeep(4);
            if(USE_ATP301X){
                //音声合成「MF選択失敗」
                atp301x.talk("<ALPHA VAL=MF>se'nnta_ku/shippai.\r\0");
            }
            continue;
        }
    
    
        //MF内のEF01を選択
        rcs620.sendInDataExchange_and_RecieveRes(wireless_SELECT_EF01_whileMFselected,(uint16_t)sizeof(wireless_SELECT_EF01_whileMFselected));
        rcs620.getCardRes(resArray, &resLen);
        
        if(SHOW_DEBUG){pc.printf("\r\nEF01 SELECT All Received Packet = \r\n");}
        debugPrintAllRxData();
        
        if(!checkSELECTres(resArray, resLen)){
            //ビープ「ピーピーピーピー」
            errorBeep(4);
            if(USE_ATP301X){
                //音声合成「EF01選択失敗」
                atp301x.talk("<ALPHA VAL=EF01>/sennta_ku/shippai.\r\0");
            }
            continue;
        }
        
        
        //EF01(共通データ要素)をREAD BINARY
        rcs620.sendInDataExchange_and_RecieveRes(wireless_READ_BINARY_noOffset_255,(uint16_t)sizeof(wireless_READ_BINARY_noOffset_255));
        rcs620.getCardRes(resArray, &resLen);
    
        if(SHOW_DEBUG){pc.printf("\r\nREAD BINARY All Received Packet = \r\n");}
        debugPrintAllRxData();
    
        //ステータスが正常か確認
        if(!checkREAD_BINARYres(resArray, resLen)){
            //ビープ「ピーピーピーピーピー」
            errorBeep(5);
            if(USE_ATP301X){
                //音声合成「共通データ要素読み取り失敗」
                atp301x.talk("kyo-tsu-de-tayo'-so/yomitori/shippai.\r\0");
            }
            continue;
        }
                      
        //データ長が短すぎないか確認
        //※将来的に暗証番号の領域を使えるようcheckREAD_BINARYres関数の外で確認してます
        if(resLen < 19){
            if(USE_ATP301X){
                //音声合成「共通データ要素読み取り失敗」
                atp301x.talk("kyo-tsu-de-tayo'-so/yomitori/shippai.\r\0");
            }
            continue;
        }
        
        
        debugPrintMFdata(resArray);
    
    //共通データ要素の中身チェック
    
        //共通データ要素の中身を分割して構造体へ保存
        mfDataStruct mfData;
        mfData = mfData_toStruct(resArray, resLen);
        
        //パック2進化10進数形式の有効期限をC標準の tm 構造体へ変換
        tm yukoKigen_tm;
        yukoKigen_tm = packedBCDdata_to_tmStruct(mfData.yukoYYYYMMDD);
                  
        if(isEfectiveLicenseCard(yukoKigen_tm)){
            allowDrive();
            
            //ループ終わり
            canDrive = true;
            
            //ビープ「ピッ(高音)」
            beep.setFreq(BEEP_FREQ_AFTER_READ);
            beep.oneshotOn(0.1);
        }else{
            if(USE_ATP301X){
                //音声合成「有効期限が切れいてるか、時計がズレています」
                atp301x.talk("yu-ko-ki'gennga/ki'rete+iru'ka toke-ga/zu'rete+ima_su.\r\0");

            //RTC時刻読み上げ
                time_t seconds = time(NULL); //read RTC as UNIX time data
                struct tm *t = localtime(&seconds);
        
                //音声合成「RTC時刻は」
                atp301x.talk("a-ruthi-shi'-/ji'kokuwa.\r\0");
                //音声合成「_年_月_日」
                sprintf(atpbuf,"<NUMK VAL=%d COUNTER=nenn>/<NUMK VAL=%d COUNTER=gatu>/<NUMK VAL=%d COUNTER=nichi>.\r",t->tm_year+1900,t->tm_mon+1,t->tm_mday);
                atp301x.talk(atpbuf);
                //音声合成「_時_分_秒です」
                sprintf(atpbuf,"<NUMK VAL=%d COUNTER=ji>/<NUMK VAL=%d COUNTER=funn>/de_su.\r",t->tm_hour,t->tm_min);
                atp301x.talk(atpbuf);
            }
        }
        
    //有効期限読み上げ
    
        if(USE_ATP301X){
            //音声合成「有効期限は_年_月_日です」
            sprintf(atpbuf,"yu-ko-ki'gennwa <NUMK VAL=%d COUNTER=nenn>/<NUMK VAL=%d COUNTER=gatu>/<NUMK VAL=%d COUNTER=nichi>/de_su.\r",
                        yukoKigen_tm.tm_year+1900, yukoKigen_tm.tm_mon+1, yukoKigen_tm.tm_mday);
            atp301x.talk(atpbuf,true);
        }
    }//while終わり
    
    
    //カードリーダの電源OFF
    rcs620.powerDown();
}

/****************************************************************************/

/*************/
/*** 制御系 ***/
/*************/

//エンジン始動OKの処理
void allowDrive(){
    //処理を変えたい場合書き換えてください
    relay = 1;
    myled1 = 1;
    myled2 = 0;
    myled3 = 0;
    myled4 = 1;
}

//エンジン始動NGの処理
void disallowDrive(){
    relay = 0;
    myled1 = 0;
    myled2 = 1;
    myled3 = 1;
    myled4 = 0;
}

void reset(){
    rcs620.reset();
}

//「免許証をタッチしてください」アナウンス
void announcePleaseTouch(){
    //音声合成「免許証をタッチしてください」
    if(USE_ATP301X){
        atp301x.talk("mennkyo'sho-o/ta'cchi/shitekudasa'i.\r\0",false);
    }
    if(!ig_start_sw.read()){
        beep.NshotOn(3, 0.07, 0.03);
    }
}

//無免許警報ブザー処理
void igswRiseIrq(){
    beep.turnOn();
    announcePleaseTouch();
}

//USE_ATP301X 設定に応じビープモード切替
//ATP3011をしゃべらせる場合はatp3011でwait()、しゃべらせない場合はbeepでwait()
void errorBeep(int shotnum){
    if(USE_ATP301X){
        beep.NshotOn(shotnum, 0.2, 0.1);
    }else{
        beep.NshotOnwithWait(shotnum, 0.2, 0.1);
    }
}

/*************/
/*** 送信系 ***/
/*************/

//AIDからSELECTコマンドを作成しIndataexchanfeへ投げる
void sendSELECTbyAID(const uint8_t aid[], bool isGetFCI){
    
    uint8_t *wireless_command;
    
    if(isGetFCI){
        wireless_command = (uint8_t*)malloc(sizeof(uint8_t) * (21));
    }else{
        wireless_command = (uint8_t*)malloc(sizeof(uint8_t) * (22));
    }

    //0x00,0xA4,0x04,0x0C,0x10,AID_16byte
    //0x00,0xA4,0x04,0x00,0x10,AID_16byte,0x00
    
    wireless_command[0] = 0x00;
    wireless_command[1] = 0xA4;
    wireless_command[2] = 0x04;
    if(isGetFCI){
        wireless_command[3] = 0x00;
    }else{
        wireless_command[3] = 0x0C;
    }
    wireless_command[4] = 0x10;
    
    for(int i = 0; i < 16; i++){
        wireless_command[i + 5] = aid[i];
    }
    
    if(isGetFCI){
        wireless_command[21] = 0x00;
        rcs620.sendInDataExchange_and_RecieveRes(wireless_command, 22);
    }else{
        rcs620.sendInDataExchange_and_RecieveRes(wireless_command, 21);
    }
    
    free(wireless_command);
}


/*************/
/*** 検査系 ***/
/*************/

//ATQB応用データを免許証仕様書記載値と照合
bool checkATQB(uint8_t cardRes[], int len){
    if(len < 14){
        return false;
    }  
    //ATQB応用データが "00 00 00 00" であるか確認
    for(int j = 6; j<=9;j++){
        if(cardRes[j] != 0x00){
            return false;
        }
    }
    return true;
}

//SELECTに対する応答をチェック
bool checkSELECTres(uint8_t cardRes[], int len){
     if(len < 2){
        return false;
    }
    //カードのレスポンスが”90 00"(正常終了)または"62 83"(暗証番号間違えすぎてロック)であればフォルダが存在と判断
    if(cardRes[0] == 0x90 && cardRes[1] == 0x00){
        if(SHOW_DEBUG){pc.printf("Card Status OK! %02X %02X\r\n",cardRes[0],cardRes[1]);}
        return true;
    }else if(cardRes[0] == 0x62 && cardRes[1] == 0x83){
        if(SHOW_DEBUG){pc.printf("Card Status LOCKED but OK! %02X %02X\r\n",cardRes[0],cardRes[1]);}
        return true;
    }else{
        if(SHOW_DEBUG){pc.printf("Card Status ERROR! %02X %02X\r\n",cardRes[0],cardRes[1]);}
        return false;
    }
}

//READ_BINARYに対する応答をチェック
bool checkREAD_BINARYres(uint8_t cardRes[], int len){
    if(len < 2){
        return false;
    }
    
    //カードのレスポンスが”90 00"(正常終了)であるかチェック
    if(cardRes[len-2] == 0x90 && cardRes[len-1] == 0x00){
        if(SHOW_DEBUG){pc.printf("Card Status OK! %02X %02X\r\n",cardRes[len-2],cardRes[len-1]);}
        return true;
    }else{
        if(SHOW_DEBUG){pc.printf("Card Status ERROR! %02X %02X\r\n",cardRes[len-2],cardRes[len-1]);}
        return false;
    }
}

//免許証有効期限チェック
bool isEfectiveLicenseCard(tm yukoKigen){
    //unix時刻で比較する
    
    time_t currentUnixTime;
    currentUnixTime = time(NULL); //read RTC as UNIX time data

    //有効期限を当日指定のunix秒に変換
    struct tm yuko_t;
    time_t yuko_unixTime;

    yuko_t.tm_sec = 00;    // 0-59
    yuko_t.tm_min = 00;    // 0-59
    yuko_t.tm_hour = KIGENGIRETOSURUJIKOKU_H;   // 0-23
    yuko_t.tm_mday = yukoKigen.tm_mday;   // 1-31
    yuko_t.tm_mon = yukoKigen.tm_mon;     // 0-11
    yuko_t.tm_year = yukoKigen.tm_year;  // 1900年からの経過年
    yuko_unixTime = mktime(&yuko_t); 
    
  
    if(SHOW_DEBUG){
        //★重要★現在時刻はポインター変数!!!!!
        
        char buffer[32];
        strftime(buffer, 32, "%F %T\n", localtime(&currentUnixTime));
        pc.printf("CurrentTime = %s\r\n", buffer);
        
        strftime(buffer, 32, "%F %T\n", localtime(&yuko_unixTime));
        pc.printf("Yuko-Kigen  = %s\r\n", buffer);
    }

    if(currentUnixTime < yuko_unixTime){
        if(SHOW_DEBUG){
            pc.printf("You can drive!\r\n");
        }
        return true;
    }else{
        if(SHOW_DEBUG){
            pc.printf("You cannot drive!\r\n");
        }
        return false;
    }
}


/*************/
/*** 時間系 ***/
/*************/

//免許証の日付は、上位ビット・下位ビットで2桁を表す「パック2進化10進数」形式なのでintへ変換
int packedBCDtoInt(uint8_t input){
    uint8_t ten,one;
    int out = 0;
    
    ten = (input >> 4) & 0x0F;
    one = input & 0x0F;
    out = 10 * (int)ten + (int)one;

    return out;
}

//免許書のパック2進化10進数形式の日付データをtm構造体へ変換
tm packedBCDdata_to_tmStruct(uint8_t yyyymmdd[]){
    int year;
    int month;
    int day;
    tm out_tm;
    
    year = 100 * packedBCDtoInt(yyyymmdd[0]) + packedBCDtoInt(yyyymmdd[1]);
    month = packedBCDtoInt(yyyymmdd[2]);
    day = packedBCDtoInt(yyyymmdd[3]);
      
    out_tm.tm_year = year - 1900;   //1900年からの経過年
    out_tm.tm_mon = month - 1;  //1月からの月数 (0 ~ 11)
    out_tm.tm_mday = day;
    
    return out_tm;
}

//共通データ要素の中身を整理して構造体へ保存
mfDataStruct mfData_toStruct(uint8_t cardRes[], int len){
    mfDataStruct mf_Data;
    for(int i = 0; i < sizeof(mf_Data.shiyoVer);i++){
        mf_Data.shiyoVer[i] = cardRes[i + 2];
    }
    for(int i = 0; i < sizeof(mf_Data.kofuYYYYMMDD);i++){
        mf_Data.kofuYYYYMMDD[i] = cardRes[i + 5];
    }
    for(int i = 0; i < sizeof(mf_Data.yukoYYYYMMDD);i++){
        mf_Data.yukoYYYYMMDD[i] = cardRes[i + 9];
    }
    return mf_Data;
}

//RTCが設定されているか確認
bool isSetRTC(){
    time_t currentUnixTime;
    currentUnixTime = time(NULL); //unix時刻でRTCを取得
    if(localtime(&currentUnixTime)->tm_year == 70){ //1970年だったら未設定
        return false;
    }else{
        return true;
    }
}

//RTCにダミー時刻を設定(コンパイルする都度手動書き換え)
void setDummyRTC(){
    //時計確認
    struct tm t;
    time_t seconds;
    //時刻設定

    t.tm_sec = 0;   // 0-59
    t.tm_min = 0;   // 0-59
    t.tm_hour = 12; // 0-23
    t.tm_mday = 29; // 1-31
    t.tm_mon = 4;   // 0-11
    t.tm_year = 120; // 1900年からの経過年
    seconds = mktime(&t); 
    set_time(seconds);
        
    seconds = time(NULL);
    char buffer[32];
    strftime(buffer, 32, "%F:%I:%M %p\n", localtime(&seconds));
    pc.printf("Current Time Setting = %s\r\n", buffer);
}

//TeraTermからRTC時刻設定
void setRTCfromUSBPC(){

    pc.printf("Pleaase make sure that the backup-battery is connected to the VB terminal.\r\n");
    
    struct tm t;
    time_t seconds;
    //時刻設定

    t.tm_sec = 0;   // 0-59
    seconds = mktime(&t); 
    set_time(seconds);
    
    
    int input = 0;
    pc.printf("Please input Year(YYYY), and push Enter-key.\r\n");
    scanf("%d",&input);
    t.tm_year = input - 1900;
    input = 0;
    pc.printf("Please input Month(1-12), and push Enter-key.\r\n");
    scanf("%d",&input);
    t.tm_mon = input - 1;   // 0-11
    input = 0;
    pc.printf("Please input Day(1-31), and push Enter-key.\r\n");
    scanf("%d",&input);
    t.tm_mday = input;    // 1-31
    input = 0;
    pc.printf("Please input Hour(0-23), and push Enter-key.\r\n");
    scanf("%d",&input);
    t.tm_hour = input;   // 0-23
    input = 0;
    pc.printf("Please input Minute(0-59), and push Enter-key at 00 sec.\r\n");
    scanf("%d",&input);   // 0-59
    t.tm_min = input;

    t.tm_sec = 0;   // 0-59
    seconds = mktime(&t); 
    set_time(seconds);
    
    seconds = time(NULL);
    char buffer[32];
    strftime(buffer, 32, "%F:%I:%M %p\n", localtime(&seconds));
    pc.printf("Current Time Setting = %s\r\n", buffer);
}

/***************/
/*** 表示機能 ***/
/***************/

//レスポンスデータを全て表示
void debugPrintAllRxData(){
    if(SHOW_DEBUG){
        uint8_t res[rcs620.RETURN_ENVELOPE_SIZE];
        int len;
        rcs620.getAllRes(res, &len);

        //レスポンスデータの読み出し
        pc.printf("\r\nRC-S620 ACK ([ArrayIndex] HEX_DATA)\r\n");
        for(int i = 0; i <= 5 ; i++){
            pc.printf(" [%3d] %02X\r\n",i,res[i]);
        }
        pc.printf("\r\nRC-S620 Response ([ArrayIndex] HEX_DATA) \r\n");
        for(int i = 6; i < len ; i++){
            pc.printf(" [%3d] %02X\r\n",i,res[i]);
        }
        pc.printf("\r\n");
    }
}

//ATQBレスポンスを整理して表示
void debugPrintATQB(uint8_t cardRes[]){
    if(SHOW_DEBUG){
        pc.printf("--- TypeB_inListPassiveTarget_Res ---\r\n");
        pc.printf(" Tg = %02X\r\n",cardRes[0]);
        pc.printf(" ---------------ATQB----------------\r\n");
        pc.printf(" FirstByte = %02X\r\n",cardRes[1]);
        pc.printf(" PUPI = ");
        for(int j = 2; j<=5;j++){
            pc.printf("%02X ",cardRes[j]);
        }
        pc.printf("\r\n");
        pc.printf(" AppData = ");
        for(int j = 6; j<=9;j++){
            pc.printf("%02X ",cardRes[j]);
        }
        pc.printf("\r\n");
        pc.printf(" ProtocolInfo = ");
        for(int j = 10; j<=12;j++){
            pc.printf("%02X ",cardRes[j]);
        }
        pc.printf("\r\n");
        pc.printf(" ------------END of ATQB------------\r\n");  
        pc.printf(" ATTRIB_LEN = %02X\r\n",cardRes[13]);
        pc.printf(" ATTRIB_RES = ");
        for(int j = 14; j<14+(int)cardRes[13];j++){
            pc.printf("%02X ",cardRes[j]);
        }
        pc.printf("\r\n");
        pc.printf("------------END of RES------------\r\n");
    }
}

//共通データ要素を整理して表示
void debugPrintMFdata(uint8_t cardRes[]){
    if(SHOW_DEBUG){
        pc.printf("\r\n---------------------MF DATA--------------------\r\n");
        pc.printf("\r\n------------Card Hakko-sha Data------------\r\n");
        pc.printf(" Tag\r\n  %02X\r\n",cardRes[0]);
        pc.printf(" LEN\r\n  %02X\r\n",cardRes[1]);
        pc.printf(" Shiyo-sho Ver\r\n  ");
        for(int i = 2; i <= 4 ;i++){
            pc.printf("%02X ",cardRes[i]);
        }
        pc.printf("\r\n Ko-hunengappi\r\n  ");
        for(int i = 5; i <= 8; i++){
            pc.printf("%02X ",cardRes[i]);
        }
        pc.printf("\r\n Yu-ko-kigen\r\n  ");
        for(int i = 9; i < 12; i++){
            pc.printf("%02X ",cardRes[i]);
        }
        pc.printf("\r\n");
        pc.printf("\r\n------------Card Hakko-mae Data------------\r\n");
        pc.printf(" Tag\r\n  %02X\r\n",cardRes[13]);
        pc.printf(" LEN\r\n  %02X\r\n",cardRes[14]);
        pc.printf(" Seizo-gyo-sha shikibetsushi\r\n  %02X\r\n",cardRes[15]);
        pc.printf(" Ango-kansu- shikibetsushi\r\n  %02X\r\n",cardRes[16]);
        pc.printf("-----------------End of MF DATA-----------------\r\n\n");
    }
}