![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
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(¤tUnixTime)); 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(¤tUnixTime)->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"); } }