If you are pairing an Arduino and RFID reader for a DIY access control, attendance logger, or lock-box project, the 13.56 MHz MFRC522 module is your default pick. It costs under $4, reads standard MIFARE Classic fobs, and communicates over SPI. However, because the MFRC522 is strictly a 3.3V device and the standard Arduino Uno runs at 5V, skipping a logic level shifter will eventually fry the reader's MISO pin. Below is the exact hardware stack, SPI pin mapping, and fail-proof code to get your reader scanning UIDs on the first try.

Which RFID Module Should You Actually Buy?

Not all RFID is created equal. Hobbyist bins are full of mismatched cards and readers operating on different frequencies. Use this decision path to select the right module for your build.

Module Frequency Protocol / Interface Cost (Approx) Best Used For
MFRC522 13.56 MHz SPI / I2C / UART $2 - $4 Standard MIFARE fobs, basic access control, DIY lockboxes.
PN532 13.56 MHz SPI / I2C / HSU $8 - $12 NFC emulation, reading smartphones, peer-to-peer data exchange.
RDM6300 125 kHz UART (Serial) $5 - $7 Legacy 125 kHz EM4100 access cards, older building fobs.
Default Pick: Choose the MFRC522 for 90% of Arduino projects. It has the widest library support (via Miguel Balboa's MFRC522 library) and the lowest cost. Only upgrade to the PN532 if you specifically need to read NFC data from an iPhone or Android device.

Parts List and SPI Pin Mapping

The most common point of failure in Arduino RFID builds is voltage mismatch. The NXP MFRC522 datasheet explicitly states the digital pins are not 5V tolerant. Feeding 5V from an Uno's SPI header into the 3.3V MFRC522 will cause latent failure, usually starting with corrupted MISO data and ending in a dead chip.

Required Hardware

  • Microcontroller: Arduino Uno R3 (ATmega328P, 5V logic)
  • RFID Module: MFRC522 Breakout Board (13.56 MHz, SPI variant)
  • Level Shifter: BSS138 4-Channel Bidirectional Logic Level Converter (crucial for 5V to 3.3V translation)
  • Wiring: 22 AWG solid core or standard Dupont jumper wires
  • RFID Tags: 13.56 MHz MIFARE Classic 1K cards or key fobs

SPI Pin Mapping Table

Wire the Arduino to the high-voltage (HV) side of the level shifter, and the MFRC522 to the low-voltage (LV) side. Ensure the level shifter's LV reference is tied to the Arduino's 3.3V output.

MFRC522 Pin Level Shifter (LV) Level Shifter (HV) Arduino Uno R3 Pin Function
3.3V LV HV 5V Power References
GND GND GND GND Common Ground
RST LV1 HV1 Pin 9 Reset / Power Down
SDA (SS) LV2 HV2 Pin 10 SPI Slave Select
MOSI LV3 HV3 Pin 11 SPI Master Out Slave In
MISO LV4 HV4 Pin 12 SPI Master In Slave Out
SCK (Direct) (Direct) Pin 13 SPI Clock (Often 3.3V tolerant, but shifter preferred)

Compilable Arduino Code with Error Handling

This code targets the Arduino Uno R3. It requires the MFRC522 library by GithubCommunity (originally Miguel Balboa), installable via the Arduino Library Manager. The script includes hardware initialization checks, robust UID extraction, and a master-key comparison to trigger an access event.

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

// Pin Definitions for Arduino Uno R3
#define RST_PIN         9
#define SS_PIN          10

MFRC522 mfrc522(SS_PIN, RST_PIN);

// Master UID for access control (Replace with your scanned fob UID)
byte masterUID[4] = {0xDE, 0xAD, 0xBE, 0xEF};

void setup() {
  Serial.begin(9600);
  while (!Serial); // Wait for serial port to connect (needed for Leonardo/Micro)
  
  SPI.begin(); // Init SPI bus
  
  // Initialize MFRC522 hardware
  mfrc522.PCD_Init();
  
  // Hardware verification: Check PCD version
  byte version = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
  if (version == 0x00 || version == 0xFF) {
    Serial.println(F("CRITICAL: PCD Init Failed. Check RST pin and 3.3V power."));
    while(1); // Halt execution
  }
  Serial.print(F("MFRC522 Firmware Version: 0x"));
  Serial.println(version, HEX);
  Serial.println(F("System Ready. Scan a 13.56MHz tag..."));
}

void loop() {
  // 1. Look for new cards
  if (!mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  // 2. Select one of the cards
  if (!mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  // 3. Process the UID
  Serial.print(F("Scanned UID: "));
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    if (mfrc522.uid.uidByte[i] < 0x10) Serial.print(F(" 0"));
    else Serial.print(F(" "));
    Serial.print(mfrc522.uid.uidByte[i], HEX);
  }
  Serial.println();

  // 4. Access Control Logic
  if (checkAccess(mfrc522.uid.uidByte, mfrc522.uid.size)) {
    Serial.println(F(">> ACCESS GRANTED. Unlocking relay..."));
    // triggerRelay(); // Add your relay control code here
  } else {
    Serial.println(F(">> ACCESS DENIED. Unknown tag."));
  }

  // Halt PICC and stop crypto1 to prevent read-lockups on subsequent scans
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  
  delay(500); // Debounce delay
}

bool checkAccess(byte *scannedUID, byte uidSize) {
  if (uidSize != 4) return false; // MFRC522 standard UIDs are 4 bytes
  for (byte i = 0; i < 4; i++) {
    if (scannedUID[i] != masterUID[i]) {
      return false;
    }
  }
  return true;
}

Debugging: The First Three Things to Check When It Fails

RFID builds rarely fail due to bad code; they fail due to SPI bus contention, voltage sag, or frequency mismatches. If your Serial Monitor is misbehaving, follow this diagnostic tree.

1. Error: Firmware Version reads 0x00 or 0xFF

Exact Serial Output: CRITICAL: PCD Init Failed. Check RST pin and 3.3V power.

  • Cause A (Most Likely): The RST pin is floating or not receiving 3.3V. The MFRC522 requires a hard reset pulse on startup. Verify your Uno Pin 9 is wired through the level shifter to the RST pin.
  • Cause B: 3.3V rail sag. The Arduino Uno's onboard 3.3V LDO is only rated for ~150mA. If you are powering a level shifter, an LCD screen, and the MFRC522 from the same 3.3V rail, the voltage will drop below the 3.0V minimum required for the PCD to boot. Power the HV side of the shifter from 5V, and ensure the LV side has a clean 3.3V reference.

2. Error: "Timeout in communication" or STATUS_TIMEOUT

Exact Library Status String: Timeout in communication. (Returned when using mfrc522.GetStatusCodeName(status) during read/write operations).

  • Cause A: SPI Clock speed is too high for the breadboard capacitance. The Arduino SPI Reference defaults to 4MHz. Long Dupont wires act as capacitors, rounding off the square wave. Fix: Add SPI.setClockDivider(SPI_CLOCK_DIV4); right after SPI.begin() in your setup loop to drop the clock to a more forgiving speed.
  • Cause B: MISO and MOSI are swapped. It is a common mistake to wire Master-Out-Slave-In to the Master's MISO pin. Remember: The Uno's MOSI (Pin 11) must feed the MFRC522's MOSI. The Uno's MISO (Pin 12) must read from the MFRC522's MISO.

3. Symptom: Card is placed on the antenna, but Serial Monitor shows nothing

  • Cause A: You are using a 125 kHz card (like an HID ProxCard or EM4100) on a 13.56 MHz reader. The physics of the antenna coil will not induce a current in a mismatched tag. Check the printing on your fob; if it doesn't say MIFARE or 13.56MHz, it won't work.
  • Cause B: The antenna coil solder joints on the MFRC522 PCB are cracked. This is common on cheap clone boards. Inspect the two large solder pads connecting the copper coil to the PCB. Reflow them with a touch of flux and a soldering iron if they look dull or fractured.
Safety Note: If you are wiring this RFID reader to control a physical magnetic door lock or an electric strike, never power the lock directly from the Arduino's 5V pin. Use a separate 12V power supply and a flyback diode across a 5V relay module to prevent inductive kickback from frying your microcontroller.

Extending or Simplifying Your RFID Build

Once you have the basic UID reader working, you will likely want to adapt the hardware for a permanent installation or a more complex network.

How to Simplify: Ditch the Level Shifter with an ESP32

If you are tired of managing 4-channel level shifters and messy breadboard wiring, migrate your code to an ESP32 DevKit V1. The ESP32 is natively a 3.3V microcontroller. You can wire the MFRC522 directly to the ESP32's SPI pins (usually GPIO 5 for SS, GPIO 18 for SCK, GPIO 19 for MISO, GPIO 23 for MOSI) without any voltage translation hardware. The exact same MFRC522 library compiles perfectly in the Arduino IDE when the ESP32 board package is installed.

How to Extend: Networked MQTT Logging

For a multi-door office or a maker-space attendance tracker, local serial printing isn't enough. Extend the build by adding an ESP8266 or using the aforementioned ESP32. Use the PubSubClient library to publish the scanned UID as a JSON payload to an MQTT broker (like Mosquitto running on a Raspberry Pi).

Implementation Tip: Do not block the RFID polling loop while waiting for WiFi connections. Use a non-blocking state machine or FreeRTOS tasks (on the ESP32) to handle network reconnection in the background while the main core continues to poll the SPI bus for MIFARE tags. This ensures a user is never left waiting at a door because the microcontroller was stuck in a WiFi.reconnect() timeout loop.