The RC522 reads 13.56 MHz MIFARE cards and tags — the blue cards that ship with most kits — and is the core of attendance and door-lock projects.
The ESP32 is a natural fit because both are 3.3 V: wire the four SPI lines plus RST and power, and the MFRC522 library does the rest. The sketch prints each card’s UID and type.
Wiring
| ESP32 DevKit V1 | RC522 RFID reader (MFRC522, 13.56 MHz) | Wire | Notes |
|---|---|---|---|
| 3V3 | 3.3V | red | The RC522 is 3.3 V-only — never 5 V. |
| GND | GND | black | |
| D18 | SCK | yellow | |
| D19 | MISO | blue | |
| D23 | MOSI | green | |
| D4 | SDA | purple | Chip select (SS). The label “SDA” is historical — it is not I2C. |
| D22 | RST | orange |
3.3 V only part, 3.3 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
- Both sides run at 3.3 V, so unlike on an Uno there is no level shifting at all.
- SCK/MISO/MOSI are the ESP32’s VSPI pins (18/19/23). Chip select is on GPIO4 rather than GPIO5 because GPIO5 is a boot-strapping pin.
- IRQ is not needed — the library polls for cards.
Code
// RC522 (MFRC522) RFID reader on an ESP32 DevKit V1 over SPI (VSPI).
// 3.3V → 3V3 (never 5 V), GND → GND, SCK → D18, MISO → D19, MOSI → D23, SDA(SS) → D4, RST → D22.
#include <SPI.h>
#include <MFRC522.h>
constexpr uint8_t SS_PIN = 4;
constexpr uint8_t RST_PIN = 22;
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23, SS_PIN); // SCK, MISO, MOSI, SS
rfid.PCD_Init();
byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
if (v == 0x00 || v == 0xFF) {
Serial.println("RC522 not answering — check 3V3, SPI wiring and the SS/RST pins");
} else {
Serial.printf("RC522 ready (firmware 0x%02X). Tap a card…\n", v);
}
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
Serial.print("Card UID:");
for (byte i = 0; i < rfid.uid.size; i++) Serial.printf(" %02X", rfid.uid.uidByte[i]);
Serial.printf(" (%s)\n", rfid.PICC_GetTypeName(rfid.PICC_GetType(rfid.uid.sak)));
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}// RC522 (MFRC522) RFID reader on an ESP32 DevKit V1 over SPI (VSPI).
// 3.3V → 3V3 (never 5 V), GND → GND, SCK → D18, MISO → D19, MOSI → D23, SDA(SS) → D4, RST → D22.
#include <SPI.h>
#include <MFRC522.h>
constexpr uint8_t SS_PIN = 4;
constexpr uint8_t RST_PIN = 22;
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23, SS_PIN); // SCK, MISO, MOSI, SS
rfid.PCD_Init();
byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
if (v == 0x00 || v == 0xFF) {
Serial.println("RC522 not answering — check 3V3, SPI wiring and the SS/RST pins");
} else {
Serial.printf("RC522 ready (firmware 0x%02X). Tap a card…\n", v);
}
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
Serial.print("Card UID:");
for (byte i = 0; i < rfid.uid.size; i++) Serial.printf(" %02X", rfid.uid.uidByte[i]);
Serial.printf(" (%s)\n", rfid.PICC_GetTypeName(rfid.PICC_GetType(rfid.uid.sak)));
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}Library: MFRC522 (GithubCommunity) — compiled with MFRC522 1.4.12.
Expected serial output
RC522 ready (firmware 0x92). Tap a card…
Card UID: 4A 3F 9C 2B (MIFARE 1KB)
Card UID: 04 6E 21 A2 4F 61 80 (MIFARE Ultralight or Ultralight C)RC522 ready (firmware 0x92). Tap a card… Card UID: 4A 3F 9C 2B (MIFARE 1KB) Card UID: 04 6E 21 A2 4F 61 80 (MIFARE Ultralight or Ultralight C)
Common errors
“RC522 not answering” (firmware 0x00 or 0xFF)
SPI wiring or chip select. Check SDA is on GPIO4 and RST on GPIO22, and that the module has 3.3 V.
Cards read only when touching the antenna
Normal range is 2–4 cm. Keep metal away from the antenna and the wires short.