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. |
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 afterSPI.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.
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.






