Breadboard with ESP32-C3 SuperMini, RC522 RFID reader module and buzzer, hand tapping an RFID card near a door

Beginner

Build an RFID Access Demo

SensorsSecurityInputs

Tap a card, unlock something -- the exact same technology behind a hotel key card, built and coded by you.

6 real parts included

Total for this kit: 1,735.00 ฿

Doesn't include any order discount you may get at checkout.

What you'll need

Project Goal

This project builds a small, working access-control demo using the exact same underlying technology found in a hotel key card or an office badge reader: tap a card near a reader, and the system decides whether to grant access. Here, 'granting access' means sounding a buzzer, but the decision logic is the same core idea used in real access-control systems.

What the Controller Does

The ESP32-C3 SuperMini talks to the RC522 reader over SPI, waits for a card or keyfob to be detected, reads its unique ID, checks that ID against a list of allowed IDs stored in your code, and triggers the buzzer only if the tag is on that list.

What the RFID Reader Does

The RC522 communicates with the controller over SPI and, once a compatible RFID card or keyfob is held within a few centimeters, reads that tag's unique ID (UID) -- a fixed identifier baked into every tag, distinct from tag to tag. The reader's job ends at reporting the UID; deciding what to do with it is entirely up to your code.

What the Buzzer Does

The active buzzer is the project's only output -- it makes a sound on command from the controller, and nothing else. It has no logic of its own; it simply beeps when told to, which is exactly what you want for a clear, unambiguous 'access granted' signal.

SPI Wiring and the Reset Pin

The RC522 uses SPI, a synchronous bus with dedicated clock, data-in, and data-out lines plus a chip-select line. A common gotcha specific to the RC522: some libraries require its reset pin to be explicitly wired and defined in code rather than left floating or tied high by default -- if the reader never initializes, this is one of the first things worth checking.

Software and Library Concepts

Community libraries for the RC522 handle the SPI communication and tag detection for you, surfacing the detected UID as a simple byte array in your code. Your own logic is what turns that UID into a decision: compare it against a stored allow-list of known-good UIDs, and only trigger the buzzer on a match.

Testing and Troubleshooting

  • Reader never detects any tag at all -- check the SPI wiring carefully, especially the reset pin; many RC522 libraries require it to be explicitly defined in code, and a floating or unconnected reset pin is a very common cause of a reader that never initializes.
  • Only one specific tag works and every other tag is rejected -- confirm your code is comparing the tag's complete UID byte-for-byte, not a truncated or partial slice of it; a partial comparison can accidentally match or reject tags inconsistently.
  • The buzzer triggers on every single tap, regardless of which tag is used -- this points to a bug in the UID comparison logic itself (for example, an allow-list check that isn't actually being enforced), not a hardware problem.
  • Detection is inconsistent even with a known-good tag -- try holding the tag closer and more centered over the reader; RC522 read range is only a few centimeters and tag orientation can affect reliable detection.

Optional Future Upgrade: Multi-User Access Log

Once single-tag access control is working reliably, a natural extension is mapping each known UID to a name or role and logging each tap (which tag, granted or denied, and when) instead of only sounding a buzzer -- turning the demo into something closer to a real access log.

Wiring

  • RC522 3.3V → ESP32-C3 SuperMini 3.3V — RC522 is 3.3V logic ONLY, never power it from 5V
  • RC522 GND → GND
  • RC522 SCK → GPIO4 (`SPI_SCK_PIN`)
  • RC522 MISO → GPIO5 (`SPI_MISO_PIN`)
  • RC522 MOSI → GPIO6 (`SPI_MOSI_PIN`)
  • RC522 SDA/SS → GPIO7 (`RFID_SS_PIN`)
  • RC522 RST → GPIO10 (`RFID_RST_PIN`)
  • Active Buzzer signal/+ → GPIO2 (`BUZZER_PIN`)
  • Active Buzzer GND → GND

Double-check the RC522 is on 3.3V before powering on — 5V will damage it. On first upload, scan your card and keyfob and watch Serial Monitor: the sketch prints every scanned UID, so copy the real hex bytes it shows into `authorizedUIDs[]` (the placeholder UID in the code is a dummy and will never match a real tag). Granted access gives one longer beep; denied gives three short beeps.

Wiring Diagram

3.3V GND SCK → GPIO4 MISO → GPIO5 MOSI → GPIO6 SDA/SS → GPIO7 RST → GPIO10 signal/+ → GPIO2 GND ESP32-C3 SuperMini RC522 Active Buzzer GND

Starter Code

#include <SPI.h>
#include <MFRC522.h>

#define SPI_SCK_PIN  4   // ESP32-C3 default hardware SPI pins - adjust to match your board's silkscreen if different
#define SPI_MISO_PIN 5
#define SPI_MOSI_PIN 6
#define RFID_SS_PIN  7   // RC522 SDA/SS - any free digital GPIO
#define RFID_RST_PIN 10  // RC522 RST - any free digital GPIO
#define BUZZER_PIN   2   // Active buzzer signal - any digital output GPIO

MFRC522 mfrc522(RFID_SS_PIN, RFID_RST_PIN);

// Add the real UIDs of your own cards/keyfobs here after reading them from
// Serial on first boot (see the scanned UID printout). Each UID is the byte
// sequence MFRC522 reports for that tag.
byte authorizedUIDs[][4] = {
  {0xDE, 0xAD, 0xBE, 0xEF}, // placeholder - replace with your card's real UID
};
const int NUM_AUTHORIZED = sizeof(authorizedUIDs) / sizeof(authorizedUIDs[0]);

void printUID(byte *uid, byte size) {
  for (byte i = 0; i < size; i++) {
    Serial.print(uid[i] < 0x10 ? " 0" : " ");
    Serial.print(uid[i], HEX);
  }
  Serial.println();
}

bool isAuthorized(byte *uid, byte size) {
  if (size != 4) return false;
  for (int i = 0; i < NUM_AUTHORIZED; i++) {
    bool match = true;
    for (int j = 0; j < 4; j++) {
      if (authorizedUIDs[i][j] != uid[j]) { match = false; break; }
    }
    if (match) return true;
  }
  return false;
}

void beepGranted() {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(400);
  digitalWrite(BUZZER_PIN, LOW);
}

void beepDenied() {
  for (int i = 0; i < 3; i++) {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(100);
    digitalWrite(BUZZER_PIN, LOW);
    delay(100);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  SPI.begin(SPI_SCK_PIN, SPI_MISO_PIN, SPI_MOSI_PIN, RFID_SS_PIN);
  mfrc522.PCD_Init();

  Serial.println("RFID Access Demo ready - scan a card or keyfob");
  Serial.println("Scanned UIDs print below - copy your own into authorizedUIDs[]");
}

void loop() {
  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  Serial.print("Scanned UID:");
  printUID(mfrc522.uid.uidByte, mfrc522.uid.size);

  if (isAuthorized(mfrc522.uid.uidByte, mfrc522.uid.size)) {
    Serial.println("Access granted");
    beepGranted();
  } else {
    Serial.println("Access denied");
    beepDenied();
  }

  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  delay(1000); // avoid re-triggering instantly on the same tag
}

Not sure about a step? Ask us