ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32 RFID刷卡实战:用MFRC522读卡与数据块读写

ESP32 RFID刷卡实战:用MFRC522读卡与数据块读写 零基础学ESP32RFID无线射频卡——让ESP32拥有“刷卡”能力先说清楚这期内容能干出什么活你手上有一张食堂的门禁卡、工牌卡或者某宝几块钱一包的IC白卡我教你用一块ESP32开发板加一个十几块钱的RFID读卡模块把这串卡号读出来、显示在串口上并且能对卡片内部的数据块做读写操作。学完之后你可以继续往下做智能门锁、考勤打卡机、会员卡识别甚至给树莓派/电脑做个刷卡登录器。整个过程不需要任何硬件基础只要会插几根杜邦线、会复制粘贴代码就能跑通。适合谁来参考完全没碰过单片机的人可以跟完全程玩过Arduino但对ESP32陌生的人也值得看一眼因为ESP32的引脚和Arduino Uno不一样直接照搬Uno的接法会踩坑。我尽量把接线图、库选择、代码注释、踩过的坑都写到能“照着做就会”的程度。1. 项目整体设计与思路拆解1.1 为什么选择MFRC522而不是读卡头或NFC模块市面上常见的RFID读卡模块有几个方向RC522也叫MFRC522是13.56MHz的高频读卡方案读取的是M1经典卡、NTAG系列这类ISO14443A协议的卡片也就是你身边最常见的门禁卡、公交卡、校园卡还有一种低频方案是125kHz的EM4100/ID卡读卡模块价格便宜但只能读一个固定ID协议相对封闭DIY玩起来不如RC522灵活。我推荐新手从MFRC522入手原因有三个成本极低模块加几张卡总共不到二十块钱坏了不心疼资料量巨大Arduino生态里还专门有Miguel Balboa维护的MFRC522库示例程序直接改几行就能用它既能读卡片UID又能对M1卡片的扇区做字节级读写这是后面做考勤系统、会员系统最需要的核心能力。注意区分MFRC522和PN532是两个方向。PN532是近场通信模块支持读NFC卡、模拟NFC卡、还支持和手机通信玩法更多但价格贵一倍接线难度也更高。零基础阶段先把RC522连熟后面再换PN532会轻松很多。1.2 这套方案的整体架构整个系统其实就是“传感器 主控 输出”三件套。RFID模块负责和卡片做无线通信它读到的数据通过SPI总线传给ESP32ESP32再把结果通过串口打印出来给你看。结构拆开来看层级组件作用感知层MFRC522模块 天线线圈发射13.56MHz射频场和卡片进行无线数据交换主控层ESP32开发板初始化SPI向RC522发送读卡指令解析返回值交互层串口监视器 / 后续接入显示屏、蜂鸣器展示卡号、提示刷卡成功状态这里有个非常关键的设计原因为什么RC522要用SPI而不是走I2C因为SPI是全双工、速度快一次性可以连续读几十个字节适合RFID这种需要频繁交换数据的场景而且MFRC522这个芯片在Arduino库里的默认走线方式几乎全是SPI网上教程多照着接不容易出错。I2C模式虽然能省引脚但我在实际调试中发现它对时序要求更严格偶尔会出现读卡不稳定所以新手不要为了省两根线去改I2C。1.3 这个项目后续能扩展成什么你先跑通读UID后面至少有三个很好落地的方向刷卡门禁把ESP32接一个继电器控制电磁锁开卡白名单比对通过后触发继电器吸合加上OLED屏幕显示“验证通过”这就是一套完整智能门锁原型考勤打卡ESP32把卡号通过WiFi上报到服务端服务端比如用C#写的桌面程序或者自建MQTT服务记录时间戳和卡号形成打卡记录离线名单管理在卡片某个扇区写入用户姓名、余额之类的数据ESP32读取扇区内容后直接在本地判断不依赖网络和云端。这些扩展方向都会在后续代码里埋好基础不要急。2. 硬件准备与接线实操2.1 器材清单你真正需要买的东西先说结论最便宜的搭配是ESP32开发板30Pin加上RC522模块加上3张IC卡加1个钥匙扣卡大概三十块钱出头。这里我列一个清单附带几个我实测过的使用建议。ESP32开发板建议买带USB转串口芯片CP2102或CH340的经典款。市面上带“WROOM-32”字样的版本通用性最好板载天线信号也比S3/C3系列更容易焊接面包板。如果你手头只有ESP32-S3也能做但脚位定义不同下面会额外提醒。MFRC522模块按接口分有焊针和排母两种建议买焊针版本搭配杜邦线插面包板就方便。注意看模块背面有没有丝印标明引脚名杂牌模块丝印经常印反买回来先核对一下。IC卡买M1白卡S50芯片一版十张大概十块钱。不要买单张几毛钱那种没有品牌芯片的超便宜卡实测部分杂牌卡在低功耗状态读卡困难。杜邦线公对母若干母对母若干因为ESP32板子上是排针RC522模块上也是排针中间需要跨过面包板接到ESP32。面包板不是必须但新手强烈建议搞一块防止接线短路。另外如果不确定自己的卡是不是M1卡可以在读卡代码跑通之后用手机NFC功能读一下看看一般M1卡会显示标签类型为MIFARE Classic 1K。卡片只剩UID可读、扇区数据无法读写的话很有可能是CUID卡或者UID卡读卡模块能读出来但写数据会有区别。2.2 ESP32和MFRC522的完整接线这一步最容易被搞乱原因是ESP32的SPI引脚和Arduino Uno的不一样。Uno的SPI是D11、D12、D13很多老的RC522教程直接拿这个来放到ESP32上就翻车。ESP32板子上的VSPI引脚固定为SCK18、MISO19、MOSI23使用SPI库时默认就是走的这几个引脚。CS线可以自定义但要避免占用系统占用脚和启动脚。我的推荐接线表如下MFRC522模块引脚ESP32引脚说明SDA / SSGPIO5片选信号可换其他IO但避免GPIO12、GPIO15上电电平影响启动SCKGPIO18SPI时钟MOSIGPIO23主出从入MISOGPIO19主入从出IRQ不接本教程用轮询方式不需要中断输出RSTGPIO22复位信号也可以直接接3.3V但不要这样偷懒3.3V3.3V千万不能接5VGNDGND共地必须接别省这是标准接法。如果你用的是ESP32-S3SPI引脚不一样常见可用SCK12、MISO13、MOSI11CS接10RST接9具体以你手里开发板的引脚图为准。S2、C3、C6也是一样先查脚位图再接线不要靠记忆盲接。有一个接线细节特别容易出现模块上标的SDA在有些情况下不是I2C的数据线而是SPI的从机选择信号。MFRC522同时支持SPI和I2C接口模块出厂大多配置为SPISDA引脚实际上用于SPI的片选。别把它接到ESP32的SDA引脚上不要一开始就误以为走的是I2C通信。接完线之后先做一次上电检查。用万用表量一下RC522模块的3.3V引脚和GND之间电压正常是3.3V上下。如果量出来是5V说明你接错到ESP32的VIN或5V引脚了立刻断电处理。插线时养成一个习惯断电插线上电检查确认无误后再接板子USB。3. 开发环境搭建Arduino IDE装ESP32支持3.1 配置Arduino IDE的开发板管理器ESP32在Arduino IDE里不会直接出现需要手动添加开发板管理器地址。我这里只说最稳定的流程。打开Arduino IDE在“文件 - 首选项 - 附加开发板管理器网址”里填入以下地址https://espressif.github.io/arduino-esp32/package_esp32_index.json然后到“工具 - 开发板 - 开发板管理器”搜索esp32找到Espressif Systems的esp32包安装版本推荐2.0.17或者3.x的稳定版。这里有个经验不要追最新版3.0版本对部分老库的兼容性有调整MFRC522库本身没什么压力但如果你之前装过老版本库或项目建议锁2.0.17这一档。国内网络下这个地址下载可能比较慢甚至卡进度条。这是正常的不要以为是死机。卡住的话可以关掉IDE重开一次有时候会走镜像缓存。实在不行就找别人打包好的esp32离线包解压到C:\Users\你的用户名\Documents\Arduino\hardware\espressif不过离线包方案有个要注意的坑不同版本离线包的目录结构和官方稍有差异装完一定要去“开发板管理器”里确认开发板列表里出现了ESP32相关的板卡。安装完成后选择开发板工具 - 开发板 - ESP32 Arduino - ESP32 Dev Module。注意不要选成“ESP32 Wrover Module”除非你的板子明确是WROVER模组选错了Flash大小会有偏差虽然大多数代码能跑但串口监控填默认地址会有小概率冲突。3.2 安装MFRC522库库管理路径“工具 - 管理库”搜索MFRC522作者是Github社区里的知名维护者Miguel Balboa库全名是MFRC522注意看清楚别装成MFRC522_I2C库。安装后你会在“文件 - 示例 - MFRC522”下看到一堆示例。本教程主要用到DumpInfo这个示例逻辑它负责在串口打印卡片的完整信息。安装完库之后先跑一次DumpInfo验证硬件通不通再自己写简化代码。还有一个细节很多国产模块包装上写的是“RC522”但芯片本身是MFRC522的兼容片。库对这类兼容片的支持很好基本不用担心。如果读卡时出现“Unknown MFRC522”之类的初始化错误大概率是接线问题而不是芯片问题。3.3 串口监视器设置上传代码前先确认三件事ESP32和电脑的USB线要选有数据传输能力的线我在实验室里遇到过很多次“烧录总是失败”最后发现是那根线只能充电不能传数据工具 - 端口里选择正确的COM口多个设备插入时在设备管理器里确认下USB转串口的名字串口监视器波特率调成和代码里Serial.begin一致的115200否则看到的是乱码。如果你上传代码时反复报“A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header”按住板子上的BOOT键不放点上传看到“Connecting...”字样再松开90%可以解决。这个问题在串口监视器占用COM口时也会出现记得先把串口监视器关掉再上传。4. 代码实现从读取UID到数据块读写4.1 第一个程序读卡号和卡片类型打开Arduino IDE新建一个文件把下面代码直接粘贴进去。这段代码的作用是初始化RC522模块然后每隔一段时间检测天线范围内是否出现卡片出现就打印卡片的UID和卡片类型。#include SPI.h #include MFRC522.h #define RST_PIN 22 // 复位引脚接ESP32 GPIO22 #define SS_PIN 5 // 片选引脚接ESP32 GPIO5 MFRC522 mfrc522(SS_PIN, RST_PIN); // 创建MFRC522实例 void setup() { Serial.begin(115200); // 与串口监视器波特率保持一致 SPI.begin(); // 启用SPI通信 mfrc522.PCD_Init(); // 初始化RC522模块 mfrc522.PCD_DumpVersionToSerial(); // 打印模块固件版本号验证初始化成功 Serial.println(等待卡片靠近...); } 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) { // 小于0x10的值前面补0保证每个字节显示两位十六进制 if (mfrc522.uid.uidByte[i] 0x10) { Serial.print(0); } Serial.print(mfrc522.uid.uidByte[i], HEX); } Serial.println(); // 打印卡片类型 MFRC522::PICC_Type piccType mfrc522.PICC_GetType(mfrc522.uid.sak); Serial.print(卡片类型: ); Serial.println(mfrc522.PICC_GetTypeName(piccType)); // 通信结束释放卡片 mfrc522.PICC_HaltA(); }上传成功之后打开串口监视器把一张卡放到天线线圈上方大概2到4厘米范围内你应该会看到类似这样的输出卡片UID: 83A3B210 卡片类型: MIFARE Classic 1K这里解释下UID的打印逻辑。M1卡的UID是4个字节如果直接Serial.print(uid.uidByte[i], HEX)遇到高位是0的字节时输出会少一个0比如0x0A会显示成“A”这对后续做卡片白名单比对会造成很多莫名其妙的“卡号对不上”问题。所以我在代码里加了一个补零逻辑这是实际开发中非常容易忽略的细节。另一个常见问题是读卡特别慢或者要卡贴得很近才读得到。可以先看看RC522模块的天线线圈上是不是覆盖了异物天线区域要尽量悬空不要紧贴金属面。如果你把它粘在金属门上用读卡距离会急剧缩短这是射频场被金属涡流吸收导致的需要把模块垫高纸板或亚克力再安装。4.2 深入理解M1卡的存储结构与数据块读写读UID只是第一步真正能做门禁、考勤、会员积分系统的关键卡点在于M1卡内部的扇区读写。我尽量讲得简单但有层次。M1卡MIFARE Classic 1K内部有1024个字节分成16个扇区编号0到15每个扇区包含4个块块编号从0到63。每个块可以存16个字节数据。最后一个扇区里的块3是特殊块存放密钥A、访问控制位、密钥B平时不要随意修改这个块否则卡片可能锁死。扇区编号包含块号用途00 ~ 3第0块是厂商数据出厂固化了UID通常不可修改块1~块3可以存数据14 ~ 7可用数据区默认密钥FFFFFFFFFFF28 ~ 11可用数据区.........1560 ~ 63可用数据区默认情况下所有扇区的密钥A和密钥B都是F0 F0 F0 F0 F0 F0十六进制FF FF FF FF FF FF合在一起写作FFFFFFFFFFFF。用默认密钥就能对卡片进行读写操作。要是遇到读取失败先检查这张卡是不是被改过密钥网上有“解密”工具针对不知密钥的卡但我不建议花精力去搞这个买新卡几块钱一张测试阶段不要折腾。读写一个数据块的代码逻辑是先验证当前扇区的密钥密钥匹配后可以调用MIFARE_Write或MIFARE_Read来写或读一个16字节的块。我直接给出一个写入示例把16个字节的数据写到第4块即扇区1的第0个块。#include SPI.h #include MFRC522.h #define RST_PIN 22 #define SS_PIN 5 MFRC522 mfrc522(SS_PIN, RST_PIN); // 要写入的16字节数据比如内部ID、余额、权限等级等 byte dataBlock[16] { 0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, 0x10, 0x20, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00 }; 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(); // 默认密钥FFFFFFFFFFFF6字节 MFRC522::MIFARE_Key key; for (byte i 0; i 6; i) { key.keyByte[i] 0xFF; } byte block 4; // 要写入的块号 byte buffer[18]; byte size sizeof(buffer); // 第一步使用密钥A验证块所在的扇区1 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; } // 第二步写入16字节数据到块4 status mfrc522.MIFARE_Write(block, dataBlock, 16); if (status ! MFRC522::STATUS_OK) { Serial.print(写入失败: ); Serial.println(mfrc522.GetStatusCodeName(status)); } else { Serial.println(写入成功); } // 第三步读回来验证一下写入的数据 status mfrc522.MIFARE_Read(block, buffer, size); if (status ! MFRC522::STATUS_OK) { Serial.print(读取失败: ); Serial.println(mfrc522.GetStatusCodeName(status)); } else { Serial.print(块4数据: ); for (byte i 0; i 16; i) { if (buffer[i] 0x10) Serial.print(0); Serial.print(buffer[i], HEX); Serial.print( ); } Serial.println(); } mfrc522.PICC_HaltA(); }这里有一个非常关键的权限问题验证块4时实际上验证的是块4所在的整个扇区1使用扇区1的密钥块号被内部换算成扇区和块的对应关系。你不要试图用密钥去“解锁”块0、块1、块2、块3之外的区域厂商数据区对普通用户是不开放的。如果你打算把写入的数据做成“余额”之类的数字建议把数字拆成多个字节存入读取时再按你自定义的协议拼回去。直接存整数到一个byte会有溢出问题这个做过数据流的都知道不展开强调了。4.3 用外部中断做刷卡事件触发进阶玩法热搜词里有“esp32外部中断实战”这里我也提一下。我们上面写的代码用的是轮询方式loop里不断检测CPU一直在跑。如果你做的是低功耗门锁项目这种写法会让电池很快耗尽。更好的做法是让RC522通过IRQ引脚通知ESP32ESP32进入深度睡眠等到有卡靠近时IRQ引脚产生下降沿ESP32被外部中断唤醒。不过这里有个坑MFRC522库默认不输出中断信号需要手动操作模块的CRG寄存器打开天线和中断使能。而且RC522模块的IRQ输出是低电平有效接到ESP32外部中断时要用INPUT_PULLUP模式。完整的低功耗代码涉及多个寄存器设置我建议你先把轮询版学明白第二篇再专门讲功耗优化版本。5. 常见问题与排查技巧实录这里把我踩过、以及帮别人排查过的最典型的坑汇总一下都是真实案例。5.1 读不到卡或读卡距离极短排查步骤按优先级排序检查RC522模块供电是不是3.3V量一下模块电源引脚检查SPI四根线有没有接错SCK、MOSI、MISO的顺序错了会导致模块初始化成功但读不到卡把模块天线线圈和卡之间隔一层薄塑料或纸片如果读卡距离突然变远说明线路有微短路或者电源纹波太大确认卡是13.56MHz的高频卡不是125kHz低频ID卡。放在手机NFC的读卡区看看手机识别不识别就能判定如果模块是焊接在洞洞板上的检查天线附近焊点有没有虚焊、锡桥。5.2 初始化失败或Serial打印版本号为0这个我在测试中遇到过两次一次是因为RST引脚被占用另一次是SPI.begin()没有放在PCD_Init()前面。务必保持顺序SPI.begin()然后mfrc522.PCD_Init()。另外如果你把SS_PIN接到了GPIO0、GPIO2这些悬空会导致上电异常或下载异常的引脚初始化失败的概率会增大建议直接换成GPIO5或GPIO21。5.3 一张卡插着读的很好换一张就读不到大概率是卡片类型不同。比如原来的卡是S50新买的卡是NFC标签类型中的NTAG213虽然同样是13.56MHz但RC522对NTAG系列的支持并不好。买卡时认准“M1白卡”或者“MIFARE Classic 1K”字样的兼容性最稳。5.4 写入数据后刷卡读取回来的数据不对先确定是不是访问控制位的问题。默认M1卡的块3访问控制位是78 77 88允许用密钥A和B读写数据块但如果你拿一张写保护过的卡或者被改过控制位的卡读取会返回错误。建议在批量写卡之前先读一次块4和块5的数据确认一下里面是不是默认的0x00全空。要是里面已经有其他系统写入的数据可能已经改过密钥你的写入操作会失败。另外要注意的是写入的数据长度是16字节不能多也不能少。如果你写的是字符串一定要补齐到16字节。很多人把字符串直接转byte数组往里塞长度超过16就写失败这是我看到过的最高频错误。5.5 ESP32程序上传不了代码RFID项目本身不涉及复杂引脚但ESP32的上传过程确实不稳。除了前面说的按住BOOT键方法还有几个实用技巧拔掉所有连接到板子上的杜邦线再上传某些引脚接错会影响下载模式串口监视器要关闭换USB线、换USB口优先用主机后面的直连口不要接USB集线器用esptool工具看下flash是否被占满如果程序太大导致分区溢出也会出现上传不出来又报不明确错误的情况。6. 从刷卡到实用系统的关键提升6.1 把卡号变成可管理的白名单读到了UID下一步自然是和“预期卡号”做比对。在白名单逻辑里建议先把卡号存成字符串再用字符串比对。我写代码时习惯把读到的4字节UID拼成字符串String uidString ; for (byte i 0; i mfrc522.uid.size; i) { if (mfrc522.uid.uidByte[i] 0x10) uidString 0; uidString String(mfrc522.uid.uidByte[i], HEX); } uidString.toUpperCase();这样处理的好处是配置白名单时可以直接写“83A3B210”不用拆字节维护起来清晰很多。后面做门禁的时候只需要把白名单存到一个数组或SD卡里比对通过后给继电器一个高电平触发信号就行。6.2 融入WiFi把刷卡记录上报到服务端ESP32和Uno最大的区别是自带WiFi和蓝牙这让RFID项目能直接联网。我在自己的考勤项目里做了这样一个流程刷卡后ESP32把UID和时间戳通过MQTT协议发给服务器服务器写入数据库前端展示考勤记录。硬件上其实只需要在代码里加WiFi连接和PubSubClient库RFID部分完全不用动。如果你们学校或公司已经有C#写的考勤系统后端可能更希望收到HTTP POST请求ESP32发JSON到指定接口也行。这里给一个建议不要急着在单片机里写复杂的数据结构化逻辑单片机的强项是采集和上报业务处理交给后端。你在ESP32里只做“读到卡号-上报字符串-等待服务器返回”整个系统会稳定很多。6.3 低功耗设计的思路做电池供电的刷卡门锁时默认烧录后的ESP32空转电流就有100多毫安电池撑不了几天。有几个思路读卡期间才给RC522模块供电用MOS管做开关平时模块断电ESP32进入light sleep用定时器或外部中断唤醒降低CPU主频到80MHz关掉WiFi用深度睡眠加RTC定时唤醒每隔几百毫秒给RC522供电一次探测有没有卡。但讲句实话纯靠ESP32深度睡眠做刷卡门锁还是有挑战的因为启动时间和RFID模块上电时间加起来差不多要1到2秒用户体验会稍差。稍微妥协一点的做法是让ESP32保持light sleep功耗大概10到20mA虽然不算极低但比全速运行好很多。7. 我实验过程中的一些体会我最早接触RFID是给公司做一个刷卡取电的测试台最初也以为RFID复杂接触后发现把硬件讲透了它就是个“通过无线电波读身份证号码”的小设备。RC522模块的读卡代码在Arduino生态里有成熟的封装真正需要花时间的是理解卡片的数据结构、密钥体系和SPI时序。最后分享一个小技巧调试初期不要把RC522模块直接放在金属电脑机箱旁边或者带金属屏蔽的桌面上测试。我一开始放在笔记本旁边读卡距离只有1厘米左右我把电脑合上、抬高模块后读卡距离立刻恢复到3厘米左右。这不是玄学13.56MHz的射频场对金属很敏感场地环境会直接影响测量结果。先找个空旷位置确认模块本身没问题再考虑放到实际安装位置调优。如果你按这个教程把卡号读出来了接下来就可以放心去做门禁、考勤、储物柜控制这些扩展了。RFID这套东西最难的门槛就是第一次接线上电的那一个小时跨过去之后你会觉得万物都可以刷卡。
返回列表