To read a 13.56 MHz RFID tag with an Arduino, use the MFRC522 module over the SPI bus. SPI provides the fastest and most reliable data transfer for this specific NXP chip, avoiding the timing glitches common in I2C clones. For a standard access control or attendance build, pair an Arduino Uno R3 (ATmega328P) with the MFRC522 via a 4-channel logic level shifter to protect the 3.3V silicon from the Uno's 5V logic.
Hardware Selection & Decision Path
The MFRC522 IC supports SPI, I2C, and UART, but the cheap blue breakout boards found on Amazon and AliExpress almost universally hardwire the chip for SPI. Furthermore, while the ESP32 is excellent for WiFi-enabled RFID logs, its 3.3V native logic and complex pin routing can introduce steep learning curves for beginners. Use the decision matrix below to lock in your hardware.
| Criteria | Option A | Option B | Verdict |
|---|---|---|---|
| Bus Interface | SPI (Fast, standard on breakouts) | I2C / UART (Requires hardware mod/soldering) | SPI |
| Microcontroller | Arduino Uno R3 (5V logic, simple routing) | ESP32 DevKit (3.3V logic, WiFi built-in) | Uno R3 (for local/offline builds) |
| Logic Shifting | Direct wire (Risks silicon degradation) | BSS138 Level Shifter (Safe, reliable) | BSS138 Shifter |
Parts List & SPI Pin Mapping
Before wiring, verify your tags. The MFRC522 operates strictly at 13.56 MHz. It will not read 125 kHz EM4100 fobs or HID Prox cards. You need Mifare Classic 1K, Mifare Ultralight, or NTAG215 tags.
| Component | Exact Variant / Part Number | Notes |
|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | Target board for the provided code. |
| RFID Module | MFRC522 Breakout (v1.0 or v2.0 clone) | Includes 13.56 MHz coil and Mifare 1K tag. |
| Logic Level Shifter | BSS138 4-Channel Bidirectional | Steps 5V Uno SPI down to 3.3V RC522 SPI. |
| Wiring | 22 AWG solid core or male-to-female jumpers | Keep SPI traces under 10cm to prevent signal reflection. |
Pin Mapping (Uno → Level Shifter → MFRC522)
| MFRC522 Pin | Level Shifter (LV / HV) | Arduino Uno R3 Pin | Function |
|---|---|---|---|
| VCC (3.3V) | LV (3.3V out) | 3.3V Pin | Power (Max 150mA) |
| GND | GND | GND | Common Ground |
| RST | LV1 / HV1 | D9 | Reset / Power Down |
| SDA (SS) | LV2 / HV2 | D10 | SPI Slave Select |
| MOSI | LV3 / HV3 | D11 | SPI Master Out Slave In |
| MISO | LV4 / HV4 | D12 | SPI Master In Slave Out |
| SCK | LV5 / HV5 | D13 | SPI Serial Clock |
Step-by-Step Wiring Procedure
- De-energize the circuit: Unplug the Arduino Uno from USB or external power. Never wire SPI buses while the microcontroller is powered; hot-plugging can latch the SPI peripheral in a fault state.
- Power the level shifter: Connect the Uno 3.3V pin to the shifter's LV (Low Voltage) rail, and the Uno 5V pin to the HV (High Voltage) rail. Tie both GND rails to the Uno GND.
- Wire the HV side to the Uno: Connect Uno D9, D10, D11, D12, and D13 to the HV1 through HV5 pins on the shifter.
- Wire the LV side to the MFRC522: Connect the shifter LV pins to the corresponding MFRC522 pins (RST, SDA, MOSI, MISO, SCK). Connect MFRC522 VCC directly to the Uno 3.3V or the LV rail.
- Verify Continuity: Use a multimeter in continuity mode. Probe from the MFRC522 SCK pin to the Uno D13 pin. You should read a clean beep through the level shifter. Ensure no adjacent pins are bridged.
Complete Arduino Code (MFRC522 Library)
This code targets the Arduino Uno R3 and relies on the widely used miguelbalboa/MFRC522 library (install via Arduino Library Manager). It includes robust error handling to prevent the serial monitor from flooding with garbage data when a tag is removed prematurely.
#include
#include
// Pin Definitions for Arduino Uno R3
#define RST_PIN 9 // Configurable, see typical pin layout above
#define SS_PIN 10 // Configurable, see typical pin layout above
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
void setup() {
Serial.begin(9600);
while (!Serial); // Wait for serial port to connect (needed for native USB)
SPI.begin(); // Init SPI bus
mfrc522.PCD_Init(); // Init MFRC522
// Verify the MFRC522 is responding and read its firmware version
byte version = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
if (version == 0x00 || version == 0xFF) {
Serial.println(F("CRITICAL: MFRC522 not detected. Check SPI wiring and 3.3V power."));
while (1); // Halt execution to prevent infinite error loops
}
Serial.println(F("MFRC522 Online. Scan a 13.56 MHz Mifare tag..."));
mfrc522.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader
}
void loop() {
// Reset the loop if no new card present on the sensor/reader.
if (!mfrc522.PICC_IsNewCardPresent()) {
return;
}
// Select one of the cards
if (!mfrc522.PICC_ReadCardSerial()) {
return;
}
// Dump debug info about the card; PICC_HaltA() is automatically called
Serial.print(F("Card UID: "));
for (byte i = 0; i < mfrc522.uid.size; i++) {
if (mfrc522.uid.uidByte[i] < 0x10) Serial.print(" 0");
else Serial.print(" ");
Serial.print(mfrc522.uid.uidByte[i], HEX);
}
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType));
// Halt the card to prevent duplicate reads of the same tag in one pass
mfrc522.PICC_HaltA();
// Small debounce delay to prevent serial buffer flooding
delay(500);
}
Debugging: Fixing Read Failures and Timeouts
When working with the NXP MFRC522 silicon, you will inevitably hit communication errors. If your serial monitor outputs "Firmware Version: 0x00" or the library throws a "PCD_ReadRegister: Communication failure", follow this ranked troubleshooting path.
The First 3 Things to Check
- Logic Level Voltage: Did you wire the Uno 5V pins directly to the MFRC522 MOSI/SCK? If yes, you have likely degraded the internal ESD protection diodes on the MISO line. Swap the module and use a level shifter.
- SS (SDA) Pin Routing: The MFRC522 requires the Slave Select pin to be pulled LOW to listen. If D10 is floating or wired to the wrong pin (like D4, which is common in copy-paste tutorials), the chip will ignore all SPI clock pulses.
- Tag Frequency Mismatch: Hold a magnet to your key fob. If it's a thick, heavy fob, it's likely 125 kHz (LF). The RC522 only reads 13.56 MHz (HF) ISO 14443A tags. Buy a pack of Mifare Classic 1K stickers to verify the reader.
Ranked Causes for "Firmware Version: 0x00"
| Rank | Cause | Measurement / Fix |
|---|---|---|
| 1 | MISO line disconnected or fried | Measure continuity from RC522 MISO to Uno D12. If open, rewire. If shorted to ground, replace module. |
| 2 | Insufficient 3.3V Current | The Uno's onboard 3.3V regulator maxes at 150mA. If sharing power with an LCD, use a dedicated AMS1117-3.3 buck module. |
| 3 | SPI Clock Speed Too High | Add SPI.setClockDivider(SPI_CLOCK_DIV4); in setup() to drop the clock to 4MHz, accommodating long jumper wires. |
Extending and Simplifying the Build
Once the baseline read is stable, you can adapt the code for specific production or hobby use cases.
Simplifying for Attendance / UID Logging
If you only need to log who walked through a door, strip out the PCD_DumpVersionToSerial() and sector authentication blocks. The UID (Unique Identifier) is broadcast in the clear during the anti-collision phase. Simply read the 4-byte or 7-byte UID array, convert it to a single unsigned long integer, and compare it against a whitelist array. This reduces loop execution time to under 2 milliseconds.
Extending for Access Control (Relay Trigger)
To trigger a magnetic door lock, add a 5V relay module. Never power a relay coil directly from an Arduino I/O pin; the back-EMF will destroy the ATmega328P. Use a 2N2222 NPN transistor or an optocoupler (like the PC817 found on most relay boards) to isolate the 12V lock power from the Uno's 5V logic. Add this logic to the loop:
unsigned long scannedUID = (mfrc522.uid.uidByte[0] << 24) |
(mfrc522.uid.uidByte[1] << 16) |
(mfrc522.uid.uidByte[2] << 8) |
(mfrc522.uid.uidByte[3]);
if (scannedUID == 0x1A2B3C4D) { // Replace with your authorized UID
digitalWrite(RELAY_PIN, HIGH); // Trigger optocoupler
delay(2000); // Hold lock open for 2 seconds
digitalWrite(RELAY_PIN, LOW);
}
By isolating the SPI bus with a level shifter and strictly validating the 13.56 MHz tag frequency, your MFRC522 build will transition from a frustrating breadboard experiment into a reliable, deployment-ready access node.






