💳 ESP32 RFID RC522 门禁系统:刷卡开门与 IC 卡读写实战

2026-05-24 · 硬件编程 · 阅读时间 12 分钟

📺 B站推荐视频:Arduino超快速入门3 — RC522门禁系统超简单制作!| 天赐航模 · 25分钟从零搭建刷卡门禁

📖 概述:什么是 RFID RC522?

RFID(Radio Frequency Identification,射频识别)是一种非接触式自动识别技术。RC522 模块基于 NXP 的 MFRC522 芯片,工作在 13.56 MHz 高频频段,通过 SPI 接口与 ESP32 通信,支持读取和写入 Mifare S50(1KB)/ S70(4KB) 类型的 IC 卡。

每张 Mifare 卡都有一个全球唯一的 4 字节 UID(唯一标识符)——这就是门禁系统识别"谁在刷卡"的核心依据。RC522 的典型读写距离为 3~6 cm,非常适合门禁考勤、会员卡识别、物品追踪等场景。

为什么选 ESP32 而不是 Arduino Uno?RC522 是 3.3V 器件,Arduino Uno 的 5V 逻辑电平需要加电平转换电路才能安全连接,而 ESP32 原生 3.3V——直接杜邦线对接即可,零外围电路。

💡 前置知识:本教程假设你已熟悉 ESP32 的 Arduino 开发环境和 SPI 基础概念。如果还没配置,请参考往期「GPIO 点亮 LED」和「I2C 连接传感器」教程了解引脚操作与总线通信基础。
🔗 联动课程:上节课我们学了 LoRa 远距离通信——今天学完 RC522 后,你可以实现「刷卡→ESP32 验证→LoRa 远程开锁→继电器动作」的分布式门禁系统!后续结合 OLED 屏幕显示刷卡者姓名、结合 Web Server 实现远程授权,玩法极其丰富。

🛒 物料清单

物料型号/参数数量参考价格
ESP32 开发板ESP32-WROOM-32(CH340 版)1¥18~25
RFID 读写模块RC522(MFRC522 芯片),13.56 MHz1¥5~10
IC 卡 + 钥匙扣Mifare Classic 1K(S50),通常随模块附赠2~3¥1~3/张
有源蜂鸣器3.3V~5V,低电平触发1¥1~2
LED 指示灯红色 5mm + 绿色 5mm + 220Ω 电阻 ×2各1¥1
继电器模块1 路 5V 光耦隔离,高/低电平触发可选1¥4~7
杜邦线母对母 + 公对母,20 cm各12条¥3/组
面包板830 孔1¥5~8
电磁锁(可选)12V 推拉式电磁铁/电插锁1¥12~25

总预算:基础版(不含电磁锁)约 ¥40~60,完整门禁版约 ¥60~90。

🔌 接线图

┌──────────────────────────────────────────────────────────┐
│                ESP32 ←→ RC522 SPI 引脚连接                  │
├────────────┬─────────────────────────────────────────────┤
│  ESP32     │  RC522 模块          说明                     │
├────────────┼─────────────────────────────────────────────┤
│  3.3V      │  VCC (3.3V)         供电(切勿接5V!)     │
│  GND       │  GND                共地                     │
│  GPIO18    │  SCK                SPI 时钟线                │
│  GPIO19    │  MISO               SPI 主入从出              │
│  GPIO23    │  MOSI               SPI 主出从入              │
│  GPIO5     │  SDA (SS/NSS)       SPI 片选(自定义引脚)     │
│  GPIO22    │  RST                复位引脚(自定义引脚)     │
├────────────┴─────────────────────────────────────────────┤
│                外设引脚连接(门禁功能)                       │
├────────────┬─────────────────────────────────────────────┤
│  GPIO15    │  红色 LED(+220Ω 限流电阻→GND) 验证失败指示    │
│  GPIO4     │  绿色 LED(+220Ω 限流电阻→GND) 验证通过指示    │
│  GPIO2     │  蜂鸣器 I/O          刷卡提示音                │
│  GPIO13    │  继电器 IN           控制电磁锁/电插锁          │
├────────────┴─────────────────────────────────────────────┤
│  注意:RC522 的 SDA 引脚是 SPI 片选(Slave Select),    │
│  不是 I2C 的 SDA!不同卖家丝印可能标为 SS / NSS / CS。       │
│  RC522 模块工作电压 3.3V,电流约 13~26mA(待机<1mA)。      │
└──────────────────────────────────────────────────────────┘
🔧 接线验证:上电后 RC522 模块上的电源指示灯应常亮。手指靠近天线线圈区域时刷卡,模块通信指示灯会闪烁。如果灯不亮,检查 3.3V 和 GND 是否接反。
SPI 引脚选择:ESP32 有两组硬件 SPI(VSPI 和 HSPI)。本教程使用 VSPI 默认引脚(SCK=18, MISO=19, MOSI=23),性能最佳。CS 和 RST 可用任意 GPIO。

💻 基础代码:读 UID + 白名单门禁

核心逻辑:刷卡 → 读取卡片 UID → 与白名单比对 → 通过则亮绿灯 + 开锁 + 蜂鸣器响一声,拒绝则亮红灯 + 蜂鸣器响三声。

// ========== RC522 白名单门禁系统 ==========
#include <SPI.h>
#include <MFRC522.h>

// ---------- 引脚定义 ----------
#define SS_PIN     5    // SDA / SS
#define RST_PIN    22   // RST
#define LED_GREEN  4    // 验证通过
#define LED_RED    15   // 验证失败
#define BUZZER     2    // 蜂鸣器
#define RELAY      13   // 继电器(控制门锁)

MFRC522 mfrc522(SS_PIN, RST_PIN);

// ---------- 白名单 UID(授权卡片) ----------
// 用串口监视器刷一次卡即可看到 UID,将其加入数组
byte whitelist[][4] = {
  {0xA3, 0x7B, 0x2C, 0x15},  // 示例:卡1(替换为你自己的UID)
  {0xD2, 0xE4, 0x8F, 0x0B},  // 示例:卡2
};
const int whitelistSize = sizeof(whitelist) / sizeof(whitelist[0]);

// ---------- 初始化 ----------
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("\n🔐 RC522 门禁系统启动中...");

  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(LED_GREEN, LOW);
  digitalWrite(LED_RED, LOW);
  digitalWrite(BUZZER, LOW);
  digitalWrite(RELAY, LOW);  // 默认锁闭

  SPI.begin();                     // 初始化 SPI 总线
  mfrc522.PCD_Init();              // 初始化 RC522
  mfrc522.PCD_DumpVersionToSerial(); // 打印固件版本

  Serial.println("✅ 系统就绪,请刷卡...");
  Serial.printf("📋 白名单共 %d 张卡\n", whitelistSize);
}

void loop() {
  // 检测是否有新卡片
  if (!mfrc522.PICC_IsNewCardPresent()) return;
  if (!mfrc522.PICC_ReadCardSerial())   return;

  // 读取 UID 并打印
  Serial.print("💳 检测到卡片 UID: ");
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.printf("%02X ", mfrc522.uid.uidByte[i]);
  }
  Serial.println();

  // 白名单验证
  bool authorized = checkWhitelist(mfrc522.uid.uidByte, mfrc522.uid.size);

  if (authorized) {
    accessGranted();
  } else {
    accessDenied();
  }

  // 停止与卡片的通信(释放天线,准备下一次刷卡)
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}

// ---------- 白名单查询 ----------
bool checkWhitelist(byte* uid, byte uidLen) {
  for (int i = 0; i < whitelistSize; i++) {
    bool match = true;
    for (byte j = 0; j < uidLen; j++) {
      if (whitelist[i][j] != uid[j]) { match = false; break; }
    }
    if (match) return true;
  }
  return false;
}

// ---------- 验证通过:开锁 ----------
void accessGranted() {
  Serial.println("✅ 验证通过!开门");
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(RELAY, HIGH);   // 继电器吸合,电锁通电
  tone(BUZZER, 2000, 150);      // 短促哔声
  delay(3000);                   // 开门保持 3 秒
  digitalWrite(RELAY, LOW);    // 继电器断开,重新锁门
  digitalWrite(LED_GREEN, LOW);
}

// ---------- 验证失败:报警 ----------
void accessDenied() {
  Serial.println("❌ 未授权卡片!拒绝访问");
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_RED, HIGH);
    tone(BUZZER, 800, 200);
    delay(300);
    digitalWrite(LED_RED, LOW);
    delay(200);
  }
}
🔑 如何获取你的卡片 UID?
第一次烧录代码后,打开串口监视器(115200 波特率),刷一张卡——串口会打印出类似 💳 检测到卡片 UID: A3 7B 2C 15 的信息。将这四个十六进制字节替换到白名单数组中即可。
继电器触发模式:不同继电器模块有高低电平触发之分。本代码默认 HIGH 触发(RELAY=HIGH 时锁通电)。如果你的模块是低电平触发,将 RELAY 的 HIGH/LOW 对调即可。

🚀 进阶应用

1. 读写 IC 卡数据块(Block Read/Write)

Mifare 1K 卡有 16 个扇区(Sector 0~15),每扇区 4 个数据块(Block 0~3),每块 16 字节。扇区 0 的 Block 0 存储 UID 和厂商数据(只读),其余扇区默认需要密钥认证才能读写。

下面的代码演示如何在扇区 1 写入用户数据(比如姓名、工号、余额),认证密钥为出厂默认值 FF FF FF FF FF FF

// ===== 向扇区 1 / Block 4 写入数据 =====
byte sector    = 1;
byte blockAddr = 4;  // 扇区1的第一个块 = 块地址4
byte dataBlock[16] = {
  'A','I','K','n','o','w','-','U',  // "AIKnow-U"
  's','e','r','0','0','1',0x00,0x00
};

// 默认密钥
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;

// 认证扇区 1
byte trailerBlock = 7;  // 扇区1的最后一块(块7)是控制块
MFRC522::StatusCode status;
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A,
                                  trailerBlock, &key, &(mfrc522.uid));
if (status != MFRC522::STATUS_OK) {
  Serial.printf("❌ 认证失败: %d\n", status);
  return;
}

// 写入数据块
status = mfrc522.MIFARE_Write(blockAddr, dataBlock, 16);
if (status == MFRC522::STATUS_OK) {
  Serial.println("✅ 数据写入成功!");
}

// 读回验证
byte buffer[18];
byte size = sizeof(buffer);
status = mfrc522.MIFARE_Read(blockAddr, buffer, &size);
if (status == MFRC522::STATUS_OK) {
  Serial.print("📖 读出数据: ");
  for (byte i = 0; i < 16; i++) {
    Serial.printf("%c", buffer[i]);
  }
  Serial.println();
}

2. 多卡类型兼容:自动识别 UID 长度

Mifare 卡有 4 字节 UID(经典 S50)7 字节 UID(S70 / Ultralight)两种。rc522.uid.size 字段自动提供 UID 长度,兼容代码如下:

void printUID(MFRC522 &rc522) {
  Serial.print("UID (");
  Serial.print(rc522.uid.size);
  Serial.print(" bytes): ");
  for (byte i = 0; i < rc522.uid.size; i++) {
    if (rc522.uid.uidByte[i] < 0x10) Serial.print("0");
    Serial.print(rc522.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
  Serial.printf("卡片类型: %s\n", rc522.PICC_GetTypeName(rc522.PICC_Type));
}

3. 修改扇区密钥:从 FF FF FF 到自定义密钥

出厂默认密钥全 FF 极不安全——任何人拿到你的卡都能读写数据。修改扇区尾块(Trailer Block)即可设置自定义密钥:

// 将扇区1的密钥A改为自定义值
byte newKeyA[6] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};

// 先读取扇区1的尾块(块7),保留访问位
byte trailer[18];
byte size = sizeof(trailer);
mfrc522.MIFARE_Read(7, trailer, &size);

// 替换密钥A(前6字节),保留访问位(6-9字节)和密钥B(10-15字节)
memcpy(trailer, newKeyA, 6);

// 写回尾块
mfrc522.MIFARE_Write(7, trailer, 16);
Serial.println("🔑 密钥已更新!");
⚠️ 修改密钥的"自杀"风险:一旦修改尾块密钥并忘记保存,该扇区将永久无法再读写(除非你知道新密钥)。强烈建议在生产环境中把密钥记录在安全位置,或者先用测试卡做实验。

🏭 实战场景

场景一:智能快递柜(联动舵机 SG90)

场景描述:快递员刷卡→系统根据快递编号匹配→自动弹开指定柜门。用户在手机上获取取件码,刷自己的 IC 卡也能打开对应柜门。适合社区或办公室的共享快递柜。

物料组合:ESP32 + RC522 ×1 + SG90 舵机 ×3(模拟三格柜门)+ OLED 显示屏 ×1

工作流程:刷卡 → 读取 UID → 查询 UID-柜号映射表 → OLED 显示"3号柜已开" → 对应舵机旋转 90° 弹开柜门 → 5 秒后自动回位。

联动已学模块:结合 5月15日的 SG90 舵机教程和 5月14日的 OLED 显示屏教程,一个完整的智能柜系统只需 60 行代码!

场景二:实验室设备授权使用记录(联动 BLE + 手机 App)

场景描述:大学实验室的贵重仪器(如示波器、信号发生器)需要刷卡才能通电使用。刷卡记录(谁、什么时间、用了什么设备)通过 BLE 实时同步到管理员的手机 App。

物料组合:ESP32 + RC522 + 继电器模块 + 实时时钟 DS3231 + BLE(ESP32 内置)

数据流:刷卡 → RC522 读取 UID → ESP32 记录时间戳 → 继电器通电(设备可用) → BLE 广播使用日志 → 手机 App 接收并上传后台。

联动已学模块:结合 5月11日的 BLE 配对通信教程,手机可实时收到"张三 14:32 使用示波器"的推送。

⚠️ 常见问题与踩坑指南

❌ 问题1:刷卡完全没有反应,串口无输出
原因排查(按顺序):
SPI 引脚接错——这是最高发问题。确认 SCK→18, MISO→19, MOSI→23, SDA→5, RST→22(对照你的实际接线);
供电不足——RC522 必须接 3.3V。如果接了 5V,模块可能已烧毁。用万用表测 VCC 引脚电压;
SPI 初始化遗漏——必须在 mfrc522.PCD_Init() 之前调用 SPI.begin()
卡片类型不兼容——RC522 只支持 13.56 MHz 的 Mifare 卡。手机 NFC 模拟的卡片通常需要用特殊模式读取。
❌ 问题2:能读到 UID,但写数据块失败(AUTH ERROR)
原因:卡片的扇区密钥不是默认的 FF FF FF FF FF FF。很多出厂卡片已经被修改过密钥,或者某些扇区使用了不同的密钥。
解决:① 确认卡是全新的 Mifare Classic 1K(S50),出厂密钥为全 FF;② 如果卡已经被写过,需要用已知密钥认证;③ 尝试使用密钥 B(PICC_CMD_MF_AUTH_KEY_B)代替密钥 A;④ 扇区 0 的 Block 0 是只读的(存 UID),不可写。
❌ 问题3:卡片靠近时偶尔能读偶尔不能读,不稳定
原因:① 天线线圈下方有金属物体(面包板底座金属片、电池等)干扰射频场;② 卡片与模块距离不稳定;③ 模块焊接不良(尤其是一些廉价 RC522 模块的排针虚焊)。
解决:将 RC522 模块悬空或用尼龙柱架高,远离金属平面。如果还不行,在代码中加入重试逻辑——连续检测失败 3 次再报错,避免偶发性漏读。
❌ 问题4:MFRC522 库编译报错 "multiple definition" 或 "xxx not declared"
解决:使用 Arduino Library Manager 搜索安装 "MFRC522" by GithubCommunity(Miguel Balboa 维护的版本)。不要手动从 GitHub 下载 zip 安装(容易缺少依赖)。如果之前装过其他 RFID 库,在 ~/Arduino/libraries/ 中清理冲突的库文件夹后重启 Arduino IDE。
ESP32 特别注意:某些旧版 MFRC522 库与 ESP32 的 SPI 实现有冲突,确保使用最新版本(≥1.4.10)。