RFID(NFC)技術を使用した非接触ICカードやタグのデータの読み書き方法について、カードのデータ構成も含めて使用方法を詳しく紹介します。
実際にデータの読み書きを行うためには「M5Stack」の「RFID2ユニット」を使用して「Arduinoコマンド」で行います。
デバイスを準備すれば「サンプルプログラム」をコピペして書き込むだけで動作確認できるので、自分オリジナルの非接触ICカード/タグを作ってみましょう。
「M5Stack」を使用した「Arduino」の開発環境の準備方法は、以下のリンクで詳しく紹介しています。
1.今回できるようになること
2.RFIDとは
3.関連用語の解説
4.RFIDタグ・非接触ICカードの種類
・MIFARE(マイフェア)
・Felica(フェリカ)
・ISO/IEC
5.MIFARE規格の種類
・MIFARE Classic
・MIFARE Plus
・MIFARE Ultralight
・MIFARE DESFire(TNP3XXX)
6.準備するもの:カード/タグの読み書き準備
・M5Stack Basic
・RFIDリーダーライター[RFID2ユニット]
・RFIDカード/タグ
・使用ライブラリ「MFRC522_I2C」
7.サンプルプログラム(コピペ)
8.動作確認
9.サンプルプログラムの詳細
・初期設定
・カードの認識(読み込み待機状態)
・カード内全データ確認表示
・データの読込み方法
・データの書込み方法
10.MIFARE 1KB のデータ構成について
・全体構成の確認
・セクタートレーラー
・認証キーA、B
・アクセスコンディション(アクセス条件)
・設定可能な「Access Bits」
11.まとめ
1.今回できるようになること
今回は個人でも安価(1つ100円前後)で入手可能な非接触ICカード/タグ(MIFARE 1K)を使用して、データの読み書き、アクセス条件の設定方法等を詳しく紹介します。
「Arduino」の開発環境を使用して、最終的にはシリアルモニタから、好きなデータ領域のデータの読み書き、書き換え、アクセス条件の設定ができるようになります。
2.RFIDとは
RFIDは「Radio Frequency Identification(ラジオ周波数識別)」の略で、無線通信を利用してデータを自動的に識別する技術です。
RFIDシステムは、タグ(トランスポンダ)とリーダーから構成されます。
タグは特定の情報(識別番号など)を保持しており、リーダーからの電波を受信すると、応答信号を送信します。
小型のタグには、個々のアイテムや製品に取り付けられる小型のチップとアンテナが内蔵されており、リーダーからの電波によって電源が供給されて起動し、データの送受信を行います。
RFIDは、物流や在庫管理、入退室管理などのさまざまな分野で利用されています。
例えば、製造業では材料の追跡や生産プロセスの管理に使用され、小売業では在庫管理や商品の追跡に利用されます。
3.関連用語の解説
非接触ICカードやタグに利用されている「RFID」技術を使用する時に登場する、主な用語を以下表にまとめました。
用語 | 解説 |
---|---|
NFC | NFCは「Near Field Communication(近距離無線通信)」の略で、非接触型の近距離無線通信技術です。 NFCは、2つのデバイスがタッチまたは非常に近い距離で接触することで、データを交換することができ、セキュリティのための情報の暗号化や認証などの保護機能もあるため、タグやスマートフォン、決済カードなどのデバイスに組み込まれています。 NFCは、主にモバイル決済やデジタルチケット、スマートロック、デバイス間のファイル転送など、さまざまな応用分野で利用されています。 |
RFIDタグ | RFIDタグは、小さなチップとアンテナから構成されており、無線通信の範囲内であれば、特定のリーダーから情報を読み取ることができます。 パッシブRFIDタグとアクティブRFIDがあり、パッシブRFIDタグは、外部の電力源を必要とせず、リーダーからの電波エネルギーを受信して動作します。 アクティブRFIDタグは、内蔵された電池や電源を利用して動作するタイプです。パッシブRFIDとは異なり、送信側と受信側の両方で電波を送受信することができます。 アクティブRFIDは通信範囲が広く、移動物体の追跡や長距離識別などに使用されます。 |
PICC | PICC(Proximity Integrated Circuit Card)は、非接触型ICカード(NFCカード)のことを指します。 PICCは、近距離(数センチ)での通信に使用される技術で、カードリーダーやスマートフォンなどのデバイスとの間で情報をやり取りすることができます。 RFIDタグの中でもキーホルダー型のような小型なものはPICCと同じ機能を持ちます。 主な用途として、交通カードやクレジットカード、スマートキーなどがあります。 |
トランスポンダ | トランスポンダ(Transponder)とは、Transmitter(送信機)とresponder(応答機)の両方の機能を備えたデバイスで、情報の保存や処理を行うマイクロチップとアンテナを接合した電気回路です。 RFIDシステムでは、RFIDリーダーがトランスポンダ(タグ)に電波を送信し、その信号を受け取ったタグはリーダーに固有の情報(UID, SAK等)を送り返します。 |
RFIDリーダー | RFIDリーダー(リーダーライター)はアンテナを備えたデバイスで、以下の手順でタグから情報を読み取ったり、タグに情報を書き込んだりする装置です。 ①RFIDリーダーは、一定の周波数で電波を発信します。 ②周囲のRFIDタグやICカードは、受けた電波エネルギーを使って内部の回路を動作させます。 ③タグやカードは、リーダーからの電磁波エネルギーを利用して自身の識別情報やデータを応答データとして返します。 ④RFIDリーダーは、アンテナを介してタグやカードからの応答データを受信します。 ⑤受信したデータは、リーダーが内蔵している回路や処理装置によって解析され、必要な情報(タグの識別情報やデータ)が抽出されます。 |
UID | UID (Unique Identifier)は、RFIDタグを一意に識別するための識別番号です。 通常、UIDは64ビットまたは96ビットの長さの番号として表されます。 UIDは、タグを特定するために使用され、異なるタグが同じUIDを持つことはありません。 UIDは製造時に割り当てられ、一度割り当てられたら変更されません。 |
SAK | SAK (Select Acknowledge)は、RFIDタグの機能や特性を示すバイト値です。 SAKは、リーダーがタグとの通信時に受信する応答メッセージの一部として提供されます。 SAKによって、タグがISO/IEC 14443規格(13.56MHzの周波数で動作する非接触型ICカードやRFIDタグのための規格)に準拠しているかどうか、または他の特性(例:デュアルインターフェースタグ)を持っているかどうかの判断ができます。 SAKはタグの機能や通信プロトコルに基づいて決まる値であり、リーダーが適切に対応できるかどうかを判断するために使用されます。 |
4.RFIDタグ・非接触ICカードの種類
・MIFARE(マイフェア)
NXPセミコンダクターズ(旧フィリップス社)によって開発された非接触型ICカードの規格です。
MIFAREカードにはいくつかの種類があり、メモリ容量が大きく安価なため、広く普及しています。
交通機関チケット、入退出管理、キャッシュレス決済などさまざまな用途で利用されています。
・Felica(フェリカ)
Felicaは、ソニーによって開発された非接触型ICカードの規格です。
Felicaは、主に日本で使用されており、SuicaやPASMOなどの交通系ICカードや、電子マネー(例:Edy、nanaco)、電子マネーモードのクレジットカードなど、さまざまなサービスで利用されています。※MIFAREとの互換性はありません。
・ISO/IEC
ISO(国際標準化機構)/IEC(国際電気標準会議)によって制定され、13.56MHz周波数帯域で通信する非接触型ICカードの国際規格です。
- ISO/IEC 14443
非接触型ICカードやRFIDタグが互換性を持つための規格です。
この規格は、近距離(通常は1〜10 cm程度)で動作する非接触技術に適用され、2つのタイプ(タイプAとタイプB)が定義されています。
タイプAは、データ転送速度は106 kbit/sです。
タイプBは、データ転送速度は106 kbit/sまたは212 kbit/sで、タイプAよりセキュリティレベルが高いです。
MIFAREカードもこの規格(タイプA)に準じており、公共交通機関での乗車券や電子マネーなど、さまざまなアプリケーションで使用されます。 - ISO/IEC 18092
日本では交通系IC等で使用されている「Felica」がこの規格です。
セキュリティ面で優れており、処理速度は最大で424 kbpsと ISO/IEC14443より高速です。
- ISO/IEC 15693
読み取り距離が比較的長い(5 cm以上)ため「Vicinity(近傍)」カードと呼ばれる規格です。
データ転送速度は遅く 26 kbit/sです。
通信距離が長いため、物品の追跡や在庫管理などに使用されます。
5.MIFARE規格の種類
今回、実際にデータの読み書きには「MIFARE Classic」のカード/タグを使用します。
MIFARE規格には以下のような種類があります。
・MIFARE Classic
「MIFARE Classic」は最も広く使用されるMIFAREカードです。
1KBまたは4KBのメモリ容量を持ち、それぞれ16または64セクター(データ保存領域)に分割されています。
暗号化やセキュリティ機能が限られており、比較的安価なため、主にアクセス制御や公共交通機関のチケットなどの決済に使用されます。
・MIFARE Plus
「MIFARE Plus」は「MIFARE Classic」の代替として開発され、より高いセキュリティ機能を提供します。
「MIFARE Classic」との下位互換性を持っており、徐々にアップグレードできるため、既存のシステムに導入しやすくなっています。
AES暗号化や改善されたセキュリティ機能を備えており、データ保護やセキュアなアプリケーション実装に適しています。
・MIFARE Ultralight
「MIFARE Ultralight」は、小型で低コストなRFIDカードです。
メモリ容量は144バイトまたは512バイトで、読み取り専用であり、書き込み機能はありません。
※書込み可能な「Ultralight C」という規格もあります。
公共交通機関の乗車券、イベントチケットなど、一回性の利用のために設計されています。
・MIFARE DESFire(TNP3XXX)
「MIFARE DESFire」は、通信速度が速く(848kbps)、高度なセキュリティ機能を持つRFIDカードです。
2KB、4KB、8KBのメモリ容量があり、暗号化、相互認証、データ保護などの強力なセキュリティ機能が組み込まれています。
多くのセキュリティアプリケーションに使用され、ロンドン地下鉄の交通系ICカードであるOyster Cardをはじめとした、世界各国の交通券にも「MIFARE DESfire」が採用されています。
6.準備するもの:カード/タグの読み書き準備
今回は安価(1つ100円前後)で、入手しやすい非接触ICカード/タグの「MIFARE Classic(1KB)」を使用して、実際にデータの読み書きを行います。
全体の構成は下写真のようになります。
動作確認に必要なものは以下になります。
・M5Stack Basic
「M5Stack Basic V2.6」とは「M5Stackテクノロジー社」のマイコンボードです。
大きさは 54mm x 54mm x 18mmで2.0インチ(320*240)液晶表示器を搭載しており、入出力端子、3個のボタン、3軸ジャイロ+3軸加速度センサ、スピーカ搭載でSDカードやバッテリーも内蔵され、シリアル通信はもちろんWiFiやBluetoothにも対応しています。
「M5Stack Basic V2.6」については以下のリンクで詳しく紹介しています。
・RFIDリーダーライター[RFID2ユニット]
今回使用した非接触ICカード/タグのリーダーライターは下画像の「M5Stak」社製の「RFID2ユニット」です。
RFIDリーダーライターは色々なものがありますが、電波を使用するデバイスのため、日本の電波法により、電波強度が基準値以下である確認が取れているものを使用する必要があります。
私は最初Amazonで適当に購入しましたが、この確認をする術がなかったので・・・使わずに放置してました。
以下のスイッチサイエンスさんで取り扱っている「M5Stack」社の「RFID2ユニット」は電波強度の確認がされており、安心して使用できるため、以下リンクから購入されることをお勧めします。
・RFIDカード/タグ
「RFIDカード/タグ」については下画像のようにいろいろな種類があります。
下画像の中央にある青いカードは「M5Stack」社の公式オンラインショップで¥5000-程購入した時に、おまけで付属していたものです。
特に何も書かれていなかったので空のカードとして使用できます。
「Amazon」でも以下のように色々な種類が販売されています。
「MIFARE Classic 1KB」なら何でも良いので、カードタイプでも、キーホルダタイプでも、シールでも、お好みのもので準備してください。
・使用ライブラリ「MFRC522_I2C」
今回使用したライブラリは「MFRC522_I2C」です。
「Arduino IDE」の「ライブラリマネージャ」で検索すると、下画像の「kkloesener」さん作のライブラリです。
7.サンプルプログラム(コピペ)
カード内のデータを読み込んでシリアルモニタに表示したり、シリアルモニタから入力した文字を書き込むためのサンプルプログラムを準備しました。
サンプルプログラムは以下になります。コピペで貼り付けて書き込んで実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
#include <M5Stack.h>
#include <MFRC522_I2C.h>
MFRC522_I2C mfrc522(0x28, 2, &Wire); // デバイスアドレスとリセット端子番号、TwoWireインスタンスを指定してインスタンス化(&Wire省略可)
// 変数宣言
byte read_data[16] = {}; // 読込みデータ格納用配列
byte write_data[16] = {}; // 書込みデータ格納用配列
int sector_num = 0; // セクター番号格納用
int block_num = 0; // ブロック番号格納用
// 関数 --------------------------------------------------------
// 全データ読み込みシリアルモニタ表示 ------------------------------------------
void readAllData() {
byte piccType = mfrc522.PICC_GetType(mfrc522.uid.sak); // SAK(NFCタグの識別子)を取得し、変数に格納
// カードタイプ確認
if (piccType == MFRC522_I2C::PICC_TYPE_MIFARE_MINI ||
piccType == MFRC522_I2C::PICC_TYPE_MIFARE_1K ||
piccType == MFRC522_I2C::PICC_TYPE_MIFARE_4K) {
mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); // Mifare Classicのデータを出力
} else if (piccType == MFRC522_I2C::PICC_TYPE_MIFARE_UL) {
mfrc522.PICC_DumpMifareUltralightToSerial(); // Mifare Ultralightのデータを出力
} else {
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(0, 57, 320, 200, BLACK); // 液晶のデータ表示部を黒塗りリセット
M5.Lcd.setCursor(0, 57); // 座標表示
M5.Lcd.println("This card is not supported."); // サポートされていないカードである旨を表示
delay(5000);
}
mfrc522.PICC_HaltA(); // 現在読み込んでいるカードをハルトする
mfrc522.PCD_Init(); // RFIDリーダーを初期化する
}
// ブロックデータ読み込み ------------------------------------------
void readData(int sector, int block) { // (セクター番号, ブロック番号)
MFRC522_I2C::MIFARE_Key key = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // MIFARE_Key構造体でセクター認証キーを準備(ポインタで指定するため)
Serial.println("カードデータ読み取り");
uint8_t address = sector * 4 + block; // ブロックアドレスを算出
// 読込み対象のセクターを認証する(認証キータイプ, ブロックアドレス, 認証キーポインタ, カードのUIDプロパティへのポインタ)
if (mfrc522.PCD_Authenticate(MFRC522_I2C::PICC_CMD_MF_AUTH_KEY_A, address, &key, &(mfrc522.uid)) == MFRC522_I2C::STATUS_OK) {
Serial.println("認証に成功しました");
byte buffer[18]; // 読込みデータ16byte + エラーチェック用CRC用2byte分の配列を準備
byte bufferSize = sizeof(buffer); // バッファサイズをポインタで指定できるように準備
// 認証成功で読込み実行(ブロックアドレス, データ格納バッファ配列ポインタ, 読込みデータバッファサイズポインタ)
if (mfrc522.MIFARE_Read(address, buffer, &bufferSize) == MFRC522_I2C::STATUS_OK) { // 通信成功なら
for (int i = 0; i < bufferSize-2; i++) { // 読込んだデータのCRCを除いた16byte分繰り返す
read_data[i] = buffer[i]; // バッファを読み込みデータ配列に格納
Serial.printf("%02X ", read_data[i]); // 16byteの読み込みデータをシリアルモニタに出力
}
Serial.printf("\n%s\n", read_data);
}
} else {
Serial.println("認証に失敗しました");
}
mfrc522.PCD_StopCrypto1(); // 認証済み解除
}
// ブロックデータ書込み -------------------------------------------
void writeData(int sector, int block) { // (セクター番号, ブロック番号)
MFRC522_I2C::MIFARE_Key key = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // MIFARE_Key構造体でセクターキーを準備(ポインタで指定するため)
Serial.println("カードデータ書込み");
uint8_t address = sector * 4 + block; // ブロックアドレスを算出
// 書込み対象のセクターを認証する(認証キータイプ, ブロックアドレス, 認証キーポインタ, カードのUIDプロパティへのポインタ)
if (mfrc522.PCD_Authenticate(MFRC522_I2C::PICC_CMD_MF_AUTH_KEY_A, address, &key, &(mfrc522.uid)) == MFRC522_I2C::STATUS_OK) { // 通信成功なら
Serial.println("認証に成功しました");
if (address == 0 || block == 3) { // ブロックアドレスが0またはブロック3なら書込まず終了
Serial.print("データブロックではないので書き込めません");
} else { // ブロック0〜2ならデータ書込み
mfrc522.MIFARE_Write(address, write_data, 16); // (ブロックアドレス, 書込みデータ, 書込みbyte数)
// 書き込みデータをシリアルモニタに出力
for (int i = 0; i < 16; i++) { // 16byte分繰り返す
Serial.printf("%02X ", write_data[i]); // 16進数で出力
}
}
Serial.printf("\n%s\n", write_data);
} else {
Serial.println("認証に失敗しました");
}
// カードから切断する
mfrc522.PCD_StopCrypto1(); // 認証済み解除
}
// 初期設定 ------------------------------------------------------
void setup() {
M5.begin(); // 本体初期化
M5.Power.begin(); // 電源初期化
Serial.begin(9600); // シリアル通信初期化(USBと共用、初期値は RX=G3, TX=G1)
Wire.begin(); // I2C通信初期化
mfrc522.PCD_Init(); // RFID2(MFRC522)初期化
// 初期画面表示
M5.Lcd.setTextFont(4); // フォント
M5.Lcd.println("RFID2 Card Reader / Writer"); // タイトル表示
M5.Lcd.println("Please put the card");
M5.Lcd.fillRect(0, 51, 320, 2, WHITE); // 横線
}
// メイン ------------------------------------------------------
void loop() {
// 新しいカードが読み込まれない、または、カードのシリアル番号が読み取れないならループ
while (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
delay(100); // 待機時間
}
// カードが読み取られたら以下を実行
M5.update(); // 本体ボタン初期化
// AボタンONでセクター番号+1(0〜15)
if (M5.BtnA.wasPressed()) {
sector_num = (sector_num + 1) % 16; // 16なら0リセット
}
// BボタンONでブロック番号+1(0〜2)
if (M5.BtnB.wasPressed()) {
block_num = (block_num + 1) % 3; // 3なら0リセット
}
// CボタンONでブロックデータ読込み
if (M5.BtnC.wasPressed()) {
readData(sector_num, block_num); // データ読込み関数(セクター番号, ブロック番号)
}
// Cボタン長押しで全データ読込みシリアルモニタ表示関数呼び出し
if (M5.BtnC.pressedFor(1000)) {
readAllData(); // シリアルモニタ表示関数
}
// シリアルモニタから書込みデータを設定
if (Serial.available()) { // 受信データがあれば
int bytesRead = Serial.available(); // 受信データバイト数を取得
if (bytesRead <= 16) { // 16byte以下なら
for (int i = 0; i < 16; i++) { // 書込みデータ配列を0で初期化
write_data[i] = 0x00;
}
// 受信データを書込みデータ配列に格納
for (int i = 0; i < bytesRead; i++) {
write_data[i] = Serial.read();
}
Serial.printf("%s\n", write_data);
writeData(sector_num, block_num); // データ書込み関数(セクター番号, ブロック番号)
} else {
Serial.println("文字数が16文字を超えています");
while (Serial.available()) { // 16byte以上のデータは破棄
Serial.read();
}
}
}
// 液晶表示
// カードデータ表示
M5.Lcd.fillRect(0, 57, 320, 200, BLACK); // 液晶のデータ表示部を黒塗りリセット
M5.Lcd.setCursor(0, 57); // 座標表示
// UID表示
String uidString = ""; // UIDシリアル
char byte_string[3]; // 1byteの16進数表示を格納する文字配列を準備
for (byte i = 0; i < mfrc522.uid.size; i++) { // 取得したbyte数分繰り返す
sprintf(byte_string, "%02X", mfrc522.uid.uidByte[i]); // 2桁で大文字の16進数として文字配列に格納
uidString += String(byte_string) + " "; // String型でスペース区切りで連結
}
M5.Lcd.setTextColor(CYAN, BLACK);
M5.Lcd.printf("UID : %s\n", uidString.c_str()); // Cスタイルの文字列に変換して表示
// Mifareカード(タグ)タイプ表示
byte piccType = mfrc522.PICC_GetType(mfrc522.uid.sak); // SAK(Select Acknowledge:NFCタグの識別子)を取得し、変数に格納
M5.Lcd.setTextColor(WHITE, BLACK); // 文字色
M5.Lcd.print("TYPE No. ");
M5.Lcd.println(piccType); // SAKを表示
M5.Lcd.print("TYPE Name : ");
M5.Lcd.println(mfrc522.PICC_GetTypeName(piccType)); // SAKを基にタイプ名を表示
// 書込みデータ、読み込みデータ確認表示
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.printf("Sector : %d/Block : %d/Add : %d\n", sector_num, block_num, sector_num * 4 + block_num); // ブロック情報
M5.Lcd.printf("Write : %s\n", write_data); // 書込みデータ
M5.Lcd.setTextColor(ORANGE, BLACK);
M5.Lcd.printf("Read : %s\n", read_data); // 読み込みデータ
delay(100); // 遅延時間
}
8.動作確認
サンプルプログラムの動作は以下のようになります。
動作確認で非接触ICカード/タグ内のデータ確認には「シリアルモニタ」を使用します。
「Arduino IDE」を使用する場合はした画像のように[ツール]→[シリアルモニタ]をクリックします。
「M5Stack」と非接触ICカード/タグのリーダーライター「RFID2ユニット」は付属のGROVEコネクタ配線で接続するだけです。
「M5Stack」電源を入れると上画像のような表示になります。
「RFID2ユニット」に非接触ICカードまたはタグを近づけると、上画像のように「UID」とカード/タグの「TYPE」が表示されます。
緑文字の「Sector」「Block」「Add」で読み書きするカード/タグ内のデータ領域を指定します。
画像の「Sector:0/Block:0/Add:0」には「UID」が保存されています。
上画像のように「ボタンC」を押すと、指定したデータ領域のデータを読み出すことができますが、1秒以上長押しすると下画像のように、全データ領域に保存されているデータが一覧で表示されます。
次にデータの書き込みを行います。
まずは書込みたいデータ領域を下画像のように指定します。
「ボタンA」を押すとデータ領域の「Sector(セクター)」が選択できます。
「ボタンB」を押すとデータ領域の「Block(ブロック)」が選択できます。
「Add(ブロックアドレス)」は「Sector」と「Block」から自動で算出されます。
読み書きしたいデータ領域を設定したら、下画像のように「シリアルモニタ」に保存したい内容を入力(今回は「logikara.blog」と入力)します。
保存したいデータを入力したら[送信]ボタンか[ENTER]キーを押すと書き込みが実行されます。
書き込みが完了すると上画像のように「Write:」に書き込まれたデータが表示されます。
書き込みされたデータを読み込むには「ボタンC」を押す(1秒以下)と「Read:」に表示されます。
データの読み書きを行うと「シリアルモニタ」には下画像のように表示されます。
書き込まれるデータは「16進数」で「16byte」分です。
シリアルモニタでは「16進数」を「ASCII文字」に変換したものも表示させています。
「ボタンC」を長押し(1秒以上)すると、下画像のように指定したデータ領域に書き込みが行われたことが確認できます。
9.サンプルプログラムの詳細
サンプルプログラムの各動作について、以下で詳しく紹介します。
・初期設定
まずは「RFID2ユニット」を使用するためのライブラリ「MFRC522_I2C」をインクルードします。
#include <M5Stack.h>
#include <MFRC522_I2C.h>
// デバイスアドレスとリセット端子番号、TwoWireインスタンスを指定してインスタンス化(&Wire省略可)
MFRC522_I2C mfrc522(0x28, 2, &Wire);
次に「MFRC 522_I2C」のインスタンスを「I2Cデバイスアドレス:0x28」と「リセット端子番号」を指定して作成します。
初期設定は以下のように指定します。
// 初期設定 ------------------------------------------------------
void setup() {
M5.begin(); // 本体初期化
M5.Power.begin(); // 電源初期化
Serial.begin(9600); // シリアル通信初期化(USBと共用、初期値は RX=G3, TX=G1)
Wire.begin(); // I2C通信初期化
mfrc522.PCD_Init(); // RFID2(MFRC522)初期化
}
「7行目」の「mfrc522.PCD_Init()」で「RFID2ユニット」を初期化することで、カードの読み込み待機状態となります。
・カードの認識(読み込み待機状態)
メインループで以下を実行することでカード読み込み待機状態にしています。
// メイン ------------------------------------------------------
void loop() {
// 新しいカードが読み込まれない、または、カードのシリアル番号が読み取れないならループ
while (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
delay(100); // 待機時間
}
// カードが読み取られたら以下を実行
以下の関数を使用して、カードが読み込まれてカードのシリアル番号が読み取られたことを確認します。
mfrc522.PICC_ReadCardSerial(); // カードのシリアル番号が読み取れたらtrueを返す
カードが認識されたらループを抜け、以降のプログラムが実行されます。
・カード内全データ確認表示
以下の「readAllData()」関数を実行することで、カードの全データを読み込んでシリアルモニタに表示することができます。
// 全データ読み込みシリアルモニタ表示 ------------------------------------------
void readAllData() {
byte piccType = mfrc522.PICC_GetType(mfrc522.uid.sak); // SAK(NFCタグの識別子)を取得し、変数に格納
// カードタイプ確認
if (piccType == MFRC522_I2C::PICC_TYPE_MIFARE_MINI ||
piccType == MFRC522_I2C::PICC_TYPE_MIFARE_1K ||
piccType == MFRC522_I2C::PICC_TYPE_MIFARE_4K) {
mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); // Mifare Classicのデータを出力
} else if (piccType == MFRC522_I2C::PICC_TYPE_MIFARE_UL) {
mfrc522.PICC_DumpMifareUltralightToSerial(); // Mifare Ultralightのデータを出力
} else {
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(0, 57, 320, 200, BLACK); // 液晶のデータ表示部を黒塗りリセット
M5.Lcd.setCursor(0, 57); // 座標表示
M5.Lcd.println("This card is not supported."); // サポートされていないカードである旨を表示
delay(5000);
}
mfrc522.PICC_HaltA(); // 現在読み込んでいるカードをハルトする
mfrc522.PCD_Init(); // RFIDリーダーを初期化する
}
カードのデータを確認するためには、まずは以下(上コード「3行目」)のようにカードの「SAK(NFCタグの識別子)」を取得します。
取得した「SAK」から「カードタイプ」が「MIFARE」の「MINI、1KB、4KB」であることが確認されたら、以下(上コード「8行目」)を実行することでシリアルモニタにカードの全データが表示されます。
「カードタイプ」が「MIFARE」の「Ultralight または Ultralight C」の場合は以下(上コード「10行目」)を実行することでシリアルモニタにカードの全データが表示されます。
・データの読込み方法
データを読み込むには、カード内のデータ保存領域の「セクター番号, ブロック番号」を指定して以下のように「readData()」関数を実行します。
// ブロックデータ読み込み ------------------------------------------
void readData(int sector, int block) { // (セクター番号, ブロック番号)
MFRC522_I2C::MIFARE_Key key = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // MIFARE_Key構造体でセクター認証キーを準備(ポインタで指定するため)
Serial.println("カードデータ読み取り");
uint8_t address = sector * 4 + block; // ブロックアドレスを算出
// 読込み対象のセクターを認証する(認証キータイプ, ブロックアドレス, 認証キーポインタ, カードのUIDプロパティへのポインタ)
if (mfrc522.PCD_Authenticate(MFRC522_I2C::PICC_CMD_MF_AUTH_KEY_A, address, &key, &(mfrc522.uid)) == MFRC522_I2C::STATUS_OK) {
Serial.println("認証に成功しました");
byte buffer[18]; // 読込みデータ16byte + エラーチェック用CRC用2byte分の配列を準備
byte bufferSize = sizeof(buffer); // バッファサイズをポインタで指定できるように準備
// 認証成功で読込み実行(ブロックアドレス, データ格納バッファ配列ポインタ, 読込みデータバッファサイズポインタ)
if (mfrc522.MIFARE_Read(address, buffer, &bufferSize) == MFRC522_I2C::STATUS_OK) { // 通信成功なら
for (int i = 0; i < bufferSize-2; i++) { // 読込んだデータのCRCを除いた16byte分繰り返す
read_data[i] = buffer[i]; // バッファを読み込みデータ配列に格納
Serial.printf("%02X ", read_data[i]); // 16byteの読み込みデータをシリアルモニタに出力
}
Serial.printf("\n%s\n", read_data);
}
} else {
Serial.println("認証に失敗しました");
}
mfrc522.PCD_StopCrypto1(); // 認証済み解除
}
データを読み込むためには、まず読み込み対象の「ブロック」を含む「セクター」に対して認証を行う必要があります。
認証を行うには「セクター認証キー」が必要なため、以下(上コード「3行目」)のように「MIFARE_Key構造体」でセクター認証キーを準備し、ポインタで指定します。
読み込み対象の「ブロック」は以下(上コード「6行目」)の式で「ブロックアドレス」を算出して指定します。
次に以下(上コード「8行目」)を「認証キータイプ(AまたはB), ブロックアドレス, 認証キーのポインタ, カードのUIDプロパティへのポインタ」を指定して実行することで、読込み対象の「セクター」認証が行われます。
認証に成功したら以下(上コード「13行目」)のように「ブロックアドレス, データ格納バッファ配列のポインタ, 読込みデータバッファサイズのポインタ」を指定して「mfrc522.MIFARE_Read()」を実行することでデータを読み込むことができます。
for (int i = 0; i < bufferSize–2; i++) {
read_data[i] = buffer[i];
Serial.printf(“%02X “, read_data[i]);
}
Serial.printf(“\n%s\n”, read_data);
}
読み込みが完了したら、最後に以下(上コード「23行目」)を実行し、認証済み状態を解除して完了です。
・データの書込み方法
データを書き込むには、カード内のデータ保存領域の「セクター番号, ブロック番号」を指定して以下のように「writeData()」関数を実行します。
// ブロックデータ書込み -------------------------------------------
void writeData(int sector, int block) { // (セクター番号, ブロック番号)
MFRC522_I2C::MIFARE_Key key = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // MIFARE_Key構造体でセクターキーを準備(ポインタで指定するため)
Serial.println("カードデータ書込み");
uint8_t address = sector * 4 + block; // ブロックアドレスを算出
// 書込み対象のセクターを認証する(認証キータイプ, ブロックアドレス, 認証キーポインタ, カードのUIDプロパティへのポインタ)
if (mfrc522.PCD_Authenticate(MFRC522_I2C::PICC_CMD_MF_AUTH_KEY_A, address, &key, &(mfrc522.uid)) == MFRC522_I2C::STATUS_OK) { // 通信成功なら
Serial.println("認証に成功しました");
if (address == 0 || block == 3) { // ブロックアドレスが0またはブロック3なら書込まず終了
Serial.print("データブロックではないので書き込めません");
} else { // ブロック0〜2ならデータ書込み
mfrc522.MIFARE_Write(address, write_data, 16); // (ブロックアドレス, 書込みデータ, 書込みbyte数)
// 書き込みデータをシリアルモニタに出力
for (int i = 0; i < 16; i++) { // 16byte分繰り返す
Serial.printf("%02X ", write_data[i]); // 16進数で出力
}
}
Serial.printf("\n%s\n", write_data);
} else {
Serial.println("認証に失敗しました");
}
// カードから切断する
mfrc522.PCD_StopCrypto1(); // 認証済み解除
}
データを書き込む場合も上コード「1〜8行目」までは読み込みと同様で、書込み対象のブロックの認証を行う必要があります。
認証に成功したら書き込みを実行しますが「ブロックアドレス0」は「UID」が格納されているため書き込みができません。
また、「各セクター」の「ブロック3」には認証キー(AとB)や各ブロックへのアクセス条件の設定(アクセスコンディション)が含まれており、不用意に書き込みを行うとアクセスできなくなる可能性があるため、以下(上コード「10〜18行目」)のように書込み対象のブロックを制限しています。
書き込みは以下(上コード「13行目」)のように「ブロックアドレス, 書込みデータ配列, 書込みバイト数(16固定)」を指定して「mfrc522.MIFARE_Write()」を実行することでデータを書き込むことができます。
Serial.print(“データブロックではないので書き込めません”);
} else {
mfrc522.MIFARE_Write(address, write_data, 16);
// 書き込みデータをシリアルモニタに出力
for (int i = 0; i < 16; i++) {
Serial.printf(“%02X “, write_data[i]);// 16進数で出力
}
}
書込みが完了したら、読み込みの時と同様に、最後に以下(上コード「24行目」)を実行し、認証済み状態を解除して完了です。
10.MIFARE 1KB のデータ構成について
今回使用した「非接触ICカード」の「MIFARE 1KB」を使いこなすには、内部のデータ構造を理解する必要があります。
内部のデータ構造は「データの保存領域」だけでなく「認証キー(A、B)」や「アクセス条件」を決めるデータも格納されていて少々複雑なため、以下から図を交えて詳しく紹介していきます。
・全体構成の確認
全体のデータは以下表のような構造になっています。
データ領域は「0〜15」の合計16個の「セクター」に分かれており、各セクターには4つの「ブロック」があります。
各ブロックには「ブロックアドレス」が割り当てられており「16バイト」のデータ保存領域があります。
さらに各ブロックのアクセス条件(読み書きの可否等)を設定する「アクセスビット(Access Bits)」がブロックごとに「3ビット」で設定されています。
「セクター0」の「ブロック0(ブロックアドレス0)」には0〜3バイト目に「UID(固有の識別番号)」が4バイトで格納されており、このブロックは読み込み専用です。
各セクターの「ブロック0〜2(セクター0は1〜2)」が自由にデータが保存できる領域です。
「ブロック3」は「セクタートレーラー」と呼ばれ認証キー等が格納されている特別なブロックです。
・セクタートレーラー
「各セクター」の「ブロック3」にある「セクタートレーラー」のデータ構造は以下表のようになっています。
- 0〜5バイト目:6バイトの「認証キーA」
- 6〜8バイト目:3バイトのアクセスコンディション(アクセス条件)
- 9バイト目 :1バイトのデータ領域として使用可
- 10〜15バイト目:6バイトの「認証キーB」
「各セクター」ごとに「認証キーA,B」を設定することができ、各ブロックの「アクセス条件(読み書き可否等)」はここでブロックごとに設定できます。
・認証キーA、B
「セクタートレーラー」には「各セクター」の4つの「ブロック」にアクセスするための「認証キーA,B」が設定されており、データの読み書きを行うには、毎回「認証キーAまたはB」を使用して認証を行う必要があります。
工場出荷時は「認証キーA,B」の両方とも「FF FF FF FF FF FF」が設定されています。
必要に応じて書き換えて使用することができます。
・アクセスコンディション(アクセス条件)
「各ブロック」の読み書きに使用する「認証キー」や「読み書きの可否」については「各セクター」の「ブロック3(セクタートレーラー)」の「6〜8バイト目」でブロックごとに設定することができます。
設定方法は少々複雑なので、以下表を使用して詳しく紹介していきます。
以下表の緑文字の部分が「アクセス条件」に関連する部分です。
「セクタートレーラー」の「6〜8バイト」の「各ビット」の状態から「ブロック」ごとの「アクセス条件」が決定され「Access Bits」に「3ビット」で設定されます。
上の表では一見わかりにくいので、実際の工場出荷時の設定「FF 07 80」を「ビット」から以下表のように求めてみました。
「Access Bits」の内容はこの後詳しく紹介しますが、まずは希望の「Access Bits」を上表の「3ビット」に設定するとわかりやすいです。
今回は工場出荷時の設定を各ブロックの「Access Bits」の「黄塗り部」に書きます。
この値を上の「各bit」表に当てはめていきます。
まずは「8byte目」の「bit」を全て埋め、次に「7byte目」の「4〜7bit」を埋めます。
残りの「青塗り部」に論理反転した値を記入して「16進数」に変換すると「セクタートレーラー」の「6〜8byte目」を求めることができます。
あとは求めた「アクセスコンディション(アクセス条件)」で「ブロック3」を書き換えれば設定完了です。
ライブラリの関数でアクセスコンディションを算出
今回使用したライブラリ「MFRC522_I2C」には設定したいアクセスビット(Access bits)から、セクタートレーラーのアクセスコンディションを算出する関数があるので以下で紹介します。
// セクタートレーラーの初期値を設定
byte sector_traler[16] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 認証キーA(初期値)
0xFF, 0x07, 0x80, 0x69, // アクセスコンディション(初期値)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 認証キーB(初期値)
byte access_bits[4]; // 各ブロックの設定したいアクセスビットを格納(下位3bit)
byte access_condision[3]; // アクセスコンディション算出結果格納用
// 各ブロック(0〜3)のアクセスビット(Access bits)を設定
access_bits[3] = 0x01; // ブロック3(セクタートレーラー)のアクセスビットを設定([C1 C2 C3] = [0 0 1]の時「b00000001」)
access_bits[2] = 0x00; // ブロック2のアクセスビットを設定([C1 C2 C3] = [0 0 0]の時「b00000000」)
access_bits[1] = 0x02; // ブロック1のアクセスビットを設定([C1 C2 C3] = [0 1 0]の時「b00000010」)
access_bits[0] = 0x03; // ブロック0のアクセスビットを設定([C1 C2 C3] = [0 1 1]の時「b00000011」)
// 各ブロック(0〜3)のアクセスビットの設定からアクセス条件(ブロック3の6〜8byte目の値)を算出
mfrc522.MIFARE_SetAccessBits(access_condision, access_bits[0], access_bits[1], access_bits[2], access_bits[3]);
// 算出したアクセスコンディションをセクタートレーラーの6〜8byteに設定
for (int i = 0; i < 3; i++) {
sector_traler[i + 6] = access_condision[i];
}
// 液晶、シリアルモニタ表示
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.printf("AccessBits : [");
Serial.print("Access_condision: [");
for (int i = 0; i < 3; i++) {
M5.Lcd.printf("%02X ", sector_traler[i + 6]);
Serial.printf("%02X ", access_condision[i]);
}
M5.Lcd.println("]");
Serial.println("]");
上コードの「9〜12行目」で各ブロックのアクセスビットを「access_bits」配列の下位3bitで設定します。
次に以下(上コードの「14行目」)を実行すると「access_condision」配列に3byteでアクセスコンディションが生成されます。
上コードでは液晶画面とシリアルモニタに「アクセスコンディション」が「3byte」で表示されるようにしています。
生成された「アクセスコンディション」を「セクタートレーラー(ブロック3)」の「6〜8byte目」と置き換えて書き込むと設定が完了します。
・設定可能な「Access Bits」
設定可能な「アクセスコンディション」は「セクタートレーラー」と「データブロック」で異なり、それぞれ以下表のようになります。
セクタートレーラーの「Access Bits」
「セクタートレーラー」の「アクセスビット(Access Bits)」では以下表のような設定ができます。
「認証キーA,B」と「Access Bits」の読み書き(Read/Write)の可否と、読み書きに使用する「認証キー」を設定できます。
データブロックの「Access Bits」
「データブロック」の「アクセスビット(Access Bits)」では以下表のような設定ができます。
各ブロックのデータの読み書き(Read/Write)の可否と、読み書きに使用する「認証キー」を設定できます。
11.まとめ
「非接触ICカード」について、RFID(NFC)技術の用語や仕組み、データの読み書き方法について、カードのデータ構成も含めて使い方を詳しく紹介しました。
データの読み書き方法だけ確認する予定でしたが「認証キー」や「アクセス条件」等、思った以上に複雑で長文となり、結構時間がかかりましたw
おかげで自分でも「非接触ICカード」の仕組みや使い方をよく理解することができたので良い経験になりました。
自分では、あえて何かを作るというわけではないのですが・・・目的を持って読んでくださった方の参考になればと思います。
今回は「MIFARE 1KB」だけしか動作確認できませんでしたが、今回使用した「MFRC522_I2C」ライブラリでは他の規格のものも使用できそうです。
手持ちでは「xyzプリンティング」社製の3Dプリンター「ダヴィンチmini w+」のフィラメントに内蔵されているタグが「Ultralight C」でした。
純正以外のフィラメントを使って壊れるのも困るので他社品を使うつもりはなかったのですが、これを書いている段階で「xyzプリンティング社」が3Dプリンター事業から撤退するとのことで・・・今後どうなるかわかりませんが、フィラメントが買えなくなると困るので、今のうちに解析して書き換えられるようにしておきたいと思います♪
コメント