ESP32+RC522 RFID刷卡门禁实战:从原理到代码调通

📅 发布时间:2026/10/8 6:54:47
ESP32+RC522 RFID刷卡门禁实战:从原理到代码调通
ESP32搭配RFID无线射频模块是我给零基础朋友推荐得最多的第二个项目——第一个当然是点灯。刷卡开门禁、刷工卡考勤、刷校园卡签到这些看起来很唬人的功能拆开看其实就三样东西一块基于NXP MFRC522芯片的读卡模块、一张13.56MHz的非接触式IC卡、一块几十块钱的ESP32开发板。整个项目从接线到代码调通快的半天慢的也就两三天。这篇文章我把自己实际踩过的坑和解法都整理出来了适合刚学完GPIO和串口输出、想做一个真正能落地的小项目的朋友直接照着抄。1. 项目概览这个项目到底能做什么1.1 先搞清楚我们做的是个什么东西我们做出来的这个东西本质上是一个能识别IC卡的读卡器。卡片贴近模块ESP32马上就能读出这张卡唯一的ID也就是UID然后根据你的需求做三件事在串口上打印出UID、往卡片里写入业务数据、比对UID之后控制继电器或者电磁锁开门。这三个能力正好对应了门禁、考勤、发卡器三类最常见的场景。很多没接触过的人会觉得刷卡门禁里有什么高深算法其实没有。真正干活的核心代码只有不到一百行剩下的都是配件和外围电路的事。我甚至见过有人用一块ESP32-C3超小开发板加RC522模块把整套门禁电路塞进了一个86盒底盒里成本不到三十块钱效果跟市面上几百块的成品门禁没有本质区别。1.2 RFID为什么不用接触就能读到数据RFID的全称是Radio Frequency Identification射频识别。RC522工作在13.56MHz高频段通信协议是ISO/IEC 14443A对应的常见卡片就是Mifare S50/S70这种所谓的M1卡也就是咱们日常说的IC卡。它的工作原理听起来很玄其实可以拿“隔空充电”来类比。读卡器天线会持续发射一个13.56MHz的交变电磁场卡片接近时卡里的线圈感应到这个磁场会产生感应电压这个电压经过卡片芯片内部的整流电路变成工作电源直接给卡芯片供电——所以卡片自己不需要电池也不需要接触任何金属触点。供电之后卡片芯片再通过负载调制的方式把存在芯片里的UID和数据一比特一比特地回传给读卡器。这个过程用不到100毫秒所以你刷卡的时候感觉不到任何延迟。读卡距离通常在3到5厘米这也是为什么每次都要把卡贴上去刷。1.3 为什么选RC522而不是PN532做刷卡项目大部分人会在RC522和PN532之间纠结。RC522用的是NXP的MFRC522芯片PN532用的是PN532芯片两者根本不是一个定位的东西。模块核心芯片协议支持读写M1卡模拟门禁卡常见价格RC522NXP MFRC522ISO/IEC 14443A13.56MHz支持不支持3~8元PN532NXP PN532ISO/IEC 14443A/B、FeliCa支持支持25~50元如果你是做门禁、考勤这种只需要识别和读写M1卡的项目RC522是绝对够用的而且资料多、教程多、Arduino库也极其稳定新手几乎找不到比它更适合的练手模块。PN532的优势在支持卡模拟也就是以后想直接读取和模拟手机上的NFC卡片那才值得多花那几十块钱。我的建议是第一次做项目就选RC522等你理解了RFID的整套流程再需要高端玩法时换PN532代码逻辑是通用的。2. 硬件准备与接线这一块最容易翻车2.1 硬件清单和选购建议你需要准备的东西很少具体如下ESP32开发板常见的NodeMCU-32S、ESP32 DevKitC都能用ESP32-S3、ESP32-C3也可以只不过引脚编号有差异。新手选标准ESP32就好资料最多。RC522读卡模块买那种红色PCB的市面上绝大多数教程都用它。注意别买成PN532。M1卡/钥匙扣买RC522模块一般会送通常是白卡或者滴胶卡。需要多备用几张因为写卡实验有可能写坏。杜邦线母对母至少8根。RC522排针是公的ESP32开发板排母也是母的所以两端都要母头。可选继电器模块、蜂鸣器、LED、0.96寸OLED屏。这里多说一句选购的事。RC522模块的做工差异非常大同样的芯片天线设计和PCB用料不同读卡距离能差出一倍。我买过一块板子卡贴在上面都偶尔读不出来后来换了另一家店同一芯片的模块直接恢复到了正常的3厘米以上。所以模块不要贪便宜优先选销量大、评价里有人晒实测距离的店。2.2 接线表引脚为什么要选这几个RC522走的是SPI通信四根信号线加两根控制线再加电源和地一共八根线。给ESP32标准开发板的接线方式如下RC522引脚ESP32引脚作用说明SDA(SS)GPIO5SPI片选信号低电平选中RC522SCKGPIO18SPI时钟MOSIGPIO23主发从收ESP32向RC522写数据MISOGPIO19主收从发RC522向ESP32回数据RSTGPIO21复位RC522芯片IRQ不接中断输出本教程用轮询不接也行3.3V3.3V模块供电GNDGND共地你可能会问为什么偏偏是5、18、19、23这四个引脚因为ESP32的Arduino核心库默认会把SPI总线初始化成VSPI而VSPI的默认引脚就恰好是SCK18、MISO19、MOSI23、SS5。也就是说用默认引脚的话代码里连SPI引脚映射都不用改对新手最友好。当然你也可以用任何GPIO来重新映射SPI但那属于自找麻烦等熟练了再说。IRQ引脚我建议先不接。MFRC522库的主流程是以轮询方式工作的每跑一次loop也就几毫秒完全够用。中断能省的那点CPU时间在这个场景下感知不到。2.3 供电的三大坑我全踩过接线看着简单供电这里却藏着三个新手最容易忽略的坑。第一RC522必须接3.3V绝对不能接5V。虽然市面上有些模块板载了电平转换芯片但你不应该去赌这一下。插到5V烧芯片的例子我见过不止一次烧了直接表现为读卡距离骤降或者完全没反应。第二注意ESP32开发板3.3V引脚的供电能力。单独带RC522没有问题因为RC522工作电流也就几十毫安级别。但如果你同时接了OLED屏、RC522WiFi还在通信情况就不同了。WiFi发射瞬间的电流能冲到几百毫安电压一旦跌落第一个出问题的就是RFID表现是偶尔读不到卡。这种时候我建议给RC522单独用一个3.3V稳压模块供电前提是必须和ESP32共地。第三只要外接供电就一定要共地。RC522的地和ESP32的地不连在一起SPI通信的电平参考就乱了表现千奇百怪有时能读、有时不能读、读出的数据是乱的。遇到这种诡异的故障先把地线查一遍。3. 开发环境Arduino IDE 配 ESP32一站搞定3.1 安装ESP32开发板支持包我用的是Arduino IDE对零基础最友好别看它界面老稳定性是真的好。打开Arduino IDE先进入“文件 - 首选项”把下面的地址填到“附加开发板管理器网址”一栏https://dl.espressif.com/dl/package_esp32_index.json然后进入“工具 - 开发板 - 开发板管理器”搜索esp32选择版本点击安装。这里要注意国内直接下载经常卡在某个包上进度条一动不动。我习惯的做法是去Espressif官方GitHub的Releases页面找到对应版本比如经典的2.0.11的离线压缩包手动放到Arduino的开发板缓存目录里再通过离线包方式完成安装也就是大家常说的“arduino ide esp32离线包”方案。具体路径和操作网上有非常详细的图文教程搜索关键词就能找到。安装完成后在“工具 - 开发板”里应该能看到一堆ESP32开头的选项。标准ESP32开发板选“ESP32 Dev Module”或者“NodeMCU-32S”ESP32-S3开发板选“ESP32S3 Dev Module”ESP32-C3选“ESP32C3 Dev Module”。如果你用的是PlatformIO选开发板的方式略有不同在platformio.ini里通过board字段指定比如ESP32-S3核心板一般选esp32-s3-devkitc-1就能覆盖大部分带16MB Flash和8MB PSRAM的型号也就是丝印上写着N16R8那种板子。PlatformIO的教程也很多但我还是那句话第一次玩别同时折腾两样东西Arduino IDE先跑通再说。3.2 安装MFRC522库库管理器的安装很简单进入“工具 - 管理库”搜索MFRC522找到作者是GitHubCommunityMiguel Balboa的那个安装最新版。注意别装成别的同名库这个库是事实标准几乎所有教程都基于它。安装好之后在“文件 - 示例”里就能看到MFRC522分类下的一堆例程这本身就是最好的学习材料。尤其推荐先打开ReadAndWrite和DumpInfo这两个示例跑一遍对理解卡存储结构帮助很大。3.3 烧录方式和自动下载电路是怎么回事很多新手第一次点上传按钮就卡在“Connecting...”半天没反应这里要先搞明白开发板是怎么进入下载模式的。ESP32开发板上都有一颗USB转串口芯片常见的是CH340或者CP2102它除了把USB转成串口之外还有两个控制脚DTR和RTS分别接到开发板的EN复位脚和GPIO0脚。GPIO0在复位时为低电平芯片就会进入串口下载模式为高电平则正常启动。一键下载的时序就是先把GPIO0拉低然后EN引脚给一个短暂的低电平脉冲复位芯片芯片从Flash里的bootloader启动后esptool工具就能通过串口把新固件刷进去。这就是所谓的自动下载电路是开发板帮你自动完成的操作。如果这个自动时序没生效通常有三个原因USB线是充电线没有数据能力、串口端口被占用、GPIO0被外部电路拉高。最笨但最可靠的办法是按住开发板上的BOOT键不放点上传等IDE输出“Connecting”字样时松开BOOT键百分之九十能解决。顺便提一句在Windows上如果设备管理器里看不到COM口大概率是CP210x或者CH340的驱动没装好设备管理器里检查一下有没有带黄色感叹号的设备即可。4. 代码实战从读UID到完整门禁逻辑4.1 先把UID读出来读UID是整个项目的起点也是门槛。打开Arduino IDE新建一个工程复制下面这段代码#include SPI.h #include MFRC522.h #define SS_PIN 5 #define RST_PIN 21 MFRC522 mfrc522(SS_PIN, RST_PIN); void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); Serial.println(把卡片贴近读卡器...); } void loop() { if (!mfrc522.PICC_IsNewCardPresent()) { return; } if (!mfrc522.PICC_ReadCardSerial()) { return; } Serial.print(UID: ); for (byte i 0; i mfrc522.uid.size; i) { if (mfrc522.uid.uidByte[i] 0x10) { Serial.print(0); } Serial.print(mfrc522.uid.uidByte[i], HEX); } Serial.println(); mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); }编译上传打开串口监视器把波特率调到115200卡片贴上去串口就会打印出类似“UID: A1B2C3D4”这样的字符串。这段代码有几个细节值得说一下。第一loop里两个if都用了return而不是delay这是刻意为之。卡没有贴近时PICC_IsNewCardPresent()会很快返回false主循环立刻跳出不会被阻塞这样后续你想在同一个程序里处理WiFi、按键扫描或者OLED刷新都不会卡顿。第二uid.size表示UID的字节数标准M1卡是4字节部分NFC标签是7字节这个循环能同时兼容两种情况。第三读完之后调用了PICC_HaltA()和PCD_StopCrypto1()作用是让卡片停止工作、关闭加密通道为下一次读卡做准备很多人漏掉这两行结果就是连续刷卡时偶尔卡住。4.2 往卡里写数据光会读UID还不够RFID的价值在于卡里能存数据。M1 S50卡的存储区是1KB的EEPROM分成16个扇区每个扇区又分成4个块每块16字节。第0扇区的第0块是厂商块出厂时写了UID并锁定不能改每个扇区的第3块是控制块存放该扇区的密钥A、访问控制位和密钥B。真正能自由读写业务数据的是各个扇区的第0、1、2块。写数据之前必须先用密钥认证扇区默认的密钥A是全FF也就是6个字节0xFF。看下面的代码#include SPI.h #include MFRC522.h #define SS_PIN 5 #define RST_PIN 21 MFRC522 mfrc522(SS_PIN, RST_PIN); MFRC522::MIFARE_Key key; void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); for (byte i 0; i 6; i) { key.keyByte[i] 0xFF; } } void loop() { if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { return; } byte block 4; // 扇区1的第0块可自由读写的数据块 byte dataBlock[16] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; MFRC522::StatusCode status mfrc522.PCD_Authenticate( MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, key, (mfrc522.uid)); if (status ! MFRC522::STATUS_OK) { Serial.print(认证失败: ); Serial.println(mfrc522.GetStatusCodeName(status)); mfrc522.PICC_HaltA(); return; } status mfrc522.MIFARE_Write(block, dataBlock); if (status ! MFRC522::STATUS_OK) { Serial.print(写入失败: ); Serial.println(mfrc522.GetStatusCodeName(status)); mfrc522.PICC_HaltA(); return; } Serial.println(写入成功); mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); }写完后你可以再加一段MIFARE_Read把数据读回来验证。这里我要特别提醒一个新手常犯的错别往第0扇区写数据尤其别碰控制块。控制块里的访问控制位一旦写错整个扇区可能被永久锁死卡就废了。我给学员演示时默认都是写扇区1或扇区2的数据块离危险区越远越好。4.3 做成能开门的门禁逻辑读到了UID下一步就是把判断和执行的逻辑加进去。最简单的做法是在程序里维护一个授权卡UID白名单比对通过之后控制ESP32的一个GPIO引脚让它输出高电平驱动继电器模块继电器再控制电磁锁或者门禁电插锁。这里贴一个带白名单比对的门禁代码框架我实际在办公室门禁上用过的版本#include SPI.h #include MFRC522.h #define SS_PIN 5 #define RST_PIN 21 #define RELAY_PIN 25 MFRC522 mfrc522(SS_PIN, RST_PIN); const byte whitelist[][4] { {0xA1, 0xB2, 0xC3, 0xD4}, {0x11, 0x22, 0x33, 0x44} }; void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); } bool isAuthorized() { if (mfrc522.uid.size ! 4) { return false; } for (int i 0; i 2; i) { bool match true; for (byte j 0; j 4; j) { if (mfrc522.uid.uidByte[j] ! whitelist[i][j]) { match false; break; } } if (match) { return true; } } return false; } void loop() { if (!mfrc522.PICC_IsNewCardPresent()) return; if (!mfrc522.PICC_ReadCardSerial()) return; if (isAuthorized()) { Serial.println(授权卡开门); digitalWrite(RELAY_PIN, HIGH); delay(2000); digitalWrite(RELAY_PIN, LOW); } else { Serial.println(未授权卡拒绝); } mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); }白名单写死在代码里的问题很明显每增加一张卡都要重新编译烧录。进阶一点的做法是把UID存进ESP32的NVS非易失存储区用Preferences库读写再做一个“学习模式”长按某个按键进入录入状态此时刷卡就会把新卡的UID追加保存重启后依然生效。这样门禁的管理就不需要搬电脑了。这一块代码不难但逻辑细节比较多建议大家先把白名单版本跑通再自己试着改成Preferences存储版。4.4 外部中断在这个项目里的实战位置搜ESP32教程经常看到“外部中断”这个词很多人以为读卡必须用中断其实不是。MFRC522库的读卡主流程是轮询前面说过这样已经够快。外部中断在这个项目里真正的实战位置是门磁检测。比如你这个门禁装在实体门上想在开门或关门的瞬间立刻得到通知就可以在门框和门扇上贴一对磁性开关一根线接到ESP32的GPIO另一根接地。GPIO内部上拉门关着时开关闭合GPIO被拉低门一开开关断开GPIO恢复高电平。用attachInterrupt监听这个变化就能做到边沿触发const int doorSensorPin 26; volatile bool doorStateChanged false; void IRAM_ATTR onDoorChange() { doorStateChanged true; } void setup() { pinMode(doorSensorPin, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(doorSensorPin), onDoorChange, CHANGE); } void loop() { if (doorStateChanged) { doorStateChanged false; Serial.println(门状态发生变化); // 在这里可以记录时间、上报服务器、拍照等 } }中断服务函数里只做一件事置标志位。真正的业务逻辑放在主循环里处理这是ESP32中断编程的铁律因为ISR里不能做耗时操作否则会影响系统实时性。另外机械开关会有抖动硬件上最好加一个RC滤波电容软件上则可以在主循环里延时几毫秒再重新读一次引脚确认状态这就是常说的消抖。5. 调试实录常见问题与排查5.1 读不到卡、距离极短先查这几样读卡距离短是所有RC522新手遇到的第一个问题。正常情况下距离应该在3厘米以上如果你的卡必须贴死在模块上才偶尔出一次UID依次检查以下项目。第一用量表或者换个模块确认接线。SDA、SCK、MOSI、MISO四根线最容易被接错尤其是SDA和MOSI这两个引脚在模块上位置相邻眼神不好就会插反。第二确认你手里的卡确实是13.56MHz的M1卡。很多人从旧门禁上拆下来的卡其实是125kHz的ID卡RC522根本读不了。拿手机NFC功能试一下手机能识别出这是一张标签卡基本就是13.56MHz。第三电源问题。把模块换到USB直接供电的那一路或者外接独立3.3V供电器确认电压稳定。第四模块本身的问题。劣质模块天线虚焊、PCB天线设计差的读卡距离就只有几毫米换一个模块对比测试是最快的办法。还有一个小经验把RC522模块悬空、远离金属桌面和ESP32的天线区域读卡距离会明显好一些。金属板会吸收磁场能量等于给天线蒙了一层被子。5.2 能读UID但写卡一直失败能读UID说明SPI通信和模块初始化都没问题写卡失败通常出在认证环节。最常见的就是没有先调用PCD_Authenticate就急着MIFARE_WriteM1卡对未认证的写操作会直接拒绝。第二个常见原因是这张卡的密钥已经被改过默认的FFFFFFFFFFFF认证不过去这时需要知道正确的密钥A才能继续否则就别指望了。第三个原因是写错了块号。前面说过第0块和控制块都有保护机制不是想写就能写的报错信息里会明确返回错误码比如STATUS_ERR或STATUS_AA_WRONG。拿到错误码之后用GetStatusCodeName打印出来对照MFRC522库源码里的定义查比瞎猜快得多。5.3 ESP32上电反复重启、烧录失败怎么办这类问题看起来和RFID无关但会卡住你整个项目。最典型的是选错开发板型号。ESP32和ESP32-S3、ESP32-C3的启动方式不完全一样在Arduino IDE开发板菜单里选错型号轻则烧录失败重则反复重启。还有一个很常见的原因是USB线问题很多数据线其实只能充电识别不到串口设备或者烧录到一半断开换一根短一点的、确定带数据功能的线试试。另一类问题是手动按BOOT的过程。确保你按的是正确的按键ESP32开发板上一般有EN和BOOT两个键按住BOOT再点上传看到“Connecting”字样后松开。有些开发板没有BOOT键那就只能检查自动下载电路是否工作正常或者用外部电路强制控制GPIO0。Windows下如果一直无法识别串口先装驱动再插拔一次。5.4 常见问题速查表问题现象可能原因解决思路卡贴上去没反应接线错误/卡类型不对核对四根SPI线确认卡是M1卡能识别但距离极短供电不足/模块劣质/环境有金属独立3.3V供电换模块远离金属面读卡时好时坏共地问题/杜邦线接触不良检查地线重新插拔换线写卡认证失败未认证/密钥被改/写错块先PCD_Authenticate确认密钥和块号烧录卡在Connecting自动下载时序失效按住BOOT上传换数据线检查驱动上电反复重启选错开发板型号/电源不足核对开发板菜单换供电串口数据乱码波特率不对/两端不一致确认串口监视器波特率115200排查的顺序我总结成一个原则先软件后硬件先简单后复杂先换线再换板。遇到诡异问题别急着怀疑代码先拿万用表量电压、查断线往往五分钟就能定位。6. 项目扩展方向从刷卡器到考勤系统6.1 把UID送到上位机C#考勤系统怎么接刷卡门禁只是起点真正常见的需求是考勤。思路很简单ESP32通过USB串口把UID字符串发送给电脑比如约定好格式“UID:A1B2C3D4”上位机用C#写一个WinForm程序监听串口解析这个字符串再到数据库里比对员工信息记录上下班时间。这个方案的关键在于串口协议要约定得足够清晰。我建议用固定的帧头帧尾比如“$UID:A1B2C3D4#”上位机按帧解析不要用裸的字符串否则串口数据一错位整行数据就废了。另外ESP32端发送之前最好做一次简单校验和虽然串口误码率不高但考勤数据错了会非常麻烦。C#那边就是标准的SerialPort组件设置相同的波特率DataReceived事件里收数据放到缓冲区按行处理即可。如果不想插USB线更工程化的做法是ESP32联网。开发板连上WiFi后把UID通过HTTP POST或者MQTT协议发给服务器服务器再推送小程序或App。国内常见的物联网平台比如OneNET也提供了现成的数据流接口把刷卡事件当成一个数据点上报后台做统计报表这套架构就是商用考勤系统的雏形。6.2 顺手可以加的东西OLED、温湿度、蓝牙做考勤机显示是刚需。加一个0.96寸OLED屏把当前时间、刷卡UID提示、开门状态显示出来整台机器的可用性瞬间提升一个档次。I2C的OLED接SDA、SCL两个引脚库用Adafruit_SSD1306代码里注意在刷屏和读卡的循环之间做好调度不要让显示阻塞了读卡。再顺手接一个DHT11或者SHT30温湿度传感器把机房或者办公室的温湿度一起上报一个项目就把传感器、射频、显示、联网全串起来了练手价值直接拉满。如果你想用手机控制门禁ESP32的经典方案是蓝牙BLE。手机App发送开锁指令ESP32收到后控制继电器这就相当于给门禁加了一把“手机钥匙”。这种项目在GitHub上能找到很多现成的开源App和固件直接改一改就能用。6.3 一个提醒UID白名单只能算“玩具级”安全最后必须认真说一句只比对UID的门禁方案安全性非常有限。M1卡的UID是可以被复制卡冒充的市面上有专门的UID可写空白卡把别人的UID写到一张新卡里你这套系统就形同虚设。自己做办公室门禁、家庭门禁玩一下没有问题但如果用在真实考勤或者计费场景一定要用M1卡数据区的密钥认证甚至换成CPU卡或者手机NFC这种带安全算法的方案。这也是为什么我一直强调要学写卡和读卡而不是停留在读UID。把数据区玩明白了你的项目才真正跨过了“玩具”和“产品”的分水岭。