RFID door locks and attendance systems usually start with an RC522 and a Nano. The reader speaks SPI, which on the Nano lives on D11, D12 and D13, with chip-select on D10 and reset on D9.
The part people get wrong is voltage: the RC522 runs at 3.3 V while the Nano speaks 5 V. This guide powers it from a small regulator and passes the four Nano outputs through a BSS138 level shifter. Copy your card’s UID from the Serial Monitor into ALLOWED to make it the “key”.
Wiring
| Arduino Nano | RC522 RFID reader (MFRC522, 13.56 MHz) | Wire | Notes |
|---|---|---|---|
| AMS1117 3.3 V module (IN from Nano 5V) · 3.3V | 3.3V | red | Module IN → Nano 5V, module GND → Nano GND. The same 3.3 V feeds the shifter’s LV side. |
| GND | GND | black | |
| D10 | SDA | purple | Via level shifter — SDA is the SPI chip-select (SS) on this module. |
| D13 | SCK | yellow | Via level shifter |
| D11 | MOSI | green | Via level shifter |
| D12 | MISO | blue | RC522 output at 3.3 V — the Nano reads it as HIGH directly. |
| D9 | RST | orange | Via level shifter — Shifter LV = 3.3 V, HV = Nano 5V, GND shared. |
3.3 V only part, 5 V board. Power the RC522 from 3.3 V — never 5 V. The MFRC522 chip’s pins are not rated for 5 V either: many people wire it straight to an Uno and it works for a while, but for a build that lasts, drop the Uno’s SCK, MOSI, SDA (SS) and RST signals to 3.3 V with a level shifter or dividers. ESP32 and Pico connect directly.
Board quirks
- The RC522 is a 3.3 V chip and its inputs are not 5 V tolerant. Many tutorials wire it straight to a Nano and it works — for a while. The shifter costs ₹30 and keeps it working.
- Why a regulator: the Nano’s 3V3 pin comes from the CH340/FT232 USB chip and is meant for tiny loads. The RC522’s antenna draws tens of mA in bursts; a separate 3.3 V supply removes “reads once, then nothing” problems.
- SPI is fixed on the Nano: SCK = D13, MOSI = D11, MISO = D12. SDA (chip select) and RST can move, but update the sketch.
- D13 is also the on-board LED, so the LED flickers on every card poll. The sketch frees SPI briefly to show a clean “allowed” flash.
- The MFRC522 library clocks SPI at 4 MHz, near the limit of a BSS138 shifter. Keep the shifter wires short (under 10 cm); if reads are flaky, a 74HC4050 buffer powered from 3.3 V is a faster one-way alternative.
- Leave IRQ unconnected.
Code
// RC522 (MFRC522) RFID reader on an Arduino Nano: prints each card's UID and flashes the LED
// for one "allowed" card. The RC522 is a 3.3 V part: power it from a 3.3 V regulator and pass
// SDA(SS), SCK, MOSI and RST through a level shifter. MISO comes back at 3.3 V (read fine).
// SDA → D10, SCK → D13, MOSI → D11, MISO → D12, RST → D9.
#include <SPI.h>
#include <MFRC522.h>
const uint8_t SS_PIN = 10;
const uint8_t RST_PIN = 9;
const byte ALLOWED[4] = {0xDE, 0xAD, 0xBE, 0xEF}; // replace with your card's UID
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT); // careful: D13 is also SCK, so the LED flickers during reads
SPI.begin();
rfid.PCD_Init();
byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
Serial.print(F("RC522 version: 0x"));
Serial.println(v, HEX);
if (v == 0x00 || v == 0xFF) Serial.println(F("Not answering - check 3.3V, level shifter and SPI wiring"));
else Serial.println(F("Tap a card..."));
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
Serial.print(F("Card UID:"));
for (byte i = 0; i < rfid.uid.size; i++) {
Serial.print(rfid.uid.uidByte[i] < 0x10 ? F(" 0") : F(" "));
Serial.print(rfid.uid.uidByte[i], HEX);
}
bool allowed = rfid.uid.size == 4 && memcmp(rfid.uid.uidByte, ALLOWED, 4) == 0;
Serial.println(allowed ? F(" -> allowed") : F(" -> unknown"));
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
if (allowed) {
SPI.end(); // free D13 so the LED can show the result
digitalWrite(LED_BUILTIN, HIGH);
delay(1500);
digitalWrite(LED_BUILTIN, LOW);
SPI.begin();
}
}// RC522 (MFRC522) RFID reader on an Arduino Nano: prints each card's UID and flashes the LED
// for one "allowed" card. The RC522 is a 3.3 V part: power it from a 3.3 V regulator and pass
// SDA(SS), SCK, MOSI and RST through a level shifter. MISO comes back at 3.3 V (read fine).
// SDA → D10, SCK → D13, MOSI → D11, MISO → D12, RST → D9.
#include <SPI.h>
#include <MFRC522.h>
const uint8_t SS_PIN = 10;
const uint8_t RST_PIN = 9;
const byte ALLOWED[4] = {0xDE, 0xAD, 0xBE, 0xEF}; // replace with your card's UID
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT); // careful: D13 is also SCK, so the LED flickers during reads
SPI.begin();
rfid.PCD_Init();
byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
Serial.print(F("RC522 version: 0x"));
Serial.println(v, HEX);
if (v == 0x00 || v == 0xFF) Serial.println(F("Not answering - check 3.3V, level shifter and SPI wiring"));
else Serial.println(F("Tap a card..."));
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
Serial.print(F("Card UID:"));
for (byte i = 0; i < rfid.uid.size; i++) {
Serial.print(rfid.uid.uidByte[i] < 0x10 ? F(" 0") : F(" "));
Serial.print(rfid.uid.uidByte[i], HEX);
}
bool allowed = rfid.uid.size == 4 && memcmp(rfid.uid.uidByte, ALLOWED, 4) == 0;
Serial.println(allowed ? F(" -> allowed") : F(" -> unknown"));
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
if (allowed) {
SPI.end(); // free D13 so the LED can show the result
digitalWrite(LED_BUILTIN, HIGH);
delay(1500);
digitalWrite(LED_BUILTIN, LOW);
SPI.begin();
}
}Library: MFRC522 (GithubCommunity) — compiled with MFRC522 1.4.12.
Expected serial output
RC522 version: 0x92
Tap a card...
Card UID: 7A 3C 91 2B -> unknown
Card UID: DE AD BE EF -> allowedRC522 version: 0x92 Tap a card... Card UID: 7A 3C 91 2B -> unknown Card UID: DE AD BE EF -> allowed
Common errors
Version 0x00 or 0xFF
No SPI contact: check 3.3 V, the shifter’s LV/HV supplies, and that MISO is not going through a one-way channel. Full fix.
Version reads 0x12 or 0xB2
A clone chip. It usually works; if cards are not detected, lower the antenna gain or try a different module.