Community project

RFID Smart Door Access

ESP32
Photo of RFID Smart Door Access
Generated with AI

knealghey

Last updated August 11, 2026

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

Wiring diagram for RFID Smart Door Access

Gather all the parts

QtyComponent
1

MFRC522 RFID Module

RC522 13.56 MHz

13.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.

1

LCD 16x2 I2C

16x2 I2C, address 0x27

16x2 character LCD display with I2C backpack

1

SG90 Servo

SG90 5 V

Micro servo motor (SG90)

1

Buzzer

Active 5 V buzzer module

Piezo buzzer for sound output

1

USB-C 5V Adapter

5 V 2 A minimum

USB-C wall adapter delivering regulated 5 V to the board's USB or VBUS rail. Default wired power source for desktop / stationary projects.

Assemble it in 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.

  • 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.
  • 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.

  • The RC522 SDA pin is its SPI select pin; it is not the I2C SDA line.
  • 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.

  • If the display stays blank, carefully adjust its small contrast potentiometer.
  • 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.

  • 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.
  • 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.

  • Test repeatedly with the door open before fitting it to a door.
  • 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.

Review all connections

1. Connections between "power_adapter" and "ESP32"

Functionpower_adapterESP32
power+5VVIN
groundGNDGND

2. Connections between "rfid_reader" and "ESP32"

Functionrfid_readerESP32
powerVCC3V3
groundGNDGND
spiSCKGPIO 18
spiMOSIGPIO 23
spiMISOGPIO 19
digitalRSTGPIO 26
spiSDAGPIO 4

3. Connections between "lcd" and "ESP32"

FunctionlcdESP32
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22
powerVCC3V3

4. Connections between "door_servo" and "ESP32"

Functiondoor_servoESP32
powerVCCVIN
groundGNDGND
pwmSIGNALGPIO 25

5. Connections between "buzzer" and "ESP32"

FunctionbuzzerESP32
groundGNDGND
digitalSIGNALGPIO 33

Deploy the firmware

#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();
}

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