
很多刚开始玩ESP32的朋友第一次听到“RFID”这个词脑子里浮现的要么是公司门禁卡要么是小区停车场的抬杆总觉得这东西是“做产品的人才会碰的硬件”。我第一次把RC522模块接到ESP32上刷了一下手头的门禁卡串口监视器里立刻跳出一串十六进制编号那种“它认识这张卡了”的成就感说实话比点亮LED强太多了。这篇博文我准备从零开始把手上的RC522和ESP32配合的完整流程过一遍包含硬件接线、Arduino IDE环境搭建、基础读卡代码、外部中断实战、白名单校验和考勤对接思路适合刚上手ESP32、想做刷卡门禁或课程设计的朋友直接照着抄作业。RFID并不是什么玄学它本质上是给每个物体发一张带唯一编号的“电子身份证”。ESP32作为主控负责读卡、判断和对外通信RC522作为读卡器负责发射13.56MHz的射频场两者配合就能完成刷卡、写卡、比对、放行这一整套动作。文章里的所有代码我都实测过接线图和避坑点也是自己踩出来的尽量用大白话把原理讲明白让零基础的朋友也能把卡刷起来。1. 项目定位与原理拆解1.1 RFID到底是什么东西RFID是Radio Frequency IDentification的缩写中文叫射频识别。你可以把它理解成一套“不用接触就能识别身份”的机制和条码、二维码最大的区别是RFID卡片不需要露出来给人扫也不用精确对准只要靠近读卡器天线区域数据就能被读走。RFID卡片本身一般不带电池里面只有一个小芯片和一圈线圈。读卡器工作时会向外发射射频能量卡片靠近时线圈感应到电磁场会把这部分能量转化成电能给芯片供电然后芯片再把自身存储的数据以无线方式发回去。这个过程有点像手机的无线充电只不过卡片的耗电极低几毫瓦的能量就足够它工作了。术语上叫“无源电子标签”意思就是不用接电源、也不用装电池。RC522工作在13.56MHz这个频段属于高频RFID。这个频段的特点是读取距离近通常在3到7厘米但抗干扰能力强识别速度快适合做门禁、考勤、储物柜、电子钱包这类需要“贴一下”的场景。我们平时用的绝大多数门禁卡就是这种13.56MHz的高频卡具体常见的是M1卡型号一般叫S50或S70。读卡的时候有一个核心概念叫UID也就是卡片的唯一编号。M1卡通常有4字节或者7字节的UID出厂时写死正常情况下不允许修改。代码里读到的mfrc522.uid.uidByte就是它。很多初学者会混淆UID和卡内数据UID是身份证号出厂固定卡内数据是档案记录比如余额、工号、权限等级可以被读写和修改。1.2 为什么选择RC522模块如果你去淘宝搜ESP32 RFID会看到很多模块RC522、PN532、RDM6300、EM4095等等。我初次选型的时候也纠结过这里直接说一下我的结论。RDM6300是125kHz的低频读卡器读取距离比高频模块远一些有的能做到十几厘米但它用的是EM4100这类低频ID卡不适合模拟常见的M1门禁卡也没有扇区读写的玩法。PN532功能更全支持NFC、支持读各种类型卡甚至能模拟成一张卡但价格摆在那里驱动程序也复杂。RC522价格很亲民整套模块加两张卡也就十几块钱支持ISO14443A标准的M1卡还支持SPI、I2C、UART三种接口对学习来说性价比非常高。当然RC522也有短板它只能读ISO14443A的卡不支持ISO14443B比如身份证、部分银行卡不适配手机NFC模拟出来的某些卡更没法像PN532那样轻松做卡模拟。如果你只是做门禁、考勤、储物柜或者学RFID原理RC522完全够了。模块本身是3.3V供电直接给ESP32的3.3V供电即可不用接5V。RC522板子上通常带一个电压转换芯片看起来可以兼容5V但实际使用中3.3V更稳妥主要是避免模块板载的三极管和天线驱动电路过热的问题。1.3 这套方案能做什么ESP32加RC522的典型应用场景我个人整理下来至少有这几个方向。第一个是门禁模拟。往代码里维护一张白名单UID列表刷卡命中就驱动继电器开锁或者舵机开门没命中就亮红灯或者蜂鸣器报警。第二个是考勤打卡。刷卡记录工号和刷卡时间通过串口把数据发给电脑上的C#上位机上位机写入数据库这就是很多课程设计里常见的“C# RFID考勤系统”。第三个是储物柜管理。卡内扇区可以写余额、写柜号刷卡时校验并操作。第四个是低功耗唤醒。平时ESP32处于深度睡眠状态卡片靠近时通过RC522的IRQ引脚产生外部中断把芯片唤醒再读卡这样电池供电也能撑很久。这篇文章后面主要围绕门禁考勤这个主线来讲因为它是RFID最经典、性价比最高的应用。你在自己项目里拿这套代码改成储物柜或者设备权限管理只需要改下判断逻辑和反馈动作就行。2. 硬件准备与接线2.1 零件清单做这个项目需要的东西不多大部分玩ESP32的人手头都有。ESP32开发板一块我用的是经典ESP32 DevKitC V4其他兼容板也差不多RC522模块一个最好带两张卡和一个钥匙扣方便测试不同UID杜邦线若干母对母优先RC522的排针是公的ESP32引脚也是公的面包板一块方便整理走线LED两个红绿各一、电阻两个220Ω左右、蜂鸣器一个可选0.96寸OLED或者LCD1602可选用于显示刷卡状态这里提醒一句RC522模块的排针有些是弯针有些是直针买的时候看清楚直针更方便接面包板。买套餐是最省心的通常带两个S50白卡加一个蓝色钥匙扣。2.2 SPI接线对照表RC522和ESP32之间的通讯我推荐用SPI接口速度比UART快驱动库也最成熟。RC522的SDA引脚在SPI模式下做片选信号有些板子上印的是SS或者CS本质上一个东西。RC522引脚ESP32引脚说明SDA(SS)GPIO5SPI片选SCKGPIO18SPI时钟MOSIGPIO23主机输出从机输入MISOGPIO19主机输入从机输出IRQGPIO27中断请求可选RSTGPIO22复位输入3.3V3.3V模块电源GNDGND共地为什么用这几个引脚ESP32在Arduino环境下默认的SPIVSPI就是SCK18、MISO19、MOSI23、SS5MFRC522库初始化的也是这组引脚。初期不建议自己乱换引脚因为换引脚需要额外指定SPIClass排查问题的时候容易多一层变量。RST引脚一定要接RC522模块如果RST悬空会处于不确定状态读卡容易失败。IRQ引脚在基础读卡阶段可以不接等做到外部中断那一章再接。如果你手头的模块是六脚版本的RC522一般没有IRQ脚先别管中断用轮询方式跑通就行。2.3 接线时的几个经验我第一次接线就踩了坑这里分享几个细节。首先所有设备必须共地也就是RC522的GND和ESP32的GND连在一起否则SPI信号电平没有参考点读卡时会出现各种诡异现象。其次杜邦线尽量短RC522天线区最好远离ESP32的金属外壳和USB插头靠近大块金属物体时射频场会被吸收刷卡距离明显变短。再次供电不要一根细长杜邦线拉很远RC522工作时天线驱动电路的峰值电流不低供电线太长会导致压降读卡不稳定。如果你发现RC522天线部分发烫多半是供电电压有问题或者模块本身短路赶紧断电检查。天线线圈周围尽量不要放螺丝、铜柱这类金属件会改变天线阻抗影响读卡灵敏度。3. 开发环境搭建Arduino IDE与ESP323.1 在Arduino IDE中安装ESP32开发板包Arduino IDE是目前ESP32最友好的开发环境特别是零基础入门。先到官网下载新版Arduino IDE安装后打开在“文件-首选项-附加开发板管理器网址”里填入:https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json然后打开“工具-开发板-开发板管理器”搜索esp32找到Espressif官方出品的esp32包点击安装。安装包比较大几百MB需要等一会儿。国内网络环境下载可能很慢可以找网友分享的离线安装包或者使用国内镜像加速。但要注意务必从可靠渠道获取不要为了图快随便下不知名网站的东西。安装完成后在开发板列表里能看到ESP32 Arduino、ESP32 Dev Module等条目。选择ESP32 Dev Module烧录速度和Flash容量保持默认一般都能正常用。3.2 安装MFRC522库Arduino生态的MFRC522库有很多个版本我用的比较多的是Miguel Balboa维护的MFRC522库在库管理器里直接搜索MFRC522就能找到。另一个叫“MFRC522 by GithubCommunity”的库也常见功能差不多示例程序略有不同。初学阶段安装其中一个就够两个都装反而可能因为源文件重名导致编译报错。库安装好之后菜单栏“文件-示例-MFRC522”里能看到ReadNUID、DumpInfo等案例。ReadNUID是读UID的简洁示例DumpInfo会打印卡片的完整信息包括厂商代码、扇区数据等对调试很有帮助。3.3 编译烧录与串口监视器把ESP32插上电脑如果之前没装过驱动先去装CP2102或CH340的USB转串口驱动。Windows下如果设备管理器能识别到COM口说明驱动没问题。选择开发板后在“工具-端口”里选对应的COM口。上传前有个常见坑很多ESP32开发板不会自动进入下载模式需要按住板上的BOOT键不放点击上传看到滚动的进度条后再松开。部分新板子支持自动下载电路不用手动按键但初期不熟悉时刷写失败先试BOOT键大概率能解决。打开串口监视器波特率选115200。代码里Serial.begin(115200)两者必须一致否则看到的全是乱码。4. 基础读卡代码实现4.1 初始化RC522先把最基础的读卡代码贴出来一行一行说明白。#include SPI.h #include MFRC522.h #define SS_PIN 5 #define RST_PIN 22 MFRC522 mfrc522(SS_PIN, RST_PIN); void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); mfrc522.PCD_DumpVersionToSerial(); Serial.println(RC522 Ready); } void loop() { // 后续读卡逻辑 }SPI.begin()启动ESP32的SPI外设mfrc522.PCD_Init()初始化模块。PCD_DumpVersionToSerial()会向串口输出RC522的固件版本信息比如Firmware Version: 0x92。如果串口打印不出这行字说明接线有误或者供电异常先排查电路再谈读卡。4.2 轮询方式读取UID把下面代码放进loop就可以实现第一版的“刷卡”。void loop() { if (!mfrc522.PICC_IsNewCardPresent()) { return; } if (!mfrc522.PICC_ReadCardSerial()) { return; } Serial.print(UID: ); for (byte i 0; i mfrc522.uid.size; i) { Serial.print(mfrc522.uid.uidByte[i], HEX); Serial.print( ); } Serial.println(); mfrc522.PICC_HaltA(); }很多新手不理解PICC_IsNewCardPresent和PICC_ReadCardSerial的区别。简单来说PICC_IsNewCardPresent只是发起一次Request问“附近有没有卡片响应”如果卡在感应区它返回true但此时还没拿到UID。紧接着调用PICC_ReadCardSerial才会进行防冲突、选卡、读取序列号这一整套流程。防冲突解决的是多张卡同时靠近时怎么区分的问题现阶段不需要深究。实践刷一张卡串口会打印类似UID: 93 C2 5B 4A这样的输出。这4个字节16进制数就是卡的唯一身份标识。uid.size可能是4也可能是7对应不同容量的卡。4.3 提高刷卡灵敏度的设置如果发现卡要贴得很近才能读到可以尝试修改接收增益。MFRC522库提供了PCD_SetRegisterBitMask方法mfrc522.PCD_SetRegisterBitMask(mfrc522.RFCfgReg, mfrc522.RxGain_18dB);这行设置RF接收增益到18dB会让读卡距离略微增加。但增益不是越大越好调太高时模块更容易受环境干扰出现误读或者读错UID。实际项目里建议在稳定性和距离之间平衡不是必要就不要动它。5. 外部中断实战卡片靠近立刻唤醒5.1 为什么要引入外部中断前面那种写法叫轮询代码一直在循环里问“有没有卡”。这种方式简单但如果ESP32要做其他事情比如联网、显示动画、处理传感器数据轮询读卡就会占用大量CPU而且每隔几毫秒查一次电也耗得快。外部中断的思路完全不同。ESP32的GPIO可以有多种触发模式上升沿、下降沿、低电平。简单说卡片靠近时RC522的IRQ引脚会产生一个电平跳变ESP32检测到这个跳变立刻暂停当前任务跳进中断服务函数ISR执行完再回来。这就好比你坐在屋子里等快递轮询模式是每隔几分钟开门看一眼中断模式是装了门铃快递员按铃你才去开门平时该干嘛干嘛。如果项目用的是电池还能配合esp_sleep_enable_ext0_wakeup做深度睡眠唤醒没有卡的时候芯片睡过去一有卡靠近立刻醒过来刷卡。5.2 RC522的IRQ引脚怎么接RC522的IRQ脚默认是低电平有效。模块内部有一些中断标志位比如“接收完成”“发送完成”“空闲”这些标志位置位时IRQ引脚会被拉低。但有一点要注意RC522并不会像门铃那样“自动检测到卡就拉低”。要想让IRQ脚在收到卡片数据时产生脉冲需要在初始化时手动使能模块的通讯中断寄存器。参考代码是这样的#include SPI.h #include MFRC522.h #define SS_PIN 5 #define RST_PIN 22 #define IRQ_PIN 27 MFRC522 mfrc522(SS_PIN, RST_PIN); volatile bool cardFlag false; void IRAM_ATTR cardDetected() { cardFlag true; } void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); // 使能RC522接收中断IRQ引脚才能起作用 mfrc522.PCD_WriteRegister(mfrc522.CommIEnReg, 0x7F); mfrc522.PCD_WriteRegister(mfrc522.DivIEnReg, 0x80); pinMode(IRQ_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(IRQ_PIN), cardDetected, FALLING); Serial.println(Interrupt mode ready); } void loop() { if (cardFlag) { cardFlag false; delay(50); // 等射频场稳定 if (mfrc522.PICC_IsNewCardPresent() mfrc522.PICC_ReadCardSerial()) { Serial.print(Card UID: ); for (byte i 0; i mfrc522.uid.size; i) { Serial.print(mfrc522.uid.uidByte[i], HEX); Serial.print( ); } Serial.println(); mfrc522.PICC_HaltA(); } } }CommIEnReg是通讯中断使能寄存器0x7F基本把接收、发送、空闲这几个中断都打开了。DivIEnReg是辅助中断使能寄存器0x80使能MIrq位。这部分寄存器操作MFRC522库的示例里没有详细说明我是在一份文档里翻出来的实测可用但不同版本的库寄存器定义一致如果编译报错查一下头文件里的宏名。5.3 中断回调里的几个注意事项编写ESP32外部中断的ISR有几个硬性规矩。第一ISR里不能写delay()不能调Serial.print()更不能做SPI读写因为这些函数耗时太长还可能在中断上下文里引发未知错误。正确做法是只设置一个volatile标志位剩下的事情留给loop处理。第二ISR函数最好加上IRAM_ATTR把代码放到ESP32内部RAM里否则中断触发时代码可能不在可执行内存中。第三volatile修饰符不能漏它告诉编译器这个变量随时可能被中断修改不能优化进寄存器。还有一个经验是RC522的IRQ在“卡还在感应区”的时候不会反复触发往往是第一次进入时触发一次之后需要把卡拿开再放回去中断才会再次产生。所以产品里普遍的做法还是轮询或者定时查询外部中断更多用于轻量级的“唤醒信号”。比如低功耗项目里睡眠前关闭天线外部中断被唤醒后重新初始化模块读卡效果就很理想。6. 刷卡逻辑与门禁考勤扩展6.1 白名单校验实现只读UID没太大意义刷卡项目的核心逻辑是判断“这张卡有没有权限”。最简单的方式是维护一个白名单数组uint8_t allowedUID[][4] { {0x93, 0xC2, 0x5B, 0x4A}, {0x54, 0x3D, 0x11, 0x9C} }; bool checkUID(MFRC522::Uid *uid) { for (int i 0; i 2; i) { if (uid-size 4 memcmp(uid-uidByte, allowedUID[i], 4) 0) { return true; } } return false; }把从uid.uidByte拿到的字节和预设值比对用memcmp比较。注意卡片UID有4字节和7字节的区别校验条件里加上uid-size判断避免拿4字节去比7字节的卡永远不匹配。白名单的数量大了以后建议把数组改成循环存储或者外接EEPROM不要硬编码在代码里。刷卡后的反馈可以用LED和蜂鸣器比如绿灯亮200ms表示成功红灯亮200ms表示失败。这里要提醒一句驱动LED脚输出高电平前确认ESP32的GPIO能不能承受这个电流一般经过220Ω电阻再接LED直接接管脚问题不大但如果要驱动继电器或蜂鸣器必须加三极管或驱动芯片不要直接用GPIO去推大电流负载。6.2 使用Preferences保存白名单如果白名单不长还有一种更优雅的方案存到ESP32的NVS非易失存储里。Arduino环境封装成Preferences库用起来很简单#include Preferences.h Preferences prefs; void saveWhitelist(uint8_t data[], size_t len) { prefs.begin(rfid, false); prefs.putBytes(whitelist, data, len); prefs.end(); } void loadWhitelist(uint8_t data[], size_t len) { prefs.begin(rfid, false); prefs.getBytes(whitelist, data, len); prefs.end(); }Preferences的写入次数有限一般说NVS有十万次左右的擦写寿命。白名单这种低频操作没太大压力但如果把刷卡记录高频写进去肯定不合适。刷卡记录要发到上位机或者云端而不是存开发板。6.3 对接C#上位机实现考勤系统很多课程设计和真实项目会把ESP32当采集端把C#当上位机。ESP32通过串口把UID发出去C#用SerialPort接收并处理。为了让数据可靠传输我习惯定义一个简单的帧格式0xAA 0x55 0x04 UID[0] UID[1] UID[2] UID[3] 0x0D 0x0A帧头固定AA 55中间放4字节UID最后用回车换行结尾。C#端收到一帧后用SerialPort.DataReceived事件接收缓冲区数据再按帧解析。伪代码如下private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { SerialPort sp (SerialPort)sender; int bytesToRead sp.BytesToRead; byte[] buffer new byte[bytesToRead]; sp.Read(buffer, 0, bytesToRead); for (int i 0; i buffer.Length - 5; i) { if (buffer[i] 0xAA buffer[i 1] 0x55 buffer[i 6] 0x0A) { string uid ${buffer[i 2]:X2} {buffer[i 3]:X2} {buffer[i 4]:X2} {buffer[i 5]:X2}; // 写入数据库例如记录工号、时间 } } }注意串口数据是流式的可能一次来半帧也可能一帧带好几条数据。好的上位机需要做缓冲区拼接和断帧处理不能只在事件里简单读一次。这部分如果展开讲能单独写一篇文章这里给个方向做到课程设计级别完全够用。6.4 加一个OLED显示状态如果想让项目看起来完整加一个0.96寸OLED会有立竿见影的效果。ESP32上的OLED一般用I2C接口SDA接GPIO21SCL接GPIO22用U8g2或者Adafruit SSD1306库显示。刷到卡以后在OLED上显示“欢迎某某”或者“未授权”比只看串口直观得多。我实际做项目时OLED和RC522同时工作遇到过I2C被干扰的问题尤其是杜邦线较长时。解决办法是让I2C线尽量短并在SDA、SCL上接4.7kΩ上拉电阻。如果问题依旧考虑用软件I2C牺牲一点速度换取稳定性。7. 常见问题与排查技巧7.1 读卡失败排查真的碰到读不到卡的情况不要急着怀疑模块坏了按下面顺序排查。现象可能原因解决办法串口卡在一开始没有输出ReadyRC522供电异常、SPI接线错位检查3.3V和GND确认SCK/MOSI/MISO顺序输出Firmware Version但刷卡无反应卡类型不兼容或距离太远换一张13.56MHz的M1卡调整刷卡距离刷卡时偶尔成功偶尔失败供电不足或杜邦线接触不良换短粗线外接稳压模块降低SPI速率读到的UID不是卡上的印字收到干扰或者模块增益太高恢复默认增益远离金属物体重试注意一个容易误解的点卡面上印刷的4位数字通常是卡的“卡号”和代码读出来的UID很多时候不是一回事。卡厂会把UID转成某种规则印上去也可能是卡片厂商自定义的序列号区。不要拿印刷数字和UID直接比较除非你确认这张卡的印刷方式就是UID。7.2 编译报错和库冲突MFRC522在同名库并存时会出现重复定义编译报错一大串。解决方法是到Arduino库目录把多余版本删干净只留一个。如果官方库示例能编译自己写的代码报错重点检查有没有漏掉#include SPI.h。SPI类是MFRC522库的底层依赖不引入它编译会直接报SPIClass does not name a type。7.3 ESP32开启Wi-Fi后的坑ESP32一大特色是自带Wi-Fi和蓝牙但也正因为如此SPI通信偶尔会被Wi-Fi的硬件资源占用影响。我实测中遇到的情况是Wi-Fi连接正常时刷卡偶尔会卡顿需要贴很近才能读到。排查好久最后怀疑是射频干扰和SPI时序被高优先级任务打断。解决方案有几种读卡时临时关闭Wi-Fi代码里执行WiFi.mode(WIFI_OFF)读卡后再恢复或者给RC522的读卡逻辑加一个互斥锁避免和Wi-Fi任务同时跑再或者调整SPI时钟频率降低到1MHz左右试试。实际做门禁项目如果网络需求不强建议把Wi-Fi和读卡时序分开甚至单独用另一个MCU做读卡端再通过串口发给ESP32。7.4 别忽略卡片安全问题最后提一个很多教程不愿讲的话题。RC522配合M1卡做出来的门禁原理上是可以破解的M1卡的加密算法在安全圈早被研究透了网上甚至有现成工具能复制普通门禁卡。所以如果项目只是个人学习或者课程展示无所谓真打算做商业产品的门禁考勤至少要做到修改扇区默认密钥、不使用明文存储UID白名单、在服务器端记录刷卡日志做异常检测。更稳妥的方案是使用CPU卡或者DESFire卡但那些已经不是RC522能搞定的范畴了。理解这层风险才算是真正把RFID玩明白了。我在实际调试中最大的体会是RC522这类模块的“玄学”问题九成出在供电和接线剩下的才是代码逻辑。遇到卡刷不出来先拿万用表量电压再检查杜邦线不要上来就重写代码。另外把读卡距离控制好温柔贴近不要使劲把卡片压在天线上反而容易导致天线失谐。这个项目后续还可以扩展的方向很多把白名单放到云端、在ESP32里嵌一个Web服务器、用微信小程序管理卡片、把刷卡记录同步到Excel等等。先把基础读卡跑通后面每一步都是水到渠成。