news 2026/10/18 1:07:09

ESP32+RC522实现RFID刷卡门禁:从接线到代码的完整入门指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32+RC522实现RFID刷卡门禁:从接线到代码的完整入门指南

零基础玩家最容易遇到的一个场景:把ESP32开发板和一块RFID模块买回来,插上USB,看到板载LED亮了,却不知道下一步该干什么。玩ESP32最让人上头的项目之一,就是让它读出一张卡片的ID,然后像门禁一样“滴”一下点亮LED——没错,本文要讲的正是如何通过RFID无线射频卡模块,让ESP32拥有“刷卡”能力。

这篇文章适合完全没接触过射频卡、刚会烧录点灯程序的读者,也适合想快速上手RFID做门禁、考勤、小玩具的爱好者。我会把RFID的基本原理、硬件接线、Arduino IDE环境配置、第一段能跑的代码、以及从“能读卡”到“能做门禁系统”之间那些绕不开的坑,全部串起来讲。不保证你看完能立刻做出商用门禁,但保证你能在半小时内让ESP32“认识”一张卡,并且知道后续该怎么扩展。

1. RFID不是悬空的理论:先明白刷卡到底在刷什么

1.1 “刷卡”靠的是电磁感应,不是蓝牙也不是Wi-Fi

很多人把RFID和NFC、蓝牙混为一谈,其实它们完全是两回事。RFID无线射频卡的工作方式非常“物理”:读卡器内部有一个线圈,通上交流电后会在周围产生一个交变磁场;卡片靠近时,卡片内部的天线线圈感应到这个磁场,产生感应电流,这个电流给卡片上的芯片供电,然后芯片再通过负载调制的方式把数据“回答”给读卡器。

可以这样理解:读卡器像一个不停喊话的广播站,卡片本身没有电池,它靠近广播站时被“喂”了一口电,才有力气回话。所以卡片放在读卡器上,不需要按任何按钮,也不需要插电,这就是“无线射频识别”名字的由来。

市面上常见的RFID卡大多是13.56MHz的高频卡,对应的是ISO 14443协议。我们手里那张薄薄的塑料卡,内部其实是一张线圈加一个芯片。芯片里有只读的卡号(我们常说的UID),也有可读写的存储区域。这个频率的卡读卡距离通常在2到5厘米左右,不像低频远距离卡那样能隔一两米读,但适合门禁、会员卡、办公考勤这类场景。

1.2 我们常说的“刷卡”,用的其实是卡里的两样东西

卡里可供程序使用的数据主要分两类:一类是卡号(UID),出厂时写死,全球唯一;另一类是数据扇区,可以加密读写,用来存余额、身份信息和管理数据。

门禁系统一般只看UID。读卡器把卡号读出来,和数据库里的白名单比对一下,匹配就开门,不匹配就响一声拒绝。而食堂卡、公交卡这类需要计费的系统,则会用扇区读写功能,刷卡时扣余额,再把结果写回卡里。

对零基础来说,先抓着UID做文章是最直观的。因为读UID不需要知道密钥,不需要了解加密算法,代码量很小,跑通了马上有“我在做刷卡系统”的成就感。等基础稳定了,再去研究扇区读写和加密,路线会更顺。我见过不少新手一上来就想破解加密卡,结果第一步就被密钥整崩溃,其实没必要,先学会走路再跑步。

1.3 为什么新手入门首选RC522而不是其他读卡方案

现在能读13.56MHz卡片的方案不少,但最适合ESP32入门的是RC522模块。RC522本身是一颗射频读写芯片,市面上绝大多数模块都是围绕它做的,价格便宜、资料多、驱动库成熟。

我自己也对比过几种方案:

  • RC522模块:SPI接口,接线简单,ESP32和Arduino生态支持极好,适合DIY门禁、智能柜、小玩具。
  • 韦根读卡头:输出的是韦根协议脉冲信号,更适合接入现成的门禁控制器,不适合直接接ESP32读卡号。
  • 手机NFC模块/PN532:功能更强,支持NFC读写和卡模拟,但价格更高,配置也更复杂,新手容易迷失在协议细节里。

从“用最少的代码跑通一个完整项目”这个目标出发,RC522几乎是唯一解。等以后项目复杂度上来了,再考虑升级到PN532也不迟。老实说,RC522做出来的门禁系统,对学习用途来说已经非常够用了。

2. 硬件选型与接线:RC522模块的引脚陷阱和供电细节

2.1 下单前先看这三件事

RC522模块看着长得都差不多,但不同批次和不同厂家做出来的板子还是有细微差别,买的时候最好注意三点。

第一,供电电压。绝大多数模块上面印着3.3V,少数所谓“5V版本”其实板载了稳压芯片,内部依然按3.3V工作。如果模块上明确写着5V供电,插5V也没问题,但如果是纯3.3V模块,直接接5V就可能烧芯片。最稳妥的做法是买标着3.3V供电的模块,然后从ESP32的3V3引脚取电。

第二,引脚排列。RC522模块常见的引脚有8个:SDA(也叫SS/CS)、SCK、MOSI、MISO、IRQ、GND、RST、VCC。但不同商家做的模块引脚顺序不完全一样,有的把RST放在SDA旁边,有的把VCC放在最边上。接线前一定先看模块丝印,别按网上的图闭眼插。

第三,天线面积。模块PCB上的天线线圈越大,读卡距离越稳定。我买过的几个模块里,天线区域占PCB大半的那种,实测读卡距离能稳定在3到4厘米;天线缩水的小板子,经常贴着卡都读不出来。这个影响在买的时候看不出,到手后想退货又麻烦,所以尽量选天线占比大的板子。

2.2 ESP32开发板和RC522的SPI接线,照着插就行

RC522支持SPI和I2C两种接口,模块默认通常工作在SPI模式。ESP32上有两套SPI控制器,默认接法是走VSPI引脚。我用的接线方式如下:

RC522引脚功能连接到ESP32引脚
SDA / SSSPI片选信号GPIO5
SCKSPI时钟GPIO18
MOSI主出从入GPIO23
MISO主入从出GPIO19
RST复位GPIO22
GND电源地GND
VCC电源输入3V3
IRQ中断请求不接,留空

这里有两个容易混淆的细节:一是SDA在SPI模式下实际上就是从机选择信号SS,不要看到SDA就以为它是I2C接口,然后接到I2C引脚上去。二是IRQ引脚对入门项目来说不需要接,RC522的库主要是通过轮询方式读取数据,不需要中断信号,把IRQ空着就行。

如果你不想用上面这组默认引脚,也可以在代码里通过SPI.begin()自定义其他GPIO,比如把SS改到GPIO15,方便和别的外设避开。但新手阶段不建议折腾,先用默认引脚跑通,再考虑引脚复用问题。

2.3 供电细节:为什么不能贪方便直接上5V

ESP32的USB接口输入是5V,板上自带稳压器把5V转成3.3V给芯片和GPIO用。RC522模块的典型工作电压是3.3V,所以直接用ESP32的3V3引脚给模块供电是最稳妥的。

有的教程说5V也能供电,因为模块上集成了一颗稳压芯片,把5V降到3.3V。这话没错,但有一个隐患:如果模块上的稳压芯片质量一般,5V输入时的纹波可能更大,读卡稳定性会受影响。我实测过同一张卡、同一个位置,3.3V供电时稳定读卡,换成5V供电后会偶发读不到卡的情况。

另外还要注意电平兼容问题。RC522工作电压是3.3V,它的SPI信号电平也是3.3V,ESP32的GPIO输出同样是3.3V,所以两边可以直接互连,不需要加电平转换芯片。如果模块要求5V逻辑才稳定,反而要加转换电路,那对新手来说就太复杂了。

供电部分我的建议是:给ESP32用质量好一点的数据线连接电脑USB口或5V电源适配器,然后从3V3引脚给RC522取电,GND要共地。别把舵机、电机、继电器这些大功率设备直接挂在同一个3V3上,瞬间的电流跌落会让RC522直接死机。

3. 环境搭建与库选择:让Arduino IDE和ESP32、RFID“握手成功”

3.1 在IDE里加入ESP32支持

RC522的上手推荐用Arduino IDE,因为生态成熟、示例代码多。直接用Arduino IDE写ESP32需要先安装开发板支持包:打开文件、偏好设置,在“附加开发板管理器网址”里填入ESP32的板级支持包地址,然后到“开发板管理器”搜索ESP32并安装。

这一步的坑在于,国内网络环境下安装可能很慢,中途断开会导致开发板列表里找不到ESP32。安装完成后,在“开发板”里选择“ESP32 Dev Module”。如果你的板子是其他型号,选相近的通用ESP32开发板选项通常也能编译烧录。

装好之后先做一个小测试:写一个点灯程序烧进去,确认开发板能被识别、能正常上传。这一步看起来多余,但能提前排除驱动没装好、端口选错、烧录线是“充电线不能传数据”这类基础问题。很多新手卡在RFID半天没进展,最后发现是USB线压根没有数据传输功能。

3.2 选择RFID库:别下载一个就完事

库管理器里搜索“MFRC522”,会出现好几个相关库,最常用的那个由社区维护,名字就叫MFRC522,下载量和使用量都最高,示例代码也最完整。安装时注意版本号,选最近更新的版本一般没毛病。

MFRC522库会自动依赖SPI库,所以安装时不用自己额外去装SPI。装完后在“文件-示例”里能看到“MFRC522”分类,里面有一堆示例程序,像ReadAndWrite、DumpInfo、rfid_default_key等等。这些示例就是我们最好的学习素材。

这里提醒一句:有些教程会推荐另一个库,功能类似但接口有些差异。如果你照着别人的代码编译报错,先看一下自己装的是哪个库,大概率是接口不兼容。我自己的习惯是统一用MFRC522库,网上绝大多数示例都能直接编译过,省心很多。

3.3 先跑官方示例DumpInfo看看“读卡”是什么效果

库安装好之后,先不急着自己写代码,打开示例里的DumpInfo程序,编译并烧录到ESP32上。然后打开串口监视器,波特率设置为115200,把一张IC卡放到RC522天线区域,静静看输出。

正常情况下,串口监视器会打印出卡片的UID、卡片类型,以及卡内每个扇区的数据。如果能看到这些内容,说明你的接线、供电、库安装全部没问题,整个RFID硬件链路是通的。如果什么都没打印,先别急着怀疑代码,把卡在模块天线中心位置缓慢移动几次,很多模块对位置比较敏感,卡放到边缘区域就检测不到。

DumpInfo这种“读卡”行为虽然不起眼,但它验证了整个RFID链路能正常工作。把这个示例弄明白了,后面自己写代码就有了一个可靠的起点。

4. 从Demo到自己写代码:读卡号、比对、亮灯

4.1 写一个最简框架:头文件、全局对象、初始化

跑通官方示例后,就可以开始自己写第一段真正属于自己的代码了。先搭建一个最简框架:

#include <SPI.h> #include <MFRC522.h> #define RST_PIN 22 #define SS_PIN 5 MFRC522 mfrc522(SS_PIN, RST_PIN); void setup() { Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init(); Serial.println("RFID reader ready."); }

这段代码做的事情很简单:引入SPI库和MFRC522库,定义复位引脚和片选引脚,创建MFRC522对象,然后初始化串口、初始化和读卡器。注意SS_PIN对应的就是上一节接线表里的GPIO5,RST_PIN对应GPIO22,和接线一一对应,代码就能和硬件对上了。

4.2 主循环里识别卡和读取UID

接下来是核心部分。主循环的逻辑就是不断检查“有没有新卡出现”,有的话就读取卡上序列号,然后把卡号以十六进制形式打印出来。

void loop() { if (!mfrc522.PICC_IsNewCardPresent()) { return; } if (!mfrc522.PICC_ReadCardSerial()) { return; } Serial.print("Card 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(); }

PICC_IsNewCardPresent()只负责检测天线范围内有没有新卡,PICC_ReadCardSerial()负责真正读取卡号。这两个函数是MFRC522库中最常用的组合,几乎每段读卡程序都会出现。读取出来的UID是字节数组,长度通常4个字节,所以打印时要做十六进制格式化,否则显示出来的东西乱七八糟。

当卡从读卡器上拿开后,循环会立刻回到“等待新卡”的状态。整个程序虽然只有二三十行,但已经是一个完整的读卡程序了。你把它烧录进去,打开串口监视器,放上卡,就能看到一串十六进制卡号。这个卡号就是从芯片出厂时写死的全球唯一标识。

4.3 把识别结果变成动作:板载LED闪烁

光能读卡号还不够,至少要让ESP32做出点“反应”。ESP32开发板上一般有个板载LED,连接在GPIO2上。我们可以在读到卡号后,让LED闪烁几次,模拟“刷卡成功”的提示。

#define LED_PIN 2 void setup() { pinMode(LED_PIN, OUTPUT); // ...其他初始化代码 } void blinkOK() { for (int i = 0; i < 3; i++) { digitalWrite(LED_PIN, HIGH); delay(200); digitalWrite(LED_PIN, LOW); delay(200); } }

然后在读取卡号成功后调用blinkOK(),效果就是“滴”一下亮灯。如果再往后扩展,把LED替换成继电器模块,就能控制电磁锁开门,一个最简单的刷卡门禁就成型了。GPIO输出能力有限,继电器模块需要独立供电,具体怎么接,下一节会展开讲。

5. 让“刷卡”变得更像真实产品:状态机、防抖与掉电存储

5.1 为什么不能整天让继电器吸合:加延时不是好办法

很多新手读完UID后做的第一件事,就是写一个简单判断:卡号匹配就让GPIO输出高电平,让继电器吸合。但马上会发现一个尴尬的问题:卡一直贴在读卡器上,主循环不停地读到同一张卡,继电器就相当于一直被锁住,门锁永远开着。

解决思路之一是在读取成功后加一个delay(3000),让继电器只吸合3秒再断开。但delay会阻塞整个主循环,三秒内CPU什么都不干,如果这时候有别的传感器要处理,或者是多张卡快速轮流刷卡,系统就卡死了。延迟方案只能用于演示,不能用于实际产品。

更好的方案是引入状态机。核心思想是:循环每一次只处理“状态变化”,而不是处理“持续存在”。卡从无到有是一个状态变化,卡一直贴在那里不再触发第二次;卡离开后再贴回来,才算是新的刷卡动作。

5.2 用“上一张卡”变量实现刷卡防抖

状态机听起来高级,实现起来其实非常简单。我们只需要记住上一次读到的卡号,每次有新卡读取时,先和上一次的卡号比较,如果相同,说明还是同一张卡一直贴着,直接忽略;如果不同,才执行开锁动作并更新“上一张卡”的变量。

String lastUID = ""; void loop() { if (!mfrc522.PICC_IsNewCardPresent()) { return; } if (!mfrc522.PICC_ReadCardSerial()) { return; } String uidStr = ""; for (byte i = 0; i < mfrc522.uid.size; i++) { if (mfrc522.uid.uidByte[i] < 0x10) { uidStr += "0"; } uidStr += String(mfrc522.uid.uidByte[i], HEX); } if (uidStr != lastUID) { lastUID = uidStr; blinkOK(); // 这里执行继电器开锁、串口打印等动作 } mfrc522.PICC_HaltA(); }

这段代码就是最简单实用的“防抖”状态机。lastUID相当于一个“上次刷卡记忆”,整个程序通过它判断这次读卡是不是一次全新的事件。实践证明,这个办法在门禁场景里比什么复杂算法都管用,而且代码好理解、不容易出错。

要注意的是,这个逻辑只防“重复刷卡”,不防“授权盗刷”。如果一张被授权的卡丢了,捡到卡的人照样能进门,所以正式项目里还需要配合密码、指纹或人证核验。

5.3 掉电后白名单怎么办:用Preferences持久化

如果白名单卡号直接硬编码在代码里,每次想添加新卡,都要改代码重新烧录一次,操作太不优雅。更麻烦的是,如果项目里允许用户自主添加卡,程序里根本没有存储空间来保存这些数据。ESP32虽然没有传统EEPROM,但它提供了Preferences库,可以简单地把数据写入非易失存储区,掉电后不丢失。

Preferences库的使用逻辑类似一个微型数据库。先定义一个命名空间,然后通过putString和getString来读写数据,比如:

#include <Preferences.h> Preferences prefs; void setup() { prefs.begin("card_db", false); String savedUID = prefs.getString("allowed1", ""); // 如果savedUID非空,就能作为白名单来比对 } void saveNewCard(String uid) { prefs.putString("allowed1", uid); prefs.end(); }

这样添加一张白名单卡,只需要刷一次卡,程序把卡号存进Preferences,下次掉电重启,卡号还在。想存十张卡,就加十个key,或者用数组形式保存。这个模块对门禁项目来说是刚需,否则每掉电一次就要重新刷一遍白名单,太痛苦。

不过话说回来,Preferences存卡号只是学习项目或小型DIY的合法方案。商用门禁系统需要更强的安全管理,比如防复制、防重放、防篡改,这套组合在RFID领域是一大分支,远不是一个简单的读卡程序能覆盖的,这里就不展开讲了。

6. 实测中的常见翻车现场:天线距离、乱码、干扰排查

6.1 读不到卡的排查顺序

项目跑不起来、读不到卡,这是新手遇到最多的问题。我的排查顺序是:先看软件输出、再看硬件接线、最后才怀疑模块本身。

第一,串口监视器里有没有“RFID reader ready”这句打印。如果连这句都没有,说明Arduino IDE根本没连上ESP32,或者串口波特率不对,先查串口选择、波特率设置和USB线是否为数据线。

第二,如果初始化打印正常但放卡没反应,检查接线。特别是SDA/SS有没有接对GPIO5,RST有没有接对GPIO22,VCC是不是接到了3V3而不是5V。我曾经把SS接到GPIO4,代码里写的却是GPIO5,结果折腾了半小时才发现问题,硬件没问题、代码也没问题,只是“人”没对上。

第三,如果接线和代码都对,还是读不到,拿卡片在模块天线上方反复缓慢移动。RC522的读卡区域通常位于PCB天线线圈的中央和边缘附近,有些卡套太厚、卡片贴了金属贴纸,都会导致耦合不充分,读卡距离会明显缩短。

6.2 读卡距离只有一两厘米怎么办

RC522这类13.56MHz模块,正常读卡距离在2到5厘米之间,如果只能贴着读,说明天线部分工作不正常。

最常见的原因是模块周围有金属。把RC522直接放在金属桌面或者金属外壳上,天线磁场会被金属吸收,读卡距离直接缩短到几乎没有。解决办法是让模块悬空,或者用非金属支架垫高,让天线背面不要紧贴导电物体。

另一个常见原因是模块天线线圈区域被遮挡。有些外壳把模块完全罩住,PCB天线上方只留一个很薄的塑料壳,也会影响读卡。我在做外壳的时候,专门在天线位置开了一个槽,读卡距离立刻恢复水平。

还有一点容易被忽略:模块使用了长排线连接。SPI信号线如果过长,信号完整性和串扰会变差,尤其飞线飞了十几厘米,读卡偶尔失败很正常。尽量缩短连接线,或者用杜邦线保持间距。

6.3 串口乱码和编译失败

串口打印出乱码,十有八九是波特率对不上。Arduino IDE右下角串口监视器的波特率,和代码里Serial.begin(115200)中的数值要一致。很多示例程序用的是9600或115200,改一个设置就行,不用改代码。

编译失败则要重点看报错信息。最常见的是“Library MFRC522 not found”,说明库没有安装成功;另一种是“SPI.h not found”,说明基础库出了问题,在库管理器里重装即可。还有一种是代码里使用了Repository里旧版的函数,和新的库接口不兼容,这时把库降级到示例对应的版本就好。

6.4 继电器或电机一动作,读卡就失灵

接触过几个做门禁的朋友,反馈过同一个症状:单独读卡没问题,但接上继电器开门那一下,读卡就中断;关门之后,又能正常读卡。

问题根源几乎都是供电瞬间跌落。继电器吸合瞬间,线圈电流很大,如果继电器和RC522共用一个电源,瞬间的大电流会把电压拉低,导致读卡器短暂丢电。解决办法是把继电器或电机单独供电,用ESP32的GPIO只控制信号,让驱动模块的电源和RFID模块的电源分开,同时保证GND可靠共地。

进阶一点的办法是在电源两端并联一个大一些的电解电容,比如470uF甚至1000uF,吸收瞬态压降。这个电容能兜住一部分瞬间电流需求,实测效果很不错。但要注意电容耐压要留有安全余量,通常用耐压10V以上的规格比较稳。

另外还要提示一句:继电器负载如果是电磁锁、电动门这类设备,一定要选正规厂家的继电器模块,并且把高压部分和低压控制部分做可靠的物理隔离,别用裸板直接上220V,安全永远是第一位。

最后,再分享一点我个人的实操体会。每次做RFID项目,我都建议先不接任何继电器、不写任何业务逻辑,只在桌面上把读卡程序跑通,让串口能稳定输出卡号,再去加功能。因为这个阶段如果出了问题,嫌疑范围最小,随便几分钟就能定位。等读卡稳定后,再考虑状态机、白名单、持久化和外部设备控制,每一步都是在前一步验证过的地基上搭建,项目越往后做越顺,不会出现“全都接了但哪一步都不确定”的尴尬局面。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 1:07:05

EN 55014-1与EN 55014-2详解:家电电动工具EMC认证与整改实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:06:53

STM32C5与CubeMX2:新一代工业控制芯片开发实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:06:10

Android显示驱动从0到1学习路线:从硬件接口到Framework实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:03:22

USART串口通信从原理到实战:帧结构、波特率与调试要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:02:21

谐波电流测试与整改全解析:从原理到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华