Community project

RFID Smart Door Access

knealghey

Published July 31, 2026

ESP325 components5 assembly steps
Remix this project
Photo of RFID Smart Door Access

This project builds an RFID-based door access system using an ESP32 microcontroller to read NFC/RFID cards and control a servo-driven latch. The system authenticates users by comparing scanned card UIDs against an enrolled credential, then unlocks the door for a configurable duration while providing visual and audio feedback through an LCD display and buzzer.

The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions for integrating the MFRC522 RFID reader, I2C LCD display, servo motor, and buzzer. The provided firmware handles card enrollment, access control logic, and automatic re-locking, ready to upload directly to the ESP32.

Wiring diagram

Interactive · read-only
Wiring diagram for RFID Smart Door Access

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
MFRC522 RFID ModuleRC522 13.56 MHz113.56 MHz RFID reader/writer module based on the NXP MFRC522 IC. Communicates over SPI and is commonly sold as an RC522 breakout with an onboard antenna.
LCD 16x2 I2C16x2 I2C, address 0x27116x2 character LCD display with I2C backpack
SG90 ServoSG90 5 V1Micro servo motor (SG90)
BuzzerActive 5 V buzzer module1Piezo buzzer for sound output
USB-C 5V Adapter5 V 2 A minimum1USB-C wall adapter delivering regulated 5 V to the board's USB or VBUS rail. Default wired power source for desktop / stationary projects.

Assembly

5 steps
  1. Prepare the low-voltage power

    Use a regulated 5 V wall adapter rated for at least 2 A. Connect its +5 V output to the ESP32 VIN/5V pin, the SG90 red wire, and the LCD VCC pin. Connect its ground to ESP32 GND, the servo brown/black wire, LCD GND, RFID GND, and buzzer GND.

    • Tip: Keep servo power wires short and use a 470–1000 µF electrolytic capacitor across 5 V and GND close to the servo: capacitor + to 5 V and − to GND.
    • Tip: Every module must share the same ground.
    • Never connect the servo to the ESP32 3.3 V pin.
    • Do not use an unregulated supply or expose any mains wiring.
  2. Wire the RC522 RFID reader at 3.3 V

    Connect RC522 VCC to ESP32 3V3, GND to GND, SCK to GPIO18, MOSI to GPIO23, MISO to GPIO19, SDA/SS to GPIO4, and RST to GPIO26.

    • Tip: The RC522 SDA pin is its SPI select pin; it is not the I2C SDA line.
    • Tip: Keep the RC522 away from large metal parts of the door where possible.
    • RC522 VCC is 3.3 V only. Applying 5 V can damage it.
  3. Wire the I2C LCD safely

    Connect LCD VCC to ESP32 3V3, GND to GND, SDA to GPIO21, and SCL to GPIO22. This design assumes a 3.3 V-compatible I2C LCD backpack and uses address 0x27.

    • Tip: If the display stays blank, carefully adjust its small contrast potentiometer.
    • Tip: Some LCD backpacks use address 0x3F; the firmware currently uses 0x27.
    • Do not power a standard pull-up-equipped I2C LCD backpack from 5 V directly on ESP32 SDA/SCL. Its pull-ups may drive the ESP32’s 3.3 V pins to 5 V. Use 3.3 V as wired, or add a proper bidirectional I2C level shifter.
  4. Connect the latch servo and buzzer

    Connect the SG90 orange/yellow signal wire to GPIO25, red to the 5 V supply, and brown/black to common ground. Connect the active buzzer module SIGNAL to GPIO33 and its GND to common ground.

    • Tip: Before attaching the servo horn to the latch, power the project so the firmware moves it to the locked position, then fit the horn mechanically.
    • Tip: Adjust LOCK_ANGLE and UNLOCK_ANGLE in firmware later if your latch moves the wrong amount.
    • An SG90 is suitable for a model latch/light mechanism, not as the only security mechanism for a real exterior door.
    • Use a buzzer module whose input recognizes 3.3 V logic; a bare high-current buzzer requires a transistor driver.
  5. Mount and test mechanically

    Mount the RC522 where cards can reach it, then connect the servo horn to a light latch linkage. Keep the door able to open manually in an emergency. On first boot, scan the card you want to authorize; it is saved in ESP32 memory. Scan that card again to unlock for five seconds.

    • Tip: Test repeatedly with the door open before fitting it to a door.
    • Tip: The LCD shows enrollment, access-granted, denied, and locked status.
    • Do not rely on RFID UID matching alone for high-security access: many low-cost RFID tags can be cloned. Use a certified lock, mechanical override, and stronger authenticated credentials for real security.

Pin assignments

Board wiring reference
PinConnectionType
VINpower_adapter +5Vpower
GNDpower_adapter GNDground
3V3rfid_reader VCCpower
GNDrfid_reader GNDground
GPIO 18rfid_reader SCKspi
GPIO 23rfid_reader MOSIspi
GPIO 19rfid_reader MISOspi
GPIO 26rfid_reader RSTdigital
GNDlcd GNDground
GPIO 21lcd SDAi2c
GPIO 22lcd SCLi2c
VINdoor_servo VCCpower
GNDdoor_servo GNDground
GPIO 25door_servo SIGNALpwm
GNDbuzzer GNDground
GPIO 33buzzer SIGNALdigital
GPIO 4rfid_reader SDAspi
3V3lcd VCCpower

Firmware

ESP32
main.cppDeploy to device
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
#include <Preferences.h>


// Forward declarations
String uidToString(const MFRC522::Uid &uid);
void showMessage(const String &line1, const String &line2);
void beep(unsigned int durationMs);
void lockDoor(bool announce);
void unlockDoor();
void denyAccess();

constexpr uint8_t RFID_SS_PIN = 4;
constexpr uint8_t RFID_RST_PIN = 26;
constexpr uint8_t RFID_SCK_PIN = 18;
constexpr uint8_t RFID_MISO_PIN = 19;
constexpr uint8_t RFID_MOSI_PIN = 23;
constexpr uint8_t LCD_SDA_PIN = 21;
constexpr uint8_t LCD_SCL_PIN = 22;
constexpr uint8_t SERVO_PIN = 25;
constexpr uint8_t BUZZER_PIN = 33;
constexpr int LOCK_ANGLE = 10;
constexpr int UNLOCK_ANGLE = 90;
constexpr unsigned long UNLOCK_TIME_MS = 5000;
constexpr unsigned long CARD_COOLDOWN_MS = 1200;

MFRC522 rfid(RFID_SS_PIN, RFID_RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo latchServo;
Preferences preferences;
String enrolledUid;
String lastMessage;
unsigned long unlockStartedAt = 0;
unsigned long lastCardAt = 0;
bool doorUnlocked = false;

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

void showMessage(const String &line1, const String &line2) {
  String message = line1 + "\n" + line2;
  if (message == lastMessage) return;
  lastMessage = message;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(line1.substring(0, 16));
  lcd.setCursor(0, 1);
  lcd.print(line2.substring(0, 16));
}

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

void lockDoor(bool announce = true) {
  latchServo.write(LOCK_ANGLE);
  doorUnlocked = false;
  if (announce) showMessage("Door locked", "Present card");
}

void unlockDoor() {
  latchServo.write(UNLOCK_ANGLE);
  doorUnlocked = true;
  unlockStartedAt = millis();
  showMessage("Access granted", "Door unlocked");
  beep(90);
  delay(70);
  beep(90);
}

void denyAccess() {
  showMessage("Access denied", "Unknown card");
  beep(600);
}

void setup() {
  Serial.begin(115200);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  Wire.begin(LCD_SDA_PIN, LCD_SCL_PIN);
  lcd.init();
  lcd.backlight();
  showMessage("Smart Door", "Starting...");
  latchServo.setPeriodHertz(50);
  latchServo.attach(SERVO_PIN, 500, 2400);
  lockDoor(false);
  SPI.begin(RFID_SCK_PIN, RFID_MISO_PIN, RFID_MOSI_PIN, RFID_SS_PIN);
  rfid.PCD_Init();
  preferences.begin("door-access", false);
  enrolledUid = preferences.getString("adminUid", "");
  if (enrolledUid.length() == 0) {
    showMessage("Enroll first card", "Scan RFID card");
    Serial.println("Enrollment mode: scan the first card to authorize it.");
  } else {
    showMessage("Door locked", "Present card");
  }
}

void loop() {
  if (doorUnlocked && millis() - unlockStartedAt >= UNLOCK_TIME_MS) lockDoor();
  if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
  if (millis() - lastCardAt < CARD_COOLDOWN_MS) {
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }
  lastCardAt = millis();
  const String scannedUid = uidToString(rfid.uid);
  Serial.println("Scanned UID: " + scannedUid);
  if (enrolledUid.length() == 0) {
    enrolledUid = scannedUid;
    preferences.putString("adminUid", enrolledUid);
    showMessage("Card enrolled", "Scan to unlock");
    beep(120);
    delay(80);
    beep(120);
  } else if (scannedUid == enrolledUid) {
    unlockDoor();
  } else {
    denyAccess();
  }
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik