← All projects
Automation Intermediate

RC522 RFID Lock: Open a Door with a Card or Key Fob

An RC522 reader recognises 13.56 MHz cards and fobs, the ESP32 checks the UID against a list and opens a solenoid lock for 3 seconds. With audible feedback.

Difficulty
Intermediate
Time
2 h
Category
Automation

What we're building

Access control for a cabinet, garage or workshop: tap a card — the lock opens; unknown card — a long beep and a log entry. The RC522 module costs next to nothing and usually comes with a card and a key fob.

Parts

  • ESP32 DevKit
  • RC522 module (13.56 MHz, MIFARE) + cards/fobs
  • 12 V solenoid lock
  • Logic-level MOSFET module (IRLZ44N) or a relay
  • 1N4007 diode across the lock (absorbs the voltage spike)
  • 3.3–5 V active buzzer
  • 12 V power supply + a 5 V buck converter for the ESP32 (e.g. MP1584)

Wiring

The RC522 talks SPI and must be powered from 3.3 V only.

RC522 ESP32
SDA (SS) GPIO 5
SCK GPIO 18
MOSI GPIO 23
MISO GPIO 19
RST GPIO 22
3.3V / GND 3V3 / GND
IRQ not connected
Other ESP32
MOSFET SIG (lock) GPIO 26
Buzzer + GPIO 25

Lock: +12V → lock → MOSFET (DRAIN), with the 1N4007 across the lock, stripe towards +12V. The 12 V and ESP32 grounds are shared.

Library

MFRC522 (GithubCommunity / miguelbalboa) — from the Library Manager.

Code

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN     5
#define RST_PIN    22
#define LOCK_PIN   26
#define BUZZER_PIN 25

const unsigned long OPEN_MS = 3000;

// UIDs of allowed cards — add yours (the Serial Monitor prints them)
const char* ALLOWED[] = {
  "A1B2C3D4",
  "04A23B1A6F5E80",
};

MFRC522 rfid(SS_PIN, RST_PIN);

String uidToString(const MFRC522::Uid &uid) {
  String s;
  for (byte i = 0; i < uid.size; i++) {
    if (uid.uidByte[i] < 0x10) s += '0';
    s += String(uid.uidByte[i], HEX);
  }
  s.toUpperCase();
  return s;
}

bool isAllowed(const String &uid) {
  for (const char* allowed : ALLOWED) {
    if (uid == allowed) return true;
  }
  return false;
}

void beep(int ms) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(ms);
  digitalWrite(BUZZER_PIN, LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(LOCK_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(LOCK_PIN, LOW);

  SPI.begin();          // SCK 18, MISO 19, MOSI 23
  rfid.PCD_Init();
  rfid.PCD_DumpVersionToSerial();   // should report version 0x91 or 0x92
  Serial.println("Tap a card...");
}

void loop() {
  if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;

  String uid = uidToString(rfid.uid);

  if (isAllowed(uid)) {
    Serial.println("Access granted: " + uid);
    beep(80);
    digitalWrite(LOCK_PIN, HIGH);
    delay(OPEN_MS);
    digitalWrite(LOCK_PIN, LOW);
  } else {
    Serial.println("Unknown card: " + uid);
    beep(600);
  }

  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

Enrolling cards

  1. Flash the sketch as-is and open the Serial Monitor.
  2. Tap your cards — you'll see Unknown card: 04A23B1A6F5E80.
  3. Copy the UID into the ALLOWED array and flash again.

Troubleshooting

  • Firmware Version: 0x0 = (unknown) — check the SPI wires and the 3.3 V supply (5 V can kill the module).
  • The lock doesn't click — the MOSFET must be logic-level; a regular IRF540 won't turn on at 3.3 V.
  • The ESP32 resets when the lock fires — missing diode or a weak power supply.

⚠️ About security

Checking only the UID is convenience, not security. MIFARE Classic UIDs are easy to clone onto "magic" cards with a writable UID. Serious access control uses MIFARE DESFire cards with cryptographic authentication. For a tool cabinet or a kids' "secret entrance" it's perfectly fine.

Also provide a mechanical way to open the door in case of a power cut.

Ideas

  • Store the card list in NVS and enrol new cards with a "master card" — no reflashing.
  • Send an entry log to Telegram (see the "Telegram Bot" project).
  • A web page to manage the card list.