Community project

RFID Smart Door Access

ESP32
Photo of RFID Smart Door Access
Generated with AI

leang

Last updated August 11, 2026

This project builds a card-based door access system using an ESP32 microcontroller and RFID reader. When an authorized card is presented, the system unlocks an electronic latch via servo motor, displays status on an LCD screen, and provides audio feedback through a buzzer. The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions for integrating the RFID module, I2C display, servo actuator, and buzzer into a functional access control system.

The firmware stores an enrolled card UID in non-volatile memory and compares incoming scans against it, triggering unlock sequences only for authorized cards. Readers will learn how to wire low-voltage components safely, configure SPI and I2C communication on the ESP32, control a servo latch mechanism, and implement basic access logic with visual and audio feedback.

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