MFRC522 RFID · Astro Tech Blog

MFRC522 RFID Reader/Writer

The MFRC522 is a 13.56 MHz RFID (Radio Frequency Identification) reader/writer IC from NXP. It communicates over SPI and can read and write MIFARE Classic, MIFARE Ultralight, and NTAG series tags at distances up to 5 cm. It is widely used in access control systems, attendance trackers, library management, and NFC-based authentication projects.

MFRC522 RFID module

For this interfacing you need the following components:

  • Arduino board (Uno, Nano, Mega, etc.)
  • MFRC522 RFID module (with keychain tag and/or card)
  • Breadboard and jumper wires
  • USB cable to connect Arduino to your computer

Schematic

Connect the MFRC522 module to the Arduino as follows:

MFRC522 Module        Arduino
--------------        -------
SDA (SS)      -->     Digital Pin 10
SCK           -->     Digital Pin 13 (SCK)
MOSI          -->     Digital Pin 11 (MOSI)
MISO          -->     Digital Pin 12 (MISO)
IRQ           -->     Not connected (optional)
GND           -->     GND
RST           -->     Digital Pin 9
3.3V          -->     3.3V (NOT 5V!)

Important: The MFRC522 is a 3.3V device. Power it from the Arduino 3.3V pin. Connecting VCC to 5V will damage the module.

Pin Map

Module PinNameArduino Connection
SDA (SS)SPI Chip SelectPin 10
SCKSPI ClockPin 13 (SCK)
MOSISPI Data (Master Out)Pin 11 (MOSI)
MISOSPI Data (Master In)Pin 12 (MISO)
IRQInterrupt RequestNot connected (optional)
GNDGroundGND
RSTResetPin 9
3.3VPower3.3V

For Mega 2560: MOSI = Pin 51, MISO = Pin 50, SCK = Pin 52, SS = Pin 53. For Leonardo: MOSI = ICSP-4, MISO = ICSP-1, SCK = ICSP-3, SS = Pin 10.

Using the IRQ pin (optional)

The IRQ pin can be used for interrupt-driven tag detection instead of polling. Connect it to any digital pin and configure it to trigger on a falling edge:

pinMode(irqPin, INPUT);
attachInterrupt(digitalPinToInterrupt(irqPin), onTagDetected, FALLING);

This is more efficient for battery-powered projects.

Install necessary Library

Install the MFRC522 library by Miguel Balboa via the Library Manager (Tools > Manage Libraries).

Alternatively, using arduino-cli:

arduino-cli lib install "MFRC522"

Code with complete explanation

This sketch reads the UID (unique identifier) from MIFARE Classic RFID tags and cards. It demonstrates tag detection, UID display, and authorized tag verification.

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN   10
#define RST_PIN  9

MFRC522 rfid(SS_PIN, RST_PIN);

// List of authorized tag UIDs (change to match your tags)
// Format: {0xAA, 0xBB, 0xCC, 0xDD}
const byte authorizedTags[][4] = {
  {0xD3, 0x6A, 0x9C, 0x2B}, // Example tag 1
  {0x41, 0x5E, 0xB2, 0x1A}  // Example tag 2
};

const int tagCount = sizeof(authorizedTags) / sizeof(authorizedTags[0]);

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  rfid.PCD_Init();
  rfid.PCD_DumpVersionToSerial(); // Show firmware version

  Serial.println("MFRC522 RFID Reader Test");
  Serial.println("Place a tag near the reader...");
  Serial.println();
}

void loop()
{
  // Look for a new tag
  if (!rfid.PICC_IsNewCardPresent())
  {
    return;
  }

  // Attempt to read the tag
  if (!rfid.PICC_ReadCardSerial())
  {
    return;
  }

  // Tag detected — print UID
  Serial.print("Tag UID: ");
  printUID(rfid.uid.uidByte, rfid.uid.size);

  // Check tag type
  MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
  Serial.print("Tag type: ");
  Serial.println(rfid.PICC_GetTypeName(piccType));

  // Check if the tag is authorized
  if (isAuthorized(rfid.uid.uidByte, rfid.uid.size))
  {
    Serial.println("Status: ACCESS GRANTED");
  }
  else
  {
    Serial.println("Status: ACCESS DENIED");
  }

  Serial.println();

  // Halt PICC so it can be read again
  rfid.PICC_HaltA();

  // Stop encryption on PCD
  rfid.PCD_StopCrypto1();
}

// Print UID as hex bytes
void printUID(byte *uid, byte uidLength)
{
  Serial.print(F("0x"));

  for (byte i = 0; i < uidLength; i++)
  {
    if (uid[i] < 0x10)
    {
      Serial.print(F("0"));
    }

    Serial.print(uid[i], HEX);

    if (i < uidLength - 1)
    {
      Serial.print(F(", "));
    }
  }
  Serial.println();
}

// Check if the detected UID matches any authorized tag
bool isAuthorized(byte *uid, byte uidLength)
{
  for (int i = 0; i < tagCount; i++)
  {
    bool match = true;

    for (byte j = 0; j < uidLength; j++)
    {
      if (uid[j] != authorizedTags[i][j])
      {
        match = false;
        break;
      }
    }

    if (match)
    {
      return true;
    }
  }

  return false;
}

Code breakdown

  • #include <MFRC522.h> — includes the MFRC522 library for SPI communication with the reader.
  • MFRC522 rfid(SS_PIN, RST_PIN) — creates an MFRC522 instance with the specified SPI chip select and reset pins.
  • SPI.begin() — initializes the SPI bus.
  • rfid.PCD_Init() — initializes the MFRC522 reader. PCD stands for Proximity Coupling Device (the reader).
  • rfid.PCD_DumpVersionToSerial() — prints the reader’s firmware version to help verify the module is working.
  • rfid.PICC_IsNewCardPresent() — returns true if a new tag is within range. Non-blocking.
  • rfid.PICC_ReadCardSerial() — reads the tag’s UID into rfid.uid. Returns false if the read fails.
  • rfid.uid.uidByte — byte array containing the tag’s UID (typically 4 or 7 bytes).
  • rfid.uid.size — length of the UID in bytes.
  • rfid.PICC_GetType(sak) — returns the tag type (MIFARE Classic, Ultralight, etc.) from the SAK byte.
  • rfid.PICC_GetTypeName(type) — returns a human-readable string for the tag type.
  • rfid.PICC_HaltA() — puts the tag in halt state so it doesn’t respond to the next poll.
  • rfid.PCD_StopCrypto1() — stops encryption on the reader.

Writing data to a tag

To write data to MIFARE Classic 1K tags (16 sectors, 4 blocks each):

#include <SPI.h>
#include <MFRC522.h>

MFRC522 rfid(10, 9);
MFRC522::MIFARE_Key key;

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  rfid.PCD_Init();

  // Default key (0xFFFFFFFFFFFF)
  for (byte i = 0; i < 6; i++)
  {
    key.keyByte[i] = 0xFF;
  }
}

void loop()
{
  if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial())
  {
    return;
  }

  // Authenticate sector 1 using key A
  MFRC522::StatusCode status = rfid.PCD_Authenticate(
    MFRC522::PICC_CMD_MF_AUTH_KEY_A, 1, &key, &(rfid.uid)
  );

  if (status != MFRC522::STATUS_OK)
  {
    Serial.print("Auth failed: ");
    Serial.println(rfid.GetStatusCodeName(status));
    return;
  }

  // Data to write (16 bytes = 1 block)
  byte data[16] = "Hello RFID!    ";

  // Write to block 4 (first block of sector 1)
  status = rfid.MIFARE_Write(4, data, 16);

  if (status == MFRC522::STATUS_OK)
  {
    Serial.println("Data written successfully");
  }

  // Halt and stop crypto
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
  delay(1000);
}

Steps to perform this interfacing

  1. Connect the MFRC522 module to the Arduino as shown in the schematic. Ensure VCC goes to 3.3V, not 5V.
  2. Install the MFRC522 library by Miguel Balboa via the Library Manager.
  3. Copy the code into the Arduino IDE.
  4. Select the correct board and port (Tools > Board and Tools > Port).
  5. Upload the sketch to the Arduino.
  6. Open the Serial Monitor (Tools > Serial Monitor, set baud rate to 9600).
  7. Observe the firmware version printed — this confirms the module is communicating.
  8. Hold an RFID tag or card near the reader (~2–5 cm). The UID and tag type will be printed.
  9. Replace the example UIDs in authorizedTags[] with your tag’s UID to test access control.
  10. Press * on the Serial Monitor to read multiple tags without resetting the Arduino.

Getting your tag’s UID

The first time you run the sketch, the Serial Monitor will show your tag’s UID in hex format (e.g., 0xD3, 0x6A, 0x9C, 0x2B). Copy this value into the authorizedTags array to grant that tag access.

Caution

  • 3.3V only: The MFRC522 operates at 3.3V. Power it from the Arduino 3.3V pin. Connecting VCC to 5V will permanently damage the module. The SPI logic pins are also 3.3V-only — on 5V Arduino boards, the MFRC522 library relies on the module being 3.3V-tolerant of 5V signals, which works for most modules but is outside spec. For production use, add a level shifter or voltage divider on MOSI and SCK.
  • Antenna range: The typical read range is 2–5 cm for credit-card-sized tags and 1–3 cm for keychain fobs. Range depends on antenna design, tag size, and nearby metal objects. Do not mount the module directly on a metal surface — the antenna coil will be detuned and range will drop drastically. Maintain at least 1 cm of clearance from metal.
  • Multiple tags: The MFRC522 reads one tag at a time. If multiple tags are in range, it may read none (collision). The library includes anti-collision logic (PICC_ReadCardSerial handles this), but keeping only one tag near the reader is recommended.
  • SPI speed: The MFRC522 operates at up to 10 MHz SPI. The default Arduino SPI library runs at 4 MHz on most boards, which works reliably. If you experience communication errors, reduce the SPI clock in the library or add 10–100 Ω series resistors on the SPI lines to reduce ringing.
  • Power draw: The MFRC522 draws approximately 10–30 mA during operation and up to 80 mA during RF field generation. This is within the Arduino 3.3V regulator’s capacity (typically 150–200 mA), but if you have other 3.3V loads, use an external 3.3V regulator.
  • Tag compatibility: The MFRC522 supports MIFARE Classic (1K, 4K), MIFARE Ultralight, MIFARE DESFire, and NTAG213/215/216. It does NOT support ICODE (ISO 15693) or LEGIC tags. Check your tag type against the library’s supported list.
  • Sector authentication: To read or write data on MIFARE Classic tags, you must authenticate with the sector key. The default factory key is 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF. If a tag has been personalized with a different key, you must provide the correct key to access its data.
  • Regulatory: The MFRC522 operates at 13.56 MHz in the ISM band. It is certified for use in most countries, but if you sell or distribute a product using it, ensure your device passes local EMC regulations.