pdf-icon

Arduino Quick Start

2. Devices & Examples

6. Applications

Dial RFID (NFC)

APIs and example programs related to Dial RFID (NFC).

Reading Position
If the RFID tag is smaller than the Dial’s outline, pay attention to where the tag touches the Dial. It’s recommended not to place it at the exact screen center where it is fully enclosed by the Dial bezel; keep the tag’s center away from the M5 logo and instead near the arrow mark on the opposite side.

Example Program

Build Requirements

  • M5Stack board manager version >= 3.2.2
  • Board option = M5Dial
  • M5Dial library version >= 1.0.3

Read UID Example

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#include "M5Dial.h"

void setup() {
  auto cfg = M5.config();
  M5Dial.begin(cfg, false, true);  // encoder, RFID
  Serial.begin(115200);
}

void loop() {
  // PICC: Proximity Integrated Circuit Card
  if (M5Dial.Rfid.PICC_IsNewCardPresent() && M5Dial.Rfid.PICC_ReadCardSerial()) {
    M5Dial.Display.clear();

    uint8_t piccType = M5Dial.Rfid.PICC_GetType(M5Dial.Rfid.uid.sak);
    Serial.print(F("PICC type: "));
    Serial.println(M5Dial.Rfid.PICC_GetTypeName(piccType));

    // Check if the tag / card is of type MIFARE Classic
    if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI && piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
      Serial.println(F("This tag / card is not of type MIFARE Classic.\n"));
      delay(500);
      return;
    }

    // Output the stored UID data
    for (byte i = 0; i < M5Dial.Rfid.uid.size; i++) {
      Serial.printf("%02X ", M5Dial.Rfid.uid.uidByte[i]);
    }
    Serial.println("\n");
    delay(500);
  }
}

Read/Write Card Example

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
#include "M5Dial.h"

MFRC522::MIFARE_Key key;

void setup() {
  auto cfg = M5.config();
  M5Dial.begin(cfg, false, true);  // encoder, RFID
  Serial.begin(115200);

  // Prepare the key (used both as key A and as key B) with FFFFFFFFFFFFh,
  // which is the default at chip delivery from the factory.
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

// Helper routine to dump a byte array as hex values to Serial.
void dump_byte_array(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(buffer[i] < 0x10 ? " 0" : " ");
    Serial.print(buffer[i], HEX);
  }
}

void loop() {
  M5Dial.update();

  // PICC: Proximity Integrated Circuit Card
  if (M5Dial.Rfid.PICC_IsNewCardPresent() && M5Dial.Rfid.PICC_ReadCardSerial()) {
    M5Dial.Display.clear();

    uint8_t piccType = M5Dial.Rfid.PICC_GetType(M5Dial.Rfid.uid.sak);
    Serial.print(F("PICC type: "));
    Serial.println(M5Dial.Rfid.PICC_GetTypeName(piccType));

    // Check if the tag / card is of type MIFARE Classic
    if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI && piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
      Serial.println(F("This tag / card is not of type MIFARE Classic.\n"));
      delay(500);
      return;
    }

    // Output the stored UID data
    String uid = "";
    for (byte i = 0; i < M5Dial.Rfid.uid.size; i++) {
      Serial.printf("%02X ", M5Dial.Rfid.uid.uidByte[i]);
      uid += String(M5Dial.Rfid.uid.uidByte[i], HEX);
    }
    Serial.println("\n");

    // M5Dial.Rfid.PICC_DumpToSerial(&(M5Dial.Rfid.uid));
    // Serial.println("\n");

    // In this example, we use the second sector (sector #1) including blocks #4, #5, #6, #7
    byte sector = 1;
    byte blockAddr = 4;
    byte trailerBlock = 7;
    byte dataBlock[] = {
      0x01, 0x02, 0x03, 0x04,  //  1,  2,  3,   4,
      0x05, 0x06, 0x07, 0x08,  //  5,  6,  7,   8,
      0x09, 0x0a, 0x0b, 0x0c,  //  9, 10, 11,  12,
      0x0d, 0x0e, 0x0f, 0xff   // 13, 14, 15, 255
    };
    MFRC522::StatusCode status;
    byte buffer[18];
    byte size = sizeof(buffer);

    // Authenticate using key A
    Serial.println(F("Authenticating using key A..."));
    status = (MFRC522::StatusCode)M5Dial.Rfid.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(M5Dial.Rfid.uid));
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("PCD_Authenticate() failed: "));
      Serial.println(M5Dial.Rfid.GetStatusCodeName(status));
      return;
    }

    // Show the whole sector as it currently is
    Serial.println(F("Current data in sector: "));
    M5Dial.Rfid.PICC_DumpMifareClassicSectorToSerial(&(M5Dial.Rfid.uid), &key, sector);
    Serial.println("");

    // Read data from the block
    Serial.print(F("Reading data from block #"));
    Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode)M5Dial.Rfid.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("MIFARE_Read() failed: "));
      Serial.println(M5Dial.Rfid.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block #"));
    Serial.print(blockAddr);
    Serial.println(F(": "));
    dump_byte_array(buffer, 16);
    Serial.println("\n");

    // Authenticate using key B
    Serial.println(F("Authenticating again using key B..."));
    status = (MFRC522::StatusCode)M5Dial.Rfid.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(M5Dial.Rfid.uid));
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("PCD_Authenticate() failed: "));
      Serial.println(M5Dial.Rfid.GetStatusCodeName(status));
      return;
    }

    // Write data to the block
    Serial.print(F("Writing data into block #"));
    Serial.print(blockAddr);
    Serial.println(F(" ..."));
    dump_byte_array(dataBlock, 16);
    Serial.println();
    status = (MFRC522::StatusCode)M5Dial.Rfid.MIFARE_Write(blockAddr, dataBlock, 16);
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("MIFARE_Write() failed: "));
      Serial.println(M5Dial.Rfid.GetStatusCodeName(status));
    }
    Serial.println("");

    // Read data from the block again, now it should be what we have written
    Serial.print(F("Reading data from block #"));
    Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode)M5Dial.Rfid.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("MIFARE_Read() failed: "));
      Serial.println(M5Dial.Rfid.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block #"));
    Serial.print(blockAddr);
    Serial.println(F(": "));
    dump_byte_array(buffer, 16);
    Serial.println("\n");

    // Check if the data in block is what we have written, by counting the number of bytes that are equal
    Serial.println(F("Checking result..."));
    byte count = 0;
    for (byte i = 0; i < 16; i++) {
      // Compare buffer (what we've read) with dataBlock (what we've written)
      if (buffer[i] == dataBlock[i]) {
        count++;
      }
    }
    Serial.print(F("Number of bytes that match = "));
    Serial.println(count);
    if (count == 16) {
      Serial.println(F("Success :-)"));
    } else {
      Serial.println(F("Failure, no match :-("));
      Serial.println(F("  perhaps the write didn't work properly..."));
    }
    Serial.println();

    // Dump the sector data
    Serial.println(F("Current data in sector: "));
    M5Dial.Rfid.PICC_DumpMifareClassicSectorToSerial(&(M5Dial.Rfid.uid), &key, sector);
    Serial.println("");

    // Halt PICC
    M5Dial.Rfid.PICC_HaltA();
    // Stop encryption on PCD
    M5Dial.Rfid.PCD_StopCrypto1();

    Serial.println("====================");
  }
}

This program reads the data from Block 4 in Sector 1 after completing Key A authentication, and then modifies the data of Block 4 in Sector 1 after completing Key B authentication. During this process, the tag / card must remain close to the Dial. The program output is as follows:

Init API

Below are usage notes for the common APIs. A typical flow for reading / writing a MIFARE card is:

  1. Initialize RFID
  2. Detect the presence of a new card and obtain the card UID (Unique Identifier)
  3. Select the card by UID to enter the active state
  4. Unlock the target Block using Key A or Key B
  5. Read / Write data
  6. Set the card to the sleep state

Operation return status codes

cpp
1 2 3 4 5 6 7 8 9 10 11
enum StatusCode {
    STATUS_OK             = 1,  // Success
    STATUS_ERROR          = 2,  // Error in communication
    STATUS_COLLISION      = 3,  // Collision detected
    STATUS_TIMEOUT        = 4,  // Timeout in communication
    STATUS_NO_ROOM        = 5,  // The buffer is not big enough
    STATUS_INTERNAL_ERROR = 6,  // Internal error in the code. Should not happen ;-)
    STATUS_INVALID        = 7,  // Invalid argument
    STATUS_CRC_WRONG      = 8,  // The CRC_A does not match
    STATUS_MIFARE_NACK    = 9   // The MIFARE PICC responded with NAK
};

begin

Function Prototype:

void begin();

Description:

  • Initialize RFID.

When calling M5Dial.begin(), you can set the parameter enableRFID to true to initialize it together.

M5Dial.begin(m5::M5Unified::config_t cfg, bool enableEncoder, bool enableRFID);

Input Parameters:

  • null

Return Value:

  • null

PICC_IsNewCardPresent

Function Prototype:

bool PICC_IsNewCardPresent();

Description:

  • Scan to check whether there is a card that has not been detected yet and is in the IDLE state. Cards in the HALT state will be ignored.

Input Parameters:

  • null

Return Value:

  • bool
    • true: A new card was detected
    • false: No new card was detected

PICC_ReadCardSerial

Function Prototype:

bool PICC_ReadCardSerial();

Description:

  • Read the card UID. After a successful read, the UID can be accessed from the class member Uid uid;. Before performing the read operation, you must call PICC_IsNewCardPresent(), PICC_RequestA(), or PICC_WakeupA() to ensure a card is detected.
cpp
1 2 3
for (byte i = 0; i < M5Dial.Rfid.uid.size; i++) {
  Serial.printf("%02X ", M5Dial.Rfid.uid.uidByte[i]);
}

Input Parameters:

  • null

Return Value:

  • bool
    • true: Read successful
    • false: Read failed

PICC_RequestA

Function Prototype:

uint8_t PICC_RequestA(uint8_t *bufferATQA, uint8_t *bufferSize);

Description:

  • Scan and detect Type A standard cards within reading range.

Input Parameters:

  • uint8_t *bufferATQA
    • Buffer to store the ATQA (Answer To Request) response.
  • uint8_t *bufferSize
    • Length of the buffer (>2 byte).

Return Value:

  • uint8_t
    • StatusCode

PICC_WakeupA

Function Prototype:

uint8_t PICC_WakeupA(uint8_t *bufferATQA, uint8_t *bufferSize);

Description:

  • Wake up Type A standard cards within range.

Input Parameters:

  • uint8_t *bufferATQA
    • Buffer to store the ATQA (Answer To Request) response.
  • uint8_t *bufferSize
    • Length of the buffer (>2 byte).

Return Value:

  • uint8_t
    • StatusCode

PICC_Select

Function Prototype:

uint8_t PICC_Select(Uid *uid, uint8_t validBits = 0);

Description:

  • Select a card by UID and set it to the active state.

Input Parameters:

  • Uid *uid
    • Pointer to the UID structure obtained by scanning the card.
  • uint8_t validBits
    • Number of valid bits in the last uint8_t; 0 means all 8 bits are valid.

Return Value:

  • uint8_t
    • StatusCode

PICC_HaltA

Function Prototype:

uint8_t PICC_HaltA();

Description:

  • Put the currently selected card into the sleep state.

Input Parameters:

  • null

Return Value:

  • uint8_t
    • StatusCode

MIFARE API

PCD_Authenticate

Function Prototype:

uint8_t PCD_Authenticate(uint8_t command, uint8_t blockAddr, MIFARE_Key *key, Uid *uid);

Description:

  • Perform MIFARE authentication. Before calling this function, the card must be selected to enter the active state. After communication with the authenticated PICC is completed, you must call PCD_StopCrypto1(); otherwise, new communication cannot be started.

Input Parameters:

  • uint8_t command
    • PICC_CMD_MF_AUTH_KEY_A
    • PICC_CMD_MF_AUTH_KEY_B
  • uint8_t blockAddr
    • Block address
  • MIFARE_Key *key
    • By default, both Key A and Key B are set to FFFFFFFFFFFF
  • Uid *uid

Return Value:

  • uint8_t
    • StatusCode

PCD_StopCrypto1

Function Prototype:

void PCD_StopCrypto1();

Description:

  • Exit the authentication state of the PCD. After communication with the authenticated PICC is completed, you must call PCD_StopCrypto1(); otherwise, new communication cannot be started.

Input Parameters:

  • null

Return Value:

  • null

MIFARE_Read

Function Prototype:

uint8_t MIFARE_Read(uint8_t blockAddr, uint8_t *buffer, uint8_t *bufferSize);

Description:

  • Read data from the specified blockAddr.

Input Parameters:

  • uint8_t blockAddr
    • Block address in the actual card sector.
  • uint8_t *buffer
    • Pointer to the buffer for receiving data.
  • uint8_t *bufferSize
    • Length of the buffer for receiving data (>= 18 byte).

Return Value:

  • uint8_t
    • StatusCode

MIFARE_Write

Function Prototype:

uint8_t MIFARE_Write(uint8_t blockAddr, uint8_t *buffer, uint8_t bufferSize);

Description:

  • Write data to the specified blockAddr.

Input Parameters:

  • uint8_t blockAddr
    • Block address in the actual card sector.
  • uint8_t *buffer
    • Pointer to the buffer containing the data to be written.
  • uint8_t *bufferSize
    • Length of the buffer for writing data (16 byte).

Return Value:

  • uint8_t
    • StatusCode
On This Page