Community project
RFID Smart Door Access
leang
Published August 9, 2026 · Updated August 11, 2026
Generated with AIThis project builds an RFID-based smart door access system using an ESP32 microcontroller to read RFID cards and control a servo-driven latch mechanism. The system displays access status on an LCD screen, provides audio feedback through a buzzer, and logs all access attempts to a Google Sheet for remote monitoring and audit trails.
The guide includes a complete wiring diagram showing how to connect the MFRC522 RFID reader, I2C LCD display, SG90 servo motor, and buzzer to the ESP32, along with a full parts list, step-by-step assembly instructions, and ready-to-use firmware that handles card enrollment, access control logic, WiFi connectivity, and cloud logging.
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 <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
#include <Preferences.h>
#include <time.h>
// Forward declarations
String uidToString(const MFRC522::Uid &uid);
void showMessage(const String &line1, const String &line2);
void beep(unsigned int ms);
void startWiFi();
void maintainWiFi();
void logScanToGoogleSheet(const String &uid, const String &result);
String currentTimestamp();
void lockDoor(bool announce);
void unlockDoor();
void denyAccess();
constexpr char WIFI_SSID[] = "IPhone 5s";
constexpr char WIFI_PASSWORD[] = "1234567890";
constexpr char GOOGLE_SCRIPT_URL[] = "https://script.google.com/macros/s/AKfycbyDitjRo6nuP5iI0ySkcBCJDonuQdhfz68IBGGZKU9PeMGLiAVGJkK66xaCuCws-noP/exec";
constexpr uint8_t RFID_SS_PIN = 4, RFID_RST_PIN = 26, RFID_SCK_PIN = 18, RFID_MISO_PIN = 19, RFID_MOSI_PIN = 23;
constexpr uint8_t LCD_SDA_PIN = 21, LCD_SCL_PIN = 22, SERVO_PIN = 25, BUZZER_PIN = 33;
constexpr int LOCK_ANGLE = 10, UNLOCK_ANGLE = 90;
constexpr unsigned long UNLOCK_TIME_MS = 5000, CARD_COOLDOWN_MS = 1200, WIFI_RETRY_MS = 10000;
MFRC522 rfid(RFID_SS_PIN, RFID_RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo latchServo;
Preferences preferences;
String enrolledUid, lastMessage;
unsigned long unlockStartedAt = 0, lastCardAt = 0, lastWiFiAttemptAt = 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 ms) { digitalWrite(BUZZER_PIN, HIGH); delay(ms); digitalWrite(BUZZER_PIN, LOW); }
void startWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
lastWiFiAttemptAt = millis();
}
void maintainWiFi() {
if (WiFi.status() != WL_CONNECTED && millis() - lastWiFiAttemptAt >= WIFI_RETRY_MS) startWiFi();
}
String currentTimestamp() {
struct tm timeInfo;
if (!getLocalTime(&timeInfo, 1000)) return "";
char buffer[25];
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &timeInfo);
return String(buffer);
}
void logScanToGoogleSheet(const String &uid, const String &result) {
if (WiFi.status() != WL_CONNECTED) { Serial.println("Sheet not updated: Wi-Fi disconnected."); return; }
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
String url = String(GOOGLE_SCRIPT_URL) + "?uid=" + uid + "&result=" + result + "×tamp=" + currentTimestamp();
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setConnectTimeout(5000);
http.setTimeout(5000);
if (http.begin(client, url)) { Serial.printf("Google Sheet response: %d\n", http.GET()); http.end(); }
}
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", "");
WiFi.mode(WIFI_STA); startWiFi();
configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC time from the internet.
if (enrolledUid.length() == 0) showMessage("Enroll first card", "Scan RFID card");
else showMessage("Door locked", "Present card");
}
void loop() {
maintainWiFi();
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();
String scannedUid = uidToString(rfid.uid);
if (enrolledUid.length() == 0) {
enrolledUid = scannedUid; preferences.putString("adminUid", enrolledUid);
showMessage("Card enrolled", "Scan to unlock"); beep(120); delay(80); beep(120);
logScanToGoogleSheet(scannedUid, "Enrolled");
} else if (scannedUid == enrolledUid) { unlockDoor(); logScanToGoogleSheet(scannedUid, "Granted"); }
else { denyAccess(); logScanToGoogleSheet(scannedUid, "Denied"); }
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.