The Verdict: Which RFID Tag Reader Arduino Module to Choose
If you are building an access control system, inventory tracker, or interactive prop, you need to pick the right 13.56 MHz or 125 kHz frontend. Hobbyists often buy the wrong module for their tags or fail to account for logic-level voltage mismatches, leading to dead boards. Here is the decision framework to select your hardware.
| Module | Frequency | Protocol | Best For | Verdict |
|---|---|---|---|---|
| RC522 | 13.56 MHz | SPI / I2C / UART | Standard MIFARE 1K/4K access control, basic UID reading. | DEFAULT PICK. Best balance of cost ($2-$4) and library support. Requires 3.3V logic. |
| PN532 | 13.56 MHz | SPI / I2C / UART | NFC emulation, reading NTAG21x, writing complex NDEF records. | Choose if you need NFC smartphone interoperability or NDEF writing. Costs more ($8-$12). |
| RDM6300 | 125 kHz | UART (Serial) | Legacy EM4100 proximity cards, simple long-range ID reading. | Choose only if your existing facility uses 125 kHz key fobs. Cannot write data. |
Hardware Spec Sheet and Pin Mapping for Arduino Uno R3
This build targets the Arduino Uno R3 (ATmega328P). Because the Uno operates at 5V logic and the NXP MFRC522 chip is strictly 3.3V tolerant, we are inserting a bidirectional logic level shifter to protect the module and ensure clean SPI signal edges.
Parts List
- Microcontroller: Arduino Uno R3 (or genuine clone with ATmega328P)
- RFID Module: RC522 (13.56 MHz, SPI interface) with included MIFARE 1K tag and key fob
- Logic Level Shifter: TXS0108E 8-channel bidirectional module (Adafruit or generic)
- Wiring: 22 AWG solid core jumper wires
- Power: 5V 1A USB power supply (do not rely on weak laptop USB ports for stable 3.3V rail generation)
Pin Mapping Table
The RC522 uses the hardware SPI bus. On the Uno R3, these pins are fixed. The Chip Select (SS) and Reset (RST) pins can be changed in software, but we will use the standard defaults.
| RC522 Pin | Level Shifter (Low Side / 3.3V) | Level Shifter (High Side / 5V) | Arduino Uno R3 Pin | Function |
|---|---|---|---|---|
| VCC (3.3V) | VA (3.3V) | VB (5V) | 3.3V / 5V | Power rails for shifter reference |
| GND | GND | GND | GND | Common ground (critical for SPI) |
| RST | A1 | B1 | D9 | Reset / Power Down |
| SDA (SS) | A2 | B2 | D10 | SPI Chip Select (Slave Select) |
| MOSI | A3 | B3 | D11 | SPI Master Out Slave In |
| MISO | A4 | B4 | D12 | SPI Master In Slave Out |
| SCK | A5 | B5 | D13 | SPI Serial Clock |
Step-by-Step Wiring and Compilable Code
Follow these numbered steps to assemble the circuit. Double-check your ground connections; SPI is highly sensitive to ground loops and floating references.
- Power the Level Shifter: Connect the Uno 5V to the shifter's VB and GND. Connect the Uno 3.3V to the shifter's VA and GND. Ensure both GND pins on the shifter are tied to the Uno GND.
- Wire the SPI Lines: Connect Uno D11, D12, D13 to the high-side (B) pins of the shifter. Connect the corresponding low-side (A) pins to the RC522 MOSI, MISO, and SCK.
- Wire Control Lines: Connect Uno D10 to high-side B2, low-side A2 to RC522 SDA. Connect Uno D9 to high-side B1, low-side A1 to RC522 RST.
- Install the Library: In the Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for
MFRC522and install the library by GithubCommunity (originally by Miguel Balboa). - Upload the Code: Copy the complete, compilable code block below. It includes explicit pin definitions, hardware SPI initialization, and basic error handling for card presence and serial read failures.
#include
#include
// Hardware SPI Pin Definitions for Arduino Uno R3
#define SS_PIN 10
#define RST_PIN 9
// Create MFRC522 instance
MFRC522 mfrc522(SS_PIN, RST_PIN);
void setup() {
Serial.begin(9600);
while (!Serial); // Wait for serial port to connect (needed for native USB boards)
// Initialize SPI bus and RC522 module
SPI.begin();
mfrc522.PCD_Init();
// Verify the RC522 is responding by reading its version register
byte v = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
if (v == 0x00 || v == 0xFF) {
Serial.println(F("CRITICAL ERROR: RC522 not detected. Check wiring and 3.3V power."));
while(1); // Halt execution
}
Serial.println(F("RC522 initialized successfully."));
Serial.println(F("Scan a MIFARE Classic PICC to read UID..."));
}
void loop() {
// 1. Look for new cards
if (!mfrc522.PICC_IsNewCardPresent()) {
return; // No card in RF field, exit loop iteration
}
// 2. Select one of the cards
if (!mfrc522.PICC_ReadCardSerial()) {
Serial.println(F("Error: PICC_ReadCardSerial() failed. Tag removed too fast or collision."));
return;
}
// 3. Print the Card UID
Serial.print(F("Card UID:"));
for (byte i = 0; i < mfrc522.uid.size; i++) {
Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
Serial.print(mfrc522.uid.uidByte[i], HEX);
}
Serial.println();
// Print PICC type
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.print(F("PICC type: "));
Serial.println(mfrc522.PICC_GetTypeName(piccType));
// Halt PICC communication to allow reading a different card next time
mfrc522.PICC_HaltA();
}
Debugging: The First Three Things to Check When It Fails
When your serial monitor stays blank or throws errors, do not start rewriting code. Hardware and physical layer issues cause 95% of RC522 failures. Run through this ranked troubleshooting path.
1. The "Timeout in communication" or "CRITICAL ERROR" Loop
Exact Error String: CRITICAL ERROR: RC522 not detected. Check wiring and 3.3V power. or Timeout in communication. when attempting to read a block.
- Cause A (Most Likely): The SPI Chip Select (SS) pin is floating or wired to the wrong digital pin. The MFRC522 library defaults to pin 10 on the Uno. If you wired SDA to pin 53 (the Mega default) or left it floating, the chip ignores the SPI clock.
- Cause B: The 3.3V voltage regulator on the Arduino Uno is sagging under the RC522's transmit load (which can peak at 150mA). Measure the VA rail on your level shifter with a multimeter. If it reads below 3.1V, power the Uno from a dedicated 5V 2A wall adapter, not a PC USB port.
- Fix: Verify D10 to SDA continuity. Add a 100µF electrolytic capacitor across the 3.3V and GND rails near the RC522 VCC pin to handle RF transmission current spikes.
2. PICC_ReadCardSerial() Returns False Intermittently
Exact Error String: Error: PICC_ReadCardSerial() failed. Tag removed too fast or collision.
- Cause A (Most Likely): 5V logic bleeding into the 3.3V MISO line because you bypassed the level shifter. The Uno reads the 3.3V HIGH signal as an undefined logic state (VIH threshold is ~3.0V on a 5V ATmega), causing corrupted SPI packets.
- Cause B: You are using a 125 kHz EM4100 key fob. The RC522 operates strictly at 13.56 MHz. It physically cannot energize or read a 125 kHz coil.
- Fix: Insert the TXS0108E level shifter. Verify your tags are MIFARE Classic, Ultralight, or NTAG (13.56 MHz). Check the tag frequency printed on the packaging.
3. "Collision detected" in Serial Monitor
Exact Error String: Collision detected. (Returned by the underlying PICC_Select function).
- Cause: Two or more 13.56 MHz tags are inside the RF field simultaneously. The RC522's anti-collision loop failed to isolate a single UID because the tags are stacked directly on top of each other, detuning the antenna coils.
- Fix: Separate tags by at least 2 inches. If building a reader enclosure, ensure the RFID antenna is not mounted directly flat against a metal chassis or copper pour on a custom PCB, which detunes the resonant frequency. Use a ferrite sheet behind the coil if mounting near metal.
Extending the Build: Adding Relays and Local Storage
Reading a UID is only the first step. To build a functional access control system, you need to make decisions based on that UID and persist state without relying on a constant PC serial connection.
How to Simplify: Standalone Access Control
If you just want a door strike to unlock when a specific tag is scanned, strip out the serial printing and add a relay check. Store the authorized UID in the Arduino's flash memory using the PROGMEM macro to save SRAM.
// Store authorized UID in flash memory
const byte authorizedUID[4] PROGMEM = {0xDE, 0xAD, 0xBE, 0xEF};
#define RELAY_PIN 8
void checkAccess() {
bool match = true;
for (byte i = 0; i < 4; i++) {
if (mfrc522.uid.uidByte[i] != pgm_read_byte(&authorizedUID[i])) {
match = false;
break;
}
}
if (match) {
digitalWrite(RELAY_PIN, HIGH); // Trigger relay
delay(2000); // Hold door open for 2 seconds
digitalWrite(RELAY_PIN, LOW);
}
}
How to Extend: Networked Logging with ESP32
If you need to log entries to a database or trigger MQTT smart home routines, the Arduino Uno is the wrong tool. Migrate this exact circuit to an ESP32 DevKit V1. The ESP32 operates natively at 3.3V, allowing you to delete the logic level shifter entirely and wire the RC522 directly to the ESP32's SPI pins (GPIO 5 for SS, GPIO 2 for RST, GPIO 23 MOSI, GPIO 19 MISO, GPIO 18 SCK). This halves your part count, eliminates the primary point of hardware failure, and unlocks WiFi for MQTT publishing.
For deeper technical specifications on the MFRC522 FIFO buffers and antenna tuning matching networks, refer to the NXP MFRC522 Standard Performance Datasheet. For SPI bus timing constraints and clock divider settings on AVR boards, consult the Arduino SPI Language Reference.






