The most reliable, cost-effective way to build an Arduino RFID reader for 13.56MHz Mifare tags is using the NXP MFRC522 module over SPI. It costs roughly $3 to $5, reads tag UIDs in under 50ms, and integrates seamlessly with standard microcontrollers. However, it is notoriously unforgiving regarding logic levels and breadboard wiring. If you ignore the 3.3V requirement or swap your MISO/MOSI lines, the module will fail silently or permanently brick its internal LDO.
This guide provides the exact hardware spec sheet, a verified pin mapping, complete compilable C++ code with hardware-level error handling, and a bench-tested debugging framework for when the reader inevitably times out.
Project Spec Sheet & Parts List
Before wiring anything, verify you have the correct variants. The MFRC522 ecosystem has a few traps for first-time buyers, specifically regarding frequency and interface.
| Component | Exact Variant Required | Notes & Warnings |
|---|---|---|
| Microcontroller | Arduino Uno R3 or Uno R4 Minima/WiFi | Code targets standard AVR/RA4M1 SPI hardware. 5V logic boards require care (see wiring). |
| RFID Module | MFRC522 8-Pin SPI Breakout | Must be the 8-pin SPI version. Do not buy the 16-pin I2C/UART variant for this code. |
| RFID Tags | Mifare Classic 1K or NTAG215 | Must be 13.56 MHz. 125kHz EM4100 fobs will not trigger the antenna. |
| Wiring | Female-to-Male Dupont, 20cm | Keep SPI traces under 30cm to prevent signal degradation and clock skew. |
Time to Complete: 30–45 minutes
Estimated Cost: $8 – $12 USD (2026 pricing)
Hardware Wiring & Pin Mapping
The MFRC522 communicates via the Serial Peripheral Interface (SPI). On the Arduino Uno architecture, the hardware SPI pins are fixed. You cannot move SCK, MOSI, or MISO to arbitrary digital pins without resorting to slow, unreliable software bit-banging.
| MFRC522 Pin | Arduino Uno Pin | Function & Critical Notes |
|---|---|---|
| SDA (SS) | D10 | SPI Slave Select. Configurable in code, but D10 is standard for Uno. |
| SCK | D13 | SPI Clock. Must be D13 on Uno R3/R4. |
| MOSI | D11 | Master Out Slave In. Data from Arduino to RFID module. |
| MISO | D12 | Master In Slave Out. Data from RFID module to Arduino. |
| IRQ | Not Connected | Interrupt pin. Leave floating for basic polling implementations. |
| GND | GND | Common ground. Must share the same ground plane as the Arduino. |
| RST | D9 | Hardware Reset. Configurable in code. Pulls the IC out of sleep. |
| VCC | 3.3V | CRITICAL: The MFRC522 IC operates at 3.3V. Feeding it 5V will destroy it. |
Complete Compilable Code (Arduino Uno R3/R4)
This code relies on the industry-standard Miguel Balboa MFRC522 library. Install it via the Arduino Library Manager (search "MFRC522") before compiling. The code below includes a hardware-level verification step to ensure the SPI bus is actually talking to the silicon, preventing the common "silent failure" loop.
#include <SPI.h>
#include <MFRC522.h>
// Pin Definitions for Arduino Uno R3/R4
#define SS_PIN 10
#define RST_PIN 9
// Instantiate MFRC522 object
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial monitor (optional for R4/Leonardo)
// Initialize SPI bus
SPI.begin();
// Initialize MFRC522 module
rfid.PCD_Init();
// --- ERROR HANDLING: Verify Hardware Communication ---
// Read the Version Register. A genuine MFRC522 returns 0x91 or 0x92.
// If it returns 0x00 or 0xFF, the SPI link is dead.
byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg);
if (version == 0x00 || version == 0xFF) {
Serial.println(F("ERROR: Communication with MFRC522 failed."));
Serial.println(F("Check SPI wiring, ensure VCC is 3.3V, and verify RST pin."));
while (1) {
// Halt execution to prevent infinite serial spam
delay(1000);
}
}
Serial.print(F("SUCCESS: MFRC522 Firmware Version: 0x"));
Serial.println(version, HEX);
Serial.println(F("Scan a 13.56MHz Mifare PICC (card/fob)..."));
}
void loop() {
// 1. Check if a new card is present in the RF field
if (!rfid.PICC_IsNewCardPresent()) {
return;
}
// 2. Attempt to read the card's serial number (UID)
if (!rfid.PICC_ReadCardSerial()) {
return;
}
// 3. Process the UID
Serial.print(F("Tag UID: "));
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) Serial.print(F("0"));
Serial.print(rfid.uid.uidByte[i], HEX);
if (i < rfid.uid.size - 1) Serial.print(F(":"));
}
Serial.println();
// 4. Identify the PICC type (Mifare Classic, Ultralight, etc.)
MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
Serial.print(F("PICC Type: "));
Serial.println(rfid.PICC_GetTypeName(piccType));
// 5. Halt the card and stop crypto operations to allow next read
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
Debugging: "Communication Timeout" & Common Failures
If your serial monitor outputs ERROR: Communication with MFRC522 failed. or Firmware Version: 0x00, the microcontroller cannot read the MFRC522 internal registers. Do not assume the module is dead right out of the box. Run through these first three checks in exact order:
- Verify VCC and GND Rails: Use a multimeter to measure the voltage directly at the module's VCC and GND pins. You must read between 3.2V and 3.4V. If you read 0V, your breadboard power rail is split or the jumper is dead. If you read 5V, you are feeding it the wrong rail and may have already cooked the IC.
- Check the MOSI/MISO Crossover: SPI requires Master-Out to connect to Slave-In, and vice versa. Arduino D11 (MOSI) must go to the module's MOSI. Arduino D12 (MISO) must go to the module's MISO. Swapping these is the #1 cause of 0x00 register reads.
- Measure RST Continuity: The MFRC522 will not initialize if the RST pin is floating or held LOW. With the power off, use your multimeter's continuity mode to verify a solid connection between Arduino D9 and the module's RST pin.
Other Ranked Failure Modes
- Tag Ignored: You are using a 125kHz EM4100 fob. The MFRC522 antenna is physically tuned to 13.56MHz. The physics of magnetic resonance mean it literally cannot energize a 125kHz coil.
- Reads Once, Then Freezes: You forgot to call
rfid.PICC_HaltA()at the end of the loop. The tag remains in an active, encrypted state and will reject subsequent polling requests until it is physically removed from the RF field. - Intermittent Drops: Your SPI jumper wires are longer than 30cm or are routed parallel to high-current AC lines, causing clock skew and bit errors on the MISO line.
Extending and Simplifying the Build
Once you have the UID printing to the serial monitor, the next step is usually actuating hardware or logging data. Here is how to adapt the build for real-world constraints.
How to Extend (Adding Access Control)
To turn this into a door strike controller, add a 5V relay module. Wire the relay's IN pin to Arduino D8, and power the relay's VCC from the Arduino's 5V pin (or an external 5V supply if the relay coil draws >40mA). In the loop(), compare the scanned UID array against a hardcoded whitelist array using memcmp(). If it matches, pull D8 HIGH for 3 seconds to energize the relay and release the maglock.
How to Simplify (Resolving SPI Pin Conflicts)
If you need to add an Ethernet shield (like the W5100) or an SD card module, you will run into an SPI bus conflict, as those shields aggressively hog the hardware SPI pins and often fail to release the MISO line.
The Fix: Switch from the MFRC522 to the PN532 NFC module. The PN532 supports I2C and UART in addition to SPI. By wiring the PN532 via I2C (SDA to A4, SCL to A5 on the Uno), you completely free up the SPI bus for your Ethernet or SD shields while retaining full 13.56MHz Mifare compatibility.
Arduino RFID Reader FAQ
Can an Arduino RFID reader read 125kHz key fobs?
No. The MFRC522 module is strictly designed for the 13.56MHz ISO/IEC 14443 standard (Mifare, NTAG, Desfire). If your building uses older, thicker 125kHz proximity cards (like HID Prox or EM4100), you must use a dedicated 125kHz reader module like the RDM6300 or Seeed Studio's 125kHz RFID kit. The antenna tuning capacitors on the MFRC522 will physically block the lower frequency.
Why does my MFRC522 read the card once and then stop responding?
This happens when the microcontroller fails to send the "Halt" command to the tag. When a Mifare card is read, it enters a "Ready" or "Active" state. If you do not call rfid.PICC_HaltA() and rfid.PCD_StopCrypto1() at the end of your code block, the card stays active. The reader will see the card is present, but the card will refuse to send its UID again until it is physically removed from the magnetic field and loses power.
Can I clone an encrypted access card with this Arduino RFID reader?
You can read the UID (the public serial number) of almost any card, and you can read/write to the data sectors of a blank Mifare Classic 1K card if you know the default sector keys (usually 0xFF x6). However, cloning a commercial access card that uses encrypted sectors or proprietary keys (like HID iClass or Mifare Desfire EV3) is impossible with the MFRC522. The MFRC522 does not support the cryptographic sniffing or brute-forcing required to extract those keys; for that, security researchers use specialized hardware like the Proxmark3.






