プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

ArduinoでNFCタグを読み書きする

はじめに

NFCタグは、電池がいらない、かざすだけでデータをやり取りできる超小型の記録媒体です。
外側はプラスチックなどですが、中にはICチップ(データを保存する脳の部分)とアンテナ(銅線がぐるぐると巻かれたもの)が入っています。
NFCタグを読み取り書き込み装置にかざすと、このアンテナに電磁誘導で電流が流れることでICチップが動作します。
SuicaEdyなどで使われるFeliCaもこの一つです。

使ってみた

https://ja.aliexpress.com/w/wholesale-NFC%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB.html?spm=a2g0o.productlist.search.0
AliExpressなどで入手できるモジュールを試してみました。

やってみたことは、以下です。
・カード固有のPIDを読み取ることと
・不揮発領域の書き込み
・不揮発領域の読み取り

電子工作の利用アイデアとしては、自作のカードリーダ付き電子錠(入場ゲート)であったり、カード情報を読み取って何かアクションするもの、備品管理システム、ユーザー識別、といったものが考えられます。

このNFCモジュールはI2CとSPI(他にも使いようがあるのかも?)に対応しているようですが、今回は回路の簡単さからI2Cで使ってみました。
モジュールにはスライドスイッチが2つありますが、SET0=H、SET1=Lでi2cモードとなります。

Arduino R3ではI2Cは簡単に接続できますが、R4ではGPIOの仕様が変わっているらしく、SDA/SCLを3.3Vにプルアップが必要となります。

「特定のカードであるかどうか」だけ読み取ればいい場合は以下のようにUIDを読み取ればOKです。

isSuccess = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);

カードにデータを書き込むには認証と書き込みを実行します。

void writeNFCdata(uint8_t* uid, uint8_t uidLength, uint8_t* writeData) {
  uint8_t keya[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // デフォルトキー
  uint8_t isSuccess = 0;

  // 書き込み認証 (Block 4 が含まれるセクターに対して行う)
  isSuccess = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);

  if (isSuccess) {
    Serial.println("Authentication isSuccess.");

    // 書き込み実行 (Block 4)
    isSuccess = nfc.mifareclassic_WriteDataBlock(4, writeData);
    if (isSuccess) {
      Serial.println("Write Block 4 isSuccessful!");
    } else {
      Serial.println("Write failed.");
    }
  }
}

データの読み取りも認証と読み取りの実行で実現できます。
ただし、書き込み→読み取りなど連続して行う場合にも認証をスキップできるとは限らないようです。

void readNFCdata(uint8_t* uid, uint8_t uidLength, uint8_t* readData) {
  // 読み取りの前にもう一度認証を行う
  uint8_t keya[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // デフォルトキー
  uint8_t isSuccess = 0;

  isSuccess = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);
  if (isSuccess) {
    // 読み取り確認
    isSuccess = nfc.mifareclassic_ReadDataBlock(4, readData);
    Serial.println("Read Block 4:");

    //ほぼ同等の組み込み関数
    //nfc.PrintHexChar(data, 16);

    //HEX
    for (uint8_t i = 0; i < 16; i++) {
      Serial.print(readData[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");

    //CHAR
    for (uint8_t i = 0; i < 16; i++) {
      Serial.print((char)readData[i]);
    }
  } else if (uidLength == 7) {
    Serial.println("Mifare Ultralight detected. Please use Ultralight functions.");

  } else {
    Serial.println("Auth failed. Check if the key is correct or tag is locked.");
  }
}

まとめ

ArduinoNFCカードの読み書きを試してみました。

ソースコード

// R4ではSDA/SCLピンまたはAnalogピンを使い、3.3Vにプルアップ(2.4kΩ)
// R3ではAnalogピンを使う SDA A4 / SCL A5
// PN532ボード SET0=H、SET1=Lでi2cモード
#include <Wire.h>
#include <Adafruit_PN532.h>

#define PN532_IRQ (2)
#define PN532_RESET (4)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);
  Serial.println("PN532 start.");
  Wire.begin();
  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();

  //見つからなかったら停止
  if (!versiondata) {
    Serial.println("\nPN532 not found!");
    while (1) {};
  }
  nfc.SAMConfig();
}

void readNFCdata(uint8_t* uid, uint8_t uidLength, uint8_t* readData) {
  // 読み取りの前にもう一度認証を行う
  uint8_t keya[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // デフォルトキー
  uint8_t isSuccess = 0;

  isSuccess = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);
  if (isSuccess) {
    // 読み取り確認
    isSuccess = nfc.mifareclassic_ReadDataBlock(4, readData);
    Serial.println("Read Block 4:");

    //ほぼ同等の組み込み関数
    //nfc.PrintHexChar(data, 16);

    //HEX
    for (uint8_t i = 0; i < 16; i++) {
      Serial.print(readData[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");

    //CHAR
    for (uint8_t i = 0; i < 16; i++) {
      Serial.print((char)readData[i]);
    }
  } else if (uidLength == 7) {
    Serial.println("Mifare Ultralight detected. Please use Ultralight functions.");

  } else {
    Serial.println("Auth failed. Check if the key is correct or tag is locked.");
  }
}

void writeNFCdata(uint8_t* uid, uint8_t uidLength, uint8_t* writeData) {
  uint8_t keya[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // デフォルトキー
  uint8_t isSuccess = 0;

  // 書き込み認証 (Block 4 が含まれるセクターに対して行う)
  isSuccess = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);

  if (isSuccess) {
    Serial.println("Authentication isSuccess.");

    // 書き込み実行 (Block 4)
    isSuccess = nfc.mifareclassic_WriteDataBlock(4, writeData);
    if (isSuccess) {
      Serial.println("Write Block 4 isSuccessful!");
    } else {
      Serial.println("Write failed.");
    }
  }
}

void loop() {
  uint8_t isSuccess = 0;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;

  //NFCタグがあるか? 500msでタイムアウト
  isSuccess = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);

  if (isSuccess) {
    Serial.print("\nUID:");
    for (uint8_t i = 0; i < 7; i++) {
      Serial.print(uid[i]);
      Serial.print(",");
    }
    Serial.println("");

    // Mifare Classic (UID 4バイト) の処理
    if (uidLength == 4) {
      Serial.println("Mifare Classic detected.");

      // 書き込みデータ (Classicは1ブロック必ず16バイト必要)
      uint8_t writeData[16] = { '1', '0', '7', 'T', 'e', 's', 't', '!', 0, 0, 0, 0, 0, 0, 0, 0 };
      writeNFCdata(&uid[0], uidLength, &writeData[0]);

      delay(200);
      
      // 読み取り中にタグを離すと失敗するケースがある
      uint8_t readData[16];
      readNFCdata(&uid[0], uidLength, &readData[0]);

      //繰り返し同じタグを読まないように
      delay(3000);
    }
    delay(100);
  }
}