Community project
RFID Smart Door Access
leang
Published August 9, 2026 · Updated August 11, 2026
Generated with AIThis 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
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| MFRC522 RFID ModuleRC522 13.56 MHz | 1 | 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. |
| LCD 16x2 I2C16x2 I2C, address 0x27 | 1 | 16x2 character LCD display with I2C backpack |
| SG90 ServoSG90 5 V | 1 | Micro servo motor (SG90) |
| BuzzerActive 5 V buzzer module | 1 | Piezo buzzer for sound output |
| USB-C 5V Adapter5 V 2 A minimum | 1 | USB-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 stepsPrepare 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.
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.
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.
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.
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| Pin | Connection | Type |
|---|---|---|
| VIN | power_adapter +5V | power |
| GND | power_adapter GND | ground |
| 3V3 | rfid_reader VCC | power |
| GND | rfid_reader GND | ground |
| GPIO 18 | rfid_reader SCK | spi |
| GPIO 23 | rfid_reader MOSI | spi |
| GPIO 19 | rfid_reader MISO | spi |
| GPIO 26 | rfid_reader RST | digital |
| GND | lcd GND | ground |
| GPIO 21 | lcd SDA | i2c |
| GPIO 22 | lcd SCL | i2c |
| VIN | door_servo VCC | power |
| GND | door_servo GND | ground |
| GPIO 25 | door_servo SIGNAL | pwm |
| GND | buzzer GND | ground |
| GPIO 33 | buzzer SIGNAL | digital |
| GPIO 4 | rfid_reader SDA | spi |
| 3V3 | lcd VCC | power |
Firmware
ESP32#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.