简介:面向51单片机开发者的RC522 RFID读写方案资料包,聚焦13.56MHz非接触式通信,适用于门禁系统、智能卡读写器及物联网设备等场景,也可作为电子设计竞赛与课程设计参考。压缩包为zip格式,共0个文件(上游未同步文件清单),大小约91.47MB。内容覆盖SPI硬件接口连接与模式配置、RC522初始化命令框架、ISO/IEC 14443A协议流程、防冲突机制及数据加解密,并围绕读写程序设计、错误处理与调试技巧展开,配有C语言示例代码与工程注释,同时涉及天线设计与调谐、电源管理优化等要点。已有1212人学习下载,适合需要从零搭建RC522读卡器、理解RFID通信全流程并完成实际项目部署的嵌入式开发者。
1. 先聊聊RC522和51单片机的适配性——为什么这个组合经久不衰
RC522是NXP出的一块13.56MHz非接触式读写芯片,支持ISO/IEC 14443A协议,常见的M1卡(S50)、NTAG系列都能读。它本身不复杂,但牵扯到模拟前端、射频发射、协议帧处理,对很多刚接触单片机的人来说,属于那种“照着例程能跑通,但一改需求就抓瞎”的模块。
至于51单片机,说实话它在性能上没有任何优势——8位内核、12MHz晶振都算高的、没有硬件SPI的型号一抓一大把。但RC522和51这个组合在课程设计、电子竞赛、毕设里依然是常青树,热度一直没下来。原因很现实:51的资料多到泛滥,RC522的例程在各大论坛、CSDN、B站上一搜一大把,尤其江科大(某知名教学UP主)的51教程把这类外设讲得极其细致,很多人的入门路径就是“51 + RC522 + LCD1602显示卡号”,做完这个项目基本就把单片机外设通信的基本功练全了。
所以这篇帖子我想从实际做的角度,把RC522在51平台上的完整链路拆开讲清楚,包括硬件连接的坑、SPI模拟时序怎么写、协议栈怎么搭、读卡流程每一步在做什么、多卡识别到底怎么回事,最后附上我自己踩过的几个经典问题。适合正在做相关课设、或者刚学完51想搞个像样外设项目的人参考。
先说一个标准配置:MCU用STC89C52RC,晶振11.0592MHz,RC522模块用SPI接口接,LCD1602用来显示卡号。这套配置网上例程最多,出问题了也好找人问。
2. 硬件接线不能只照着抄——三处最容易翻车的电路细节
RC522模块市面上有两种常见形态:一种是蓝色PCB的模块,天线和芯片做在一块板子上,引脚8个,直接排针引出;另一种是红板,同样集成天线。绝大多数情况我们用SPI模式,因为51读RC522内部寄存器最方便的就是SPI,速度也够。
2.1 SPI引脚映射与接线参考
RC522模块上丝印一般标了SDA、SCK、MOSI、MISO、IRQ、GND、RST、VCC这8个脚,其中IRQ是中断请求输出,51裸机做轮询的话可以不接。下面是我常用的接线:
| RC522引脚 | 51单片机引脚 | 说明 |
|---|---|---|
| SDA | P1.2 | 注意:这里是SPI的片选,不是I2C的数据脚 |
| SCK | P1.3 | SPI时钟 |
| MOSI | P1.1 | 主机输出、从机输入 |
| MISO | P1.0 | 主机输入、从机输出 |
| IRQ | 悬空 | 轮询模式下用不到 |
| RST | P1.4 | 复位脚,低电平有效 |
| VCC | 3.3V | 必须3.3V,不能接5V! |
| GND | GND | 共地 |
很多人第一次接就死在这几个问题上:第一,VCC接了5V,芯片当场发烫或者直接烧掉。RC522的工作电压是2.5V到3.3V,虽然有说法称模块板载了稳压芯片可以接5V,但不同批次板子不一样,最稳妥的做法是用3.3V供电。如果你手里的51开发板是5V系统,需要把板子的3.3V引脚引出来给RC522单独供电。
注意:如果开发板上有AMS1117-3.3这种稳压芯片,直接用它的输出引脚给RC522供电,不要为了省事把VCC接到5V排针上。
2.2 电平匹配问题:3.3V模块和5V单片机怎么共存
RC522的SPI引脚逻辑电平上限约等于VCC,也就是3.3V左右。51单片机IO口输出高电平是5V,直接用5V电平去驱动3.3V器件,长期使用会有烧引脚的风险。
最省事的办法:SPI的MOSI、SCK、SDA、RST这几个51输出的信号线上各串一个1kΩ电阻,分压兼限流。MISO方向是RC522输出到51,RC522的3.3V高电平对51来说足够识别(51的高电平门槛是2.0V左右),可以直连。
还有一种做法是加电平转换芯片,比如TXS0108E、PCA9306,但RC522的SPI速率本身不高,串电阻完全够用,没必要多花钱增加复杂度。
2.3 天线匹配和读卡距离的关系
RC522模块上天线线圈周围通常有配套的匹配电容,这是出厂调好的。若读卡距离太近(小于1cm甚至贴上去才能读),不要一上来就怀疑程序,先看看天线下方的PCB覆铜区域是否被螺丝柱、杜邦线挡住,金属物体靠近天线会严重涡流损耗,导致读卡失效。
我遇到过一种情况:模块用双面胶贴在金属外壳支架上,结果读卡距离从标称的3-5cm缩水到几乎只能贴着读。把模块垫高或者挪开金属之后恢复正常。如果模块附近非要有金属,至少保证天线下方3cm内没有大块金属平面。
3. 软件层面:没有硬件SPI就手搓一个,顺便把协议栈的骨架搭起来
51单片机(尤其是STC89C52)没有硬件SPI,所以全部通信要靠GPIO模拟。其实模拟SPI很简单,核心就是按照RC522的时序要求把电平拉高拉低。
3.1 模拟SPI的读写字函数
RC522工作在SPI从机模式,通信格式很固定:每次传输8位地址 + 8位数据。地址字节的最高位表示方向——高电平是读命令、低电平是写命令。注意地址字节是MSB先行,也就是先发最高位。
// 模拟SPI收发一个字节,MSB先行 unsigned char RC522_Byte(unsigned char byte) { unsigned char i, temp = 0; for (i = 0; i < 8; i++) { // 先送最高位 if (byte & 0x80) { MOSI = 1; } else { MOSI = 0; } byte <<= 1; SCK = 1; _nop_(); // 在时钟上升沿后采样,这里读MISO temp <<= 1; if (MISO) { temp |= 0x01; } SCK = 0; _nop_(); } return temp; } // 写寄存器:地址和数据 void RC522_WriteReg(unsigned char addr, unsigned char value) { SDA = 0; // 片选拉低 RC522_Byte((addr << 1) & 0x7E); // 写命令:最高位为0 RC522_Byte(value); SDA = 1; } // 读寄存器:返回寄存器值 unsigned char RC522_ReadReg(unsigned char addr) { unsigned char value; SDA = 0; RC522_Byte(((addr << 1) & 0x7E) | 0x80); // 读命令:最高位为1 value = RC522_Byte(0x00); SDA = 1; return value; }这里有个细节很多初学的人不理解:为什么地址要(addr << 1) & 0x7E再或上0x80?因为RC522的地址只有7位有效,寄存器地址本身是7位(0x00-0x7F),SPI传输时地址字节需要左移一位,最低位留作方向标志。& 0x7E是为了清掉本来就无效的第0位和最高位,防止地址越界。
3.2 初始化序列:软复位、自检、打开天线
RC522上电后必须先做初始化才能工作,流程分四步:
- 硬复位:RST引脚拉低至少10ms,再拉高。
- 软复位:向CommandReg写入0x0F,命令字(Command)为SoftReset。
- 等待复位完成:轮询CommandReg,读到Command为0x00时说明复位完成。
- 写配置寄存器、开天线:关键寄存器包括TxControlReg(打开TX1/TX2天线)、TModeReg和TPrescalerReg(设置定时器)、RFCfgReg(设置接收增益)。
- 执行自检(可选项):向CommandReg写入0x00进入自检模式,读取BufferReg的值判断芯片是否正常。
标准初始化代码大致如下:
void RC522_Init(void) { RST = 0; delay_ms(10); RST = 1; delay_ms(10); RC522_WriteReg(CommandReg, 0x0F); // SoftReset delay_ms(10); while (RC522_ReadReg(CommandReg) & 0x0F); // 等待复位完成 // 定时器配置:设置TModeReg为0x8D,TPrescalerReg为0x3E,TReloadReg为0x1F/0x30 RC522_WriteReg(TModeReg, 0x8D); RC522_WriteReg(TPrescalerReg, 0x3E); RC522_WriteReg(TReloadReg, 0x1F); RC522_WriteReg(TReloadRegH, 0x30); // 天线配置 RC522_WriteReg(TxAskReg, 0x40); // 强制100%ASK调制 RC522_WriteReg(TxControlReg, 0x83); // 开天线 RC522_WriteReg(RFCfgReg, 0x7F); // 接收增益48dB // 清除中断标志 RC522_WriteReg(Status2Reg, 0x00); RC522_WriteReg(ComIEnReg, 0x00); RC522_WriteReg(DivIEnReg, 0x00); RC522_WriteReg(ComIrqReg, 0x7F); RC522_WriteReg(ModeReg, 0x3D); }注意TModeReg、TPrescalerReg、TReloadReg这三组寄存器共同决定了RC522内部定标器的溢出时间。这个时间很关键,它相当于“一次命令的最长等待时间”,如果设得太短,读卡过程中容易出现超时误报;设得太长,卡不在场时MCU要白等很久。一般例程里的0x8D/0x3E/0x1F/0x30这套组合,实测溢出时间在25ms左右,够用。
4. 重点拆解读卡流程:从寻卡到读扇区,每一步在做什么,数据又是怎么传的
RC522的所有操作本质上是MCU往FIFO缓冲区里填写命令字节,然后启动命令,RC522会把响应数据写回FIFO,MCU再读出来。理解这条链路,比背代码重要得多。
4.1 标准读卡五步协议链
以读取M1卡数据为例,完整流程是:寻卡 → 防冲突 → 选卡 → 三次认证 → 读块。
第一步寻卡(Request/PICC)。MCU向FIFO写入命令字节0x26(Request mode,7字节帧)或0x52(Wake-up mode,7字节帧),然后执行PCD_TRANSCEIVE命令(命令码0x0E),RC522会发送RF场信号,等待卡片应答。卡片返回的ATQA(Answer To Request)是两个字节,比如M1 S50返回0x04 0x00。
第二步防冲突(Anticollision)。当一张以上卡片同时进入射频场时,M1卡的防碰撞机制是以UID的每一位为单位进行逐位仲裁。MCU发送0x93(级联级1)、0x20(防碰撞命令),卡片返回4字节UID(实际是完整UID的前4字节加上BCC校验字节)。
第三歩选卡(Select)。把上一步拿到的完整4字节UID连同BCC发回去,命令是0x93、0x70,卡片会返回1字节SAK(Select Acknowledge),用于告知卡的类型。这里如果返回0x08,说明这是完整UID,不需要级联第二级。
第四步三次认证(Authentication)。M1卡的内存分成16个扇区,每个扇区有4个块,要读某一块数据,必须先对所在扇区进行密码认证。认证命令是0x60(Key A)或0x61(Key B),发送时把扇区号、块地址、6字节密钥一起发给卡片。M1出厂默认密钥是6个0xFF,这也是课程设计里最常用的密钥。
第五步读块(Read)。认证通过后,发送0x30 + 块地址,卡片返回16字节数据。写块类似,发送0xA0 + 块地址,然后跟16字节数据,再接CRC校验(RC522硬件会自动计算,不用MCU管)。
4.2 命令帧在51代码里的落地写法
上面五步每一步都要填FIFO、发命令、查询中断标志、读结果。以寻卡为例:
unsigned char RC522_Request(unsigned char reqMode, unsigned char *tagType) { unsigned char status; RC522_WriteReg(Status2Reg, 0x00); // 清状态 RC522_WriteReg(ComIrqReg, 0x7F); // 清中断 RC522_WriteReg(FIFOLevelReg, 0x80); // 清FIFO // 把寻卡命令写入FIFO RC522_WriteReg(FIFODataReg, reqMode); // 执行Transceive命令 RC522_WriteReg(CommandReg, PCD_TRANSCEIVE); // 置位BitFramingReg,表示最后发送的字节只有7位有效 RC522_WriteReg(BitFramingReg, 0x07); // 等待完成:轮询ComIrqReg的RxIRq位和TimerIRq位 status = RC522_WaitForResponse(); // 读取返回数据,长度一般是2字节 *tagType = RC522_ReadReg(FIFODataReg); RC522_ReadReg(FIFODataReg); return status; }RC522_WaitForResponse是轮询超时函数,核心逻辑是循环检测ComIrqReg寄存器的bit0(RxIRq,接收完成)和bit4(TimerIRq,定时器溢出)。如果读到RxIRq置位说明卡片有响应;如果读到TimerIRq置位就说明超时,直接返回MI_ERR。
一个常被忽略的细节是BitFramingReg = 0x07这行。寻卡命令reqMode(0x26或0x52)只有7位有效,而FIFO本身按字节存储,通过BitFramingReg告诉RC522“最后一个字节只发7位”,这样卡片才能正确识别这是一次完整的7位命令帧。很多移植了例程却始终寻不到卡的人,多半是把这行删了或注释掉了。
4.3 读UID和读扇区的区别
搜RC522资料时经常会看到“读卡号”和“读卡数据”两种说法,这是两码事,一定要分清楚。
读UID不需要认证。UID存在于每张卡的只读区(第0扇区第0块的前4字节),MCU只要做寻卡、防冲突、选卡三步就能拿到。这也是为什么“显示卡号”的例程里看不到认证代码。
读扇区数据就要走完五步,在第0块里还包含厂商信息,自己往扇区写入的数据也存在块1、块2这种数据块里。所以如果你看到例程里有0x60开头的数组,那是认证命令;看到0x30开头的是读块命令;看到0xA0开头的是写块命令。
提醒:M1卡的默认密钥是公开常识(6个0xFF),课设、毕设用默认密钥读写自己的卡完全没问题。但不要尝试对陌生人的卡做暴力枚举密钥或绕过认证的操作,那涉及安全问题,不在本文讨论范围内。
5. 多卡识别的真相:防碰撞算法到底是什么,51上又是怎么实现的
“RC522 多卡识别”是搜索热度很高的词。很多人以为多卡识别就是RC522能同时读好几张卡的数据,其实不是。RFID系统在半双工通信下,同一时刻只能和一张卡完成数据交换。所谓“多卡识别”,指的是多张卡同时进场时,系统能逐个把每张卡的UID读出来,中间不发生冲突或漏读。
5.1 位级防碰撞的工作过程
M1卡的防碰撞是一种典型的二进制搜索算法,RC522和卡片的交互过程是这样的:
第一轮,MCU发送防碰撞命令(0x93、0x20),场上所有卡同时响应,把自己UID的每一位都发出来。如果两张卡的UID在某个bit位上一个发0、一个发1,这个位就会产生冲突,RC522检测到冲突后会把该位置1,MCU从返回的40位数据(32位UID + 8位BCC)里能知道冲突发生在哪个bit。
第二轮,MCU在冲突位处强制指定一个分支,比如要求UID的第N位必须是0,再发一次防碰撞命令。这样只有UID符合要求的那张卡会继续响应,另一张卡会进入静默状态,于是成功分离出一张卡。
第三轮,MCU对这张卡的UID执行选卡操作,完成读取后,对它执行Halt命令(0x50 0x00),让它退场。然后再对剩下的卡重复上述过程。
在51上做这件事的最大开销是时间。M1卡的防碰撞是一个逐位逼近的过程,UID有32位,最坏情况下要做33轮(32位加最后确认)。每轮通信还要等RC522的RF收发时间。实测在11.0592MHz的STC89C52上,完整识别两张卡大概需要50-80ms,这个速度对门禁、考勤这种场景完全够用。
5.2 轮询切换和一卡一读的工程实现
实际工程中更常见的需求是:读卡器不停地扫,任何一张卡靠近都能读到UID,而不是一次性把场内所有卡全读一遍。
做法是主循环里不断执行“寻卡→防冲突→选卡→Halt”这四步,每次循环只处理一张卡。寻卡用0x26(Request,只响应进入场内的卡)还是0x52(Wake-up,可以唤醒处于Halt状态的卡)取决于具体场景。
| 场景 | 寻卡命令 | 原因 |
|---|---|---|
| 门禁,人走卡走 | 0x26 | 卡移出后重新进入时才触发 |
| 多卡轮流刷(比如消费机) | 0x52 | 能唤醒已Halt的卡,连续刷不同卡更顺畅 |
| 一卡一读,防重复刷 | 0x26 + 做标记 | 读完之后记录UID,同一张卡短时间内不重复处理 |
如果你要做“一卡一读”的项目(比如刷一次卡亮一次灯,同一张卡不能连续亮),最简单的做法是维护一个上次UID的变量,每次读到新UID后和上次比对,相同就忽略,不同才执行动作。不要试图在RC522层面彻底禁止卡重复响应——芯片本身的机制决定了卡只要在场上,就能被反复读到,业务逻辑应该在单片机层面解决。
6. 我自己踩过的坑,和几条能让你少走弯路的验证方法
这几年在不少群和论坛里看到新手问RC522的问题,我自己早期也翻过车,挑几个出现频率最高的集中说一下。
6.1 卡片完全读不到,程序卡死在等待响应
排查顺序永远是先硬件后软件。第一步量RC522的VCC是不是3.3V,用万用表测模块上的电压。第二步示波器看SCK引脚有没有时钟波形,没有波形说明模拟SPI代码没跑起来,检查IO口初始化有没有把对应引脚设为准双向口。第三步看SDA片选信号,很多模拟SPI的例程里SDA是P1.2,但有些模块丝印上写的是SDA(其实是SPI的NSS,片选),不要和I2C的SDA搞混。
排除硬件问题后,把初始化代码里的自检打开。RC522有个自检功能:向CommandReg写入0x00,等待自检完成后读BufferReg,如果返回值是0x00 0x22 0x35 0x44 0x36 0x1F 0x35(不同版本芯片略有差异),说明芯片和MCU之间的SPI通信是通的。这个测试比盲改代码高效得多。
6.2 读卡距离只有1cm,怎么调都上不去
先确认天线附近有没有金属,再看供电电流。RC522发射功率不小,如果供电线又细又长,压降会导致天线输出功率不足。把杜邦线换成粗线或者直接飞短线,读卡距离往往能明显改善。
如果还不够,可以小心地调整RFCfgReg的接收增益。默认0x7F对应约48dB,可以试着改成0x9F(约54dB)或0xBF(约60dB),但增益太高会提高误码率,导致读卡反而变慢或不稳定。调这个寄存器没有标准答案,要根据实际环境一点一点试。
6.3 卡片能读到UID,但读扇区数据时报认证错误
这个几乎是模块默认密钥和卡不匹配导致的。很多新买的白卡出厂默认密钥是6个0xFF,但如果有人之前用别的工具改过卡密钥,默认密钥就失效了。解决办法是换一张没改过的M1卡测试,或者用已知密钥的卡验证代码。不要怀疑认证命令写错——0x60 + 扇区号 + 块地址 + 6字节密钥,这个组合反复核对几遍基本不会有问题。
另外特别提醒:「扇区号」和「块地址」不是一个概念。M1卡第0扇区包含块0到块3,扇区号0对应的块地址是0、1、2、3。如果你想读第2扇区的块0,发送的块地址应该是2×4+0=8。这块搞错的话,认证时明明选了扇区2,读块却用了块2,操作自然会失败。
6.4 一个省事的调试思路:先读UID,再做别的
如果你第一次在51上驱动RC522,不要上来就写完整的多扇区读写程序。建议分三步走:
- 先实现寻卡和防冲突,能用串口或者LCD输出4字节UID,这一步通了,说明SPI和基础命令都对。
- 再加上Halt命令,让一张卡只能被读一次,养成“读一次停一次”的习惯。
- 最后加认证和读写块,用一个已知默认密钥的新卡测试。
这样每一层都有明确的验证标准,出问题了能快速缩小范围。不要指望一步到位写完所有代码再调,RC522这类射频外设的定位手段本来就不像I2C传感器那么直观,逐层验证是最省时间的路径。
说了这么多,RC522这套东西真跑起来之后你会觉得其实并不神秘——SPI模拟通信打通了,剩下的就是对着数据手册填寄存器、发命令、读FIFO,和操作I2C、UART外设没有本质区别。真正有价值的经验反而是那些手册上不会写的部分,比如电平匹配、天线周围的金属干扰、M1卡的防碰撞行为,这些东西只有亲手搭过一套硬件、真实卡在手里一张一张试过才会理解。希望这篇帖子能让你少走点弯路,顺利把这颗小芯片跑起来。
本文还有配套的精品资源,点击获取