2019年7月12日 星期五

Mifare RFID-RC522模組實驗(一):讀取Mifare RFID卡的UID識別碼


本文旨在補充《超圖解Arduino互動設計入門》第18章「RFID無線識別裝置與問答遊戲製作」單元,書本採用的RFID讀卡機模組是採用9600bps, TTL序列通訊介面,RFID的通訊頻率為125KHz。這種模組的接線和程式都很簡單,每當感測到RFID卡,讀卡機就把卡片的識別碼送往序列埠。
市售Arduino套件裡面的RFID模組,多半是Mifare(讀音:my-fare)規格,跟書本的不一樣,因此本文將補充說明Mifare的基本原理及其模組的接線,以及讀取卡片識別碼的程式。
Mifare是NXP(恩智普)半導體公司推出的非接觸型IC卡(也就是外表沒有金屬接點的卡片),在市場上獲得廣泛的採用,像是停車場的感應幣(token)、現金卡(如:台灣的悠遊卡)、員工識別證…等等。Mifare卡有不同的系列,如:Mifare Classic, Mifare UltraLight, Mifare Pro…等,主要的差別在於資料安全加密和驗證的等級。
Mifare卡內建EEPROM,應用程式可對它寫入和讀取數據,例如:停車票卡可紀錄車主的停車時間。Mifare還具備「防衝突處理」機制,也就是避免訊號干擾:若多張卡片同時出現在偵測範圍,Mifare讀寫器將能逐一選擇卡片進行處理。例如,假設超級市場裡的每個商品都貼上RFID標籤,結帳櫃台的RFID讀取設備若具備防干擾機制,將能自動掃描堆放在購物推車裡的所有商品,結帳人員不必再手動逐一掃描商品條碼,也不會重複計算。附帶一提,所有符合ISO/IEC 14443-A標準規範的RFID卡片都要有防衝突處理功能,而Mifare是依循ISO/IEC 14443-A規格建立的非接觸式IC卡。
底下是Mifare裝置的概略圖,它的卡片屬於被動式、無內建電源(也稱為「無源」),卡片所需的電力來自讀寫器的電磁場。
啟動RFID讀寫器之後,讀寫器的天線將不停地發送電磁波,每當卡片進入此電磁場,卡片內部的線圈和電路將與此電磁場產生共振,從而獲得電能。無線充電器也是透過電磁感應、非直接連線方式獲取能量,RFID讀寫器和卡片兩端的線圈(天線)相當於變壓器裡的主線圈和副線圈(請參閱「測試Palm Touchstone(點金石)無線充電模組」貼文。
依照感應距離,非接觸型IC卡分成緊耦合型(close-coupled,需要緊貼感測器)、接近式(proximity,10cm以內)和鄰近式(vicinity,50cm以內),Mifare屬於接近式,這種類型的卡片簡稱為PICC(Proximity IC Card,接近式IC卡),讀寫器則簡稱PCD(Proximity Coupling Device,接近型耦合器)。
另一方面,某些RFID的應用場合,標籤的尺寸大小有固定的標準,像信用卡和金融卡,這一類的卡片長、寬和厚度,都要遵循國際ID-1標準(85.6 × 53.98 × 0.76mm),目前的技術沒有辦法把電池塞入這麼薄的卡片,所以非接觸型卡片只能採用無線供電方案。
當卡片被電磁能啟動(activate)之後,將等待接收與回應來自讀寫器的命令。微控器和Mifare讀寫器之間採用TTL數位訊號傳遞資料,為了用電波傳送數位訊號,必須將數位訊號加上載波調變,Mifare的載波頻率是13.56MHz。上圖的射頻訊號波形只是描述概念,數位訊號在調變成電波之前,從讀寫器發送的訊號會採用米勒(Miller)編碼,而從卡片發出訊息則經過曼徹斯特(Manchester)編碼。讀寫器在操作卡片時,都會經過三次雙向認證,互相驗證使用的合法性,而且通訊過程中的所有數據都經過加密,以確保安全。

Mifare讀寫器模組與Arduino接線示範

本單元使用的Mifare RFID-RC522讀寫器模組的外觀與接腳定義如下,模組採用的MFRC522晶片本身有支援UART, I2C和SPI介面,但是本文採用的程式庫僅支援SPI介面。








Arduino Uno板的接線示範如下,SPI介面的晶片線選擇通常接在Arduino數位10腳,但這不是強制性的,模組的Reset腳也可以接在其他腳位:

操控Mifare模組的MFRC522程式庫

本單元程式採用Miki Balboa開發的這個MFRC522程式庫來操控Mifare模組,若不使用程式庫,我們需要詳閱MFRC522晶片的規格書,了解讀寫器、卡片和微控制器之間的數據通訊流程,以及晶片內部的暫存器的指令位址,才能動手撰寫程式。
下載程式庫之後,請將它解壓縮到Arduino的libraries資料夾,再開啟Arduino IDE,即可從主功能表的「檔案→範例→MFRC522」指令底下找到一些範例程式。










底下列舉本單元使用到的MFRC522程式物件的方法和屬性:
  • MFRC522物件.PCD_Init():初始化MFRC522讀卡機模組
  • MFRC522物件.PICC_IsNewCardPresent():是否感應到新的卡片
  • MFRC522物件.PICC_ReadCardSerial():讀取卡片的資料
  • MFRC522物件.PICC_GetType():取得卡片類型
  • MFRC522物件.PICC_GetTypeName():取得卡片類型名稱
每張Mifare卡片都有個唯一的ID(unique identifier,簡稱UID),當讀寫機讀取到卡片的資料之後,UID的長度和內容,可從底下兩個屬性值取得:
  • MFRC522物件.uid.size:包含UID的長度
  • MFRC522物件.uid.uidByte:包含UID碼的陣列

讀取Mifare卡片的UID碼

讀取Mifare卡片的流程如下,我們的程式不需要理會其中的「防衝突處理」和「選卡」部份,讀寫器會幫我們搞定,但是在讀取資料之後,我們的程式要發出命令讓卡片進入停止(halt)狀態,避免讀寫器重複讀取同一張卡片:
AK代表select acknowledge,直譯為「選擇應答」,是由卡片發給讀寫器,對於選擇卡片命令的回應,不同類型的Mifare卡片的SAK值不一樣(例如,Mifare Classic的SAK值為0x18),程式可藉此判別感應到的卡片類型。詳細的防衝突處理與SAK值判斷流程,請參閱NXP公司的“MIFARE ISO/IEC 14443 PICC Selection”技術文件(PDF格式)。
讀取Mifare卡片類型及其UID碼的程式如下:
#include <SPI.h>
#include <MFRC522.h>     // 引用程式庫

#define RST_PIN      A0        // 讀卡機的重置腳位
#define SS_PIN       10        // 晶片選擇腳位

MFRC522 mfrc522(SS_PIN, RST_PIN);  // 建立MFRC522物件

void setup() {
  Serial.begin(9600);
  Serial.println("RFID reader is ready!");

  SPI.begin();
  mfrc522.PCD_Init();   // 初始化MFRC522讀卡機模組
}

void loop() {
    // 確認是否有新卡片
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
      byte *id = mfrc522.uid.uidByte;   // 取得卡片的UID
      byte idSize = mfrc522.uid.size;   // 取得UID的長度

      Serial.print("PICC type: ");      // 顯示卡片類型
      // 根據卡片回應的SAK值(mfrc522.uid.sak)判斷卡片類型
      MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
      Serial.println(mfrc522.PICC_GetTypeName(piccType));

      Serial.print("UID Size: ");       // 顯示卡片的UID長度值
      Serial.println(idSize);

      for (byte i = 0; i < idSize; i++) {  // 逐一顯示UID碼
        Serial.print("id[");
        Serial.print(i);
        Serial.print("]: ");
        Serial.println(id[i], HEX);       // 以16進位顯示UID值
      }
      Serial.println();

      mfrc522.PICC_HaltA();  // 讓卡片進入停止模式
    } 
}
程式第20行宣告一個指向儲存UID值的指標變數(假設UID碼的長度為4):
第25行的“MFRC522::PICC_Type”代表引用在MFRC522類別(程式庫)裡面定義的PICC_Type這個資料類型,其中的雙冒號(::)代表範圍解析運算子(scope-resolution operator),用來表示“PICC_Type”定義在MFRC522程式庫裡面。如果不用雙冒號指出“PICC_Type”資料類型的來源,程式編譯器會產生未定義之類的錯誤。

上傳程式碼之後,開啟序列埠監控視窗,你可以嘗試一次讓Mifare模組感應多個卡片(筆者同時用3個),它將能逐一顯示每個卡片的類型和UID:

除非這些卡片離開、再次進入感應區,否則它們不會被重複讀取。

沒有留言:

張貼留言

algorithm

 #include <iostream> #include <string.h> using namespace std; int main(int argc, char** argv)  { for(int j=2;j<=100;j++)//j...