简介:STM32+RC522刷卡模块是一套面向嵌入式入门者和物联网开发者的RFID读卡参考工程,覆盖从STM32最小系统到RC522射频前端的完整软件链路,核心目标是读取MIFARE系列IC卡的唯一ID并实时显示。压缩包共148个文件,整体约2.78MB,以C源码和头文件为主,包含STM32F10x标准外设库、MFRC522驱动和Keil MDK工程配置,也保留编译生成的.o/axf/hex等文件,可直接打开烧录验证。代码中集成了OLED液晶显示与USART串口打印,便于观察卡号;主流程覆盖RC522初始化、SPI读写、寻卡、防冲突、选卡、读卡以及数据帧解析和CRC校验,并附接线配置与寄存器操作说明。工程目录保留了项目文件、链接脚本和中间产物,适合对照学习从底层寄存器配置到上层驱动调用的完整开发思路,也可作为门禁、考勤或校园一卡通项目的原型参考。该项目已有5339人浏览学习,尤其适合希望快速上手RFID应用或复刻刷卡读卡功能的嵌入式开发者。 手头这个门禁项目刚收尾,趁着印象还深,把STM32+RC522这张刷卡组合的完整玩法整理出来。RC522方案便宜、资料多,校园卡、门禁卡、考勤机里很多都是13.56MHz的M1卡,所以STM32随便连个RC522模块,就是一套能跑的刷卡原型。但真正动手的人会发现,照着网上的例程接线容易,想让刷卡稳定不翻车,后面全是细节。这篇围绕RC522的硬件接线、驱动移植、一次完整刷卡流程和多卡场景里的坑,把可能踩的雷提前排一排。
1. 为什么门禁小项目绕不开RC522
1.1 一片13.56MHz的读卡芯片能做什么
RC522是NXP的ISO/IEC 14443A读卡芯片,工作频率13.56MHz,最常见的配套卡是M1卡,也就是MIFARE Classic 1K。它的工作过程说白了就是:芯片通过线圈产生射频场,给非接触IC卡无线供电,然后按14443A协议完成寻卡、防冲撞、选卡、密钥认证、数据读写。
这个组合能落地的场景不少:
- 门禁、智能锁:读UID白名单,命中后控制继电器开锁
- 考勤、会议签到:记录UID和刷卡时间,可离线缓存
- 实验室器材借还、售货机:鉴权后触发输出
- 毕设、课设演示:这是最常见的用途,也是最容易从零跑通的题目之一
如果你只是想做"读卡号+控制输出"级别的原型,M1卡完全够用。如果目标是NFC手机交互、卡模拟这类功能,RC522就吃力了,那是另一个选型方向。
1.2 为什么不是PN532或其他
很多人选型时会在RC522和PN532之间纠结。我的建议很直接:STM32学习项目、小门禁、毕设选RC522,理由是便宜、协议逻辑简单、资料极多,踩坑了都能搜到答案。PN532支持14443A/B、Felica、NFC双模式,能模拟卡也能读卡,但价格贵、体积大,驱动复杂度也高一个量级。
| 方案 | 支持类型 | 接口 | 价格 | 上手难度 | 适合场景 |
|---|---|---|---|---|---|
| RC522 | ISO14443A,M1/Ultralight | SPI/I2C/UART | 极低 | 低 | 门禁、考勤、毕设原型 |
| PN532 | 14443A/B、Felica、NFC | SPI/I2C/UART | 中高 | 中高 | NFC产品原型、卡模拟 |
| 串口一体读卡器 | 一般只读UID | UART | 中 | 极低 | 不折腾底层,直接拿卡号 |
这里要强调一个容易被忽略的判断标准:可排查性。RC522的寄存器数量不多,通信时序简单,出了问题用万用表、逻辑分析仪都能快速定位。相比之下,一体式读卡器虽然零门槛,但内部黑盒,出了问题你只能换板子。对于想学STM32的人来说,RC522的"透明感"反而是最大的价值。
2. 硬件接线与电气层面的几个坑
2.1 引脚接线,照抄不翻车
RC522模块和STM32的接线,标准接法如下,我用的是SPI1:
| RC522引脚 | STM32引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 必须3.3V,别接5V |
| GND | GND | 共地 |
| RST | PB0 | 复位引脚,GPIO控制 |
| NSS/SDA | PA4 | SPI片选,GPIO或硬件NSS均可 |
| SCK | PA5 | SPI时钟 |
| MOSI | PA7 | 主机输出 |
| MISO | PA6 | 主机输入 |
| IRQ | 不接 | 查询方式不需要 |
两个容易看晕的地方:一是模块上丝印可能把NSS标成SDA,别把它跟I2C的SDA混了,这个SDA在RC522语境里是SPI片选;二是IRQ引脚,官方库用查询方式读寄存器就够了,不需要真接外部中断。新手阶段把IRQ空着,少一根线少一个出错点。
2.2 RC522必须3.3V,供电和连接线别省
这个坑我在新手期踩过,拿5V往VCC一插,以为模块上有稳压电路。实际上很多廉价RC522模块根本没有板载稳压,5V轻则发热、重则烧片,就算侥幸没烧,MISO引脚的输出电平也可能超过STM32的容忍范围,长期用有隐患。建议直接从STM32板的3.3V引脚取电,如果只有5V电源,用AMS1117-3.3稳压后再供电。
另一个问题是杜邦线长度。供电线、SPI信号线尽量短,超过20cm时射频场稳定性和SPI信号都会恶化。射频部分对电源纹波尤其敏感,我习惯在模块供电脚就近并一个10μF电解电容和一个100nF陶瓷电容,花两毛钱能省掉大半的偶发刷卡失败。
顺带说一句,遇到过好多次"刷不了卡"最后发现根本不是RC522的问题,是ST-Link连不上芯片、程序压根没下载进去。STM32开发板上最常见的报错就是no target found,排查时要先确认调试器连接和程序下载正常,再回头查模块。别一上来就怀疑RC522,白折腾半天。
2.3 为什么感应距离只有几厘米还读不到
RC522模块的天线是板载的,正常情况下白卡能稳定在3~5cm距离刷卡。如果你要贴上去才能读,优先检查三件事:
- 天线周围有没有金属:模块不要贴在金属外壳或铜柱附近,13.56MHz的磁场会被金属涡流吃掉
- 供电是不是被拉垮:用万用表量模块VCC,刷卡瞬间电压如果有明显跌落,就是供电不足
- 天线匹配电容:有些模块批次匹配电容参数有差异,感应距离会明显不同。这个问题在原型阶段不用深究,产品化以后才需要网分仪去调谐振点
如果你发现单个模块怎么调都只有1cm距离,大概率不是程序问题,是硬件本身。换一个模块对比测试是最快的排查方法,RC522模块便宜,备两个不心疼。
3. 驱动移植:SPI通信与MFRC522寄存器操作
3.1 数据手册几十个寄存器,实际只盯这几个
RC522内部寄存器很多,但移植驱动的时候,实际频繁操作的也就七八个:
| 寄存器 | 地址 | 作用 |
|---|---|---|
| CommandReg | 0x01 | 下发命令,如发送、软复位 |
| ComIrqReg | 0x04 | 中断标志,判断发送/接收完成 |
| FIFODataReg | 0x09 | 读写发送/接收数据 |
| FIFOLevelReg | 0x0A | FIFO中剩余字节数 |
| BitFramingReg | 0x0D | 控制最后一字节发送位数、接收起始 |
| TxControlReg | 0x14 | 天线开关控制 |
| Status2Reg | 0x08 | 查询MFCrypto1On、忙碌状态 |
| VersionReg | 0x37 | 版本号,通信自检用 |
把这几个寄存器搞清楚,网上任何一个版本的RC522驱动你都能读懂,而不是只会复制粘贴。CommandReg下发命令、FIFODataReg塞数据、ComIrqReg等结果,这三位一体就是RC522驱动的基本套路。
3.2 SPI读写帧的一个关键细节
很多人移植时卡在"SPI读不到数据",问题往往出在地址格式上。RC522的SPI帧里,寄存器地址不是直接写寄存器号,MFRC522库会把寄存器地址左移一位,写操作bit0=0,读操作bit0=1。举个例子,CommandReg寄存器地址是0x01,写的时候发0x02,读的时候发0x82。
如果你自己用CubeMX的HAL库写底层,地址拼接不对,数据写不到目标寄存器,读回来全是0xFF或0x00。SPI模式一般用Mode0,也就是CPOL=0、CPHA=0,串在SCLK上的数据要在正确相位采样。时钟频率不用拉高,RC522模块10MHz以内都稳,STM32的SPI分频到1~4MHz完全够了,瓶颈不在SPI速率,在卡的响应时间。
3.3 移植驱动按这个顺序来
我的习惯是严格分步走,每步都留验证点:
- 用CubeMX初始化SPI1,配置PA5/PA6/PA7/PA4,生成工程
- 实现SPI写字节、读字节两个底层函数
- 实现MFRC522_WriteReg和MFRC522_ReadReg,处理地址偏移
- 实现PCD_Init:软复位、配置定时器、开天线
- 读VersionReg验证通信是否正常
- 实现寻卡/防冲撞/选卡/认证/读写函数
第5步千万别跳。VersionReg读不到,后面调什么都是瞎猜。正常返回值一般是0x92或0x91,读0xBB也是某些国产兼容芯片的正常版本号。读到0xFF是SPI通信失败,读到0x00基本是RST没拉对、电源没到位、片选没工作。
我见过不少同学用标准库例程先跑通,再往HAL工程里挪。这个顺序没问题,但移植时优先关注SPI底层收发函数是否和原来的例程匹配,而不是一上来就调读卡流程。底层通信稳定了,上层逻辑才有意义。
3.4 等待刷卡结果尽量不用固定延时
这是"delay卡死"最常见的源头。有人写的等待逻辑是:
HAL_Delay(10); if (ComIrqReg & 0x01) { // 处理数据 }这种写法在单卡正常时偶尔能过,但卡响应慢一点、天线匹配差一点,数据就丢了。更坑的是把等待写成无条件死等:
while ((ComIrqReg & 0x01) == 0);一旦卡没有响应,程序永远停在这里,整个系统看起来就是卡死了。正确做法是每个等待循环都带超时退出,超时返回错误码而不是原地死等。官方驱动的PCD_WaitForIrq就是这种思路,用计数器判断超时,调用方能明确知道是成功还是超时,便于上层做重试。
固定延时还有一个连带问题:每次读卡前做全芯片软件复位的人不在少数,想着"复位一下更干净"。RC522刚复位时天线场还没稳定,第一次请求经常失败,反而增加了无效等待。正常情况下,初始化一次就够了,后面直接走读卡流程。
4. 一次完整刷卡:寻卡、防冲撞、选卡、认证和读写扇区
4.1 一张M1卡的访问流程不是"读一下卡号"那么简单
很多精简例程把读卡号封装成一个函数,看起来方便,但遇到异常情况就不好排查。完整流程要按协议顺序走:
- Request寻卡:发0x26(REQA)或0x52(WUPA),卡返回ATQA,比如0x4400表示MIFARE Classic 1K
- Anticoll防冲撞:发0x93 0x20,卡返回4字节UID和1字节校验BCC
- Select选卡:发0x93 0x70 + UID + BCC,卡返回SAK
- Auth密钥认证:对目标扇区做KeyA或KeyB认证
- Read/Write:按块读写16字节数据
每一步返回的状态都要检查。我调试时会把每一步打印到串口:REQ OK、ANTI OK、SEL OK、AUTH OK,这样刷不上卡时能看到卡在哪一步,而不是一脸懵。
4.2 关键指令到底发了什么
| 指令 | 实际发送内容 | 返回 |
|---|---|---|
| REQA | 0x26 | 2字节ATQA |
| WUPA | 0x52 | 2字节ATQA |
| Anticoll | 0x93 0x20 | 4字节UID + 1字节BCC |
| Select | 0x93 0x70 + UID + BCC | 1字节SAK |
| Auth KeyA | 0x60 + 块号 + 密钥 + UID | 成功则MFCrypto1On置位 |
| Auth KeyB | 0x61 + 块号 + 密钥 + UID | 成功则MFCrypto1On置位 |
| Read | 0x30 + 块号 | 16字节数据 |
| Write | 0xA0 + 块号 + 16字节数据 | 4位应答 |
| Halt | 0x50 | 卡进入HALT状态 |
这里有个细节:REQA和WUPA都用于寻卡,但REQA只唤醒静止状态的卡,WUPA连HALT状态的卡也能唤醒。多卡场景下WUPA的容错性更好,后面讲排查时会提到。
4.3 扇区、块和默认密钥
M1卡1K容量是1KB,分成16个扇区,每个扇区4个块,每块16字节。第0扇区的第0块由厂商写入UID和厂商数据,出厂后一般不可改。每个扇区的第3块是尾块,存放KeyA(6字节)、访问位(4字节)、KeyB(6字节)。所以实际可存用户数据的是每扇区的块0到块2,一共16×3×16=768字节。
读写时用的是全局块号,不是扇区号。扇区2的块0是全局第8块(2×4+0),扇区3的块1是全局第13块。这个换算搞错,就会把数据写到意想不到的地方去。
出厂默认密钥是FF FF FF FF FF FF。很多学校、公司的读卡器如果没有改过密钥,拿一个RC522就能读,这是M1卡广为人知的安全弱点。如果你的门禁系统只比对UID、不做密钥认证和卡内数据校验,那和裸奔没有区别。产品化至少要改密钥,不要用默认密钥,更不要明文硬编码在生产固件里。
4.4 一个可以直接抄的完整调用流程
下面这段代码不是完整驱动,但逻辑顺序就是实际协议顺序,底层函数用任何一版驱动替换即可:
uint8_t sn[4], sak; uint8_t atqa[2]; uint8_t buf[16]; uint8_t keyA[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t block = 0x01; // 扇区0第1块 if (RC522_Request(0x52, atqa) == OK) { // 寻卡,WUPA方式 if (RC522_Anticoll(sn) == OK) { // 防冲撞,拿到4字节UID if (RC522_Select(sn, &sak) == OK) { // 选卡,确认SAK if (RC522_Auth(0x60, block, keyA, sn) == OK) { RC522_Read(block, buf); // 读16字节 for (uint8_t i = 0; i < 16; i++) { printf("%02X ", buf[i]); } } } } }要特别提醒UID字节序。不同例程打印UID的格式可能不一样,同一个卡在这块板子上显示ABCD,换一块板子变成DCBA,多半是读取顺序的问题。一旦定了格式,后面存白名单、上位机对比都按这个格式来,别混用。
5. 多卡同场与刷不上卡的排查实录
5.1 先泼冷水:RC522不是为"同时刷多张卡"设计的
虽然14443A协议自带防冲撞机制,但RC522模块天线范围小、射频场弱,两张卡同时贴上去很容易撞包,要么只识别出一张,要么这次识别A下次识别B。如果做的是"盘点一批卡"这种需求,RC522不合适,趁早换大天线读卡器或者PN532。
但实际项目里说的"多卡识别",更多是指三种情况:
- 多张卡同时在场时,能稳定识别出目标卡,而不是随机挑一张
- 防冲撞乱码,明明只有一张卡也读出错UID
- 一台设备管理多张授权卡,拿其中任意一张都能通过
这三种情况,RC522配合软件重试和超时控制,是可以规避大部分问题的。
5.2 一张授权卡都读不顺的排查实录
说一个我实际遇到的案例。现象是两张授权卡叠在一起刷,偶尔能开锁,更多时候灯亮一下没反应;单独刷任何一张都正常。如果只看现象,容易怀疑硬件坏了,但排查下来发现不是。
第一步,拿单张卡反复测,排除天线和供电问题。第二步,把读卡流程每一步打串口日志,发现是Anticoll返回乱码。第三步,看驱动代码,Request用的是REQA,两张卡都是静止状态,都回了ATQA,然后防冲撞时两卡的响应撞在一起,Anticoll返回的数据校验不通过,软件直接丢弃。
问题根源是两卡同时进场的时序撞包。解决方式分两层:
- 软件层:把REQA换成WUPA,请求函数加重试机制,第一次防冲撞失败不立即报错,重新Anticoll几次,成功概率大幅提升
- 硬件层:把模块天线位置调整,让两张卡不要完全叠在一起,人为错开进场时序
关键点在于,很多驱动把Anticoll失败当成硬错误直接返回。只要加上"失败→短延时→重试"的循环,多卡场景的体验会明显改善。这个重试不是瞎等,重试间隔取20~50ms,既不会卡顿,也能有效错开卡的随机响应时间。
5.3 读卡慢半拍背后的超时问题
另一个常见现象是卡放上去要等1~2秒才响应。多数原因是等待ComIrqReg的超时时间设太长。RC522的应答时间在毫秒级,超时上限设几十ms足够,没有必要设几百ms。如果每次读卡都要等很久,挨个排查:
- SPI时钟是不是太慢:如果SPI分频系数设得特别大,传输一条命令的时间会被拉长
- 是否每次刷卡前都重新初始化:如前面说的,初始化后天线场没稳定,第一下容易失败
- 上次刷卡后有没有收尾:读卡失败后,要发Halt指令或者让卡回到IDLE状态,否则下一轮Request可能收不到应答。这就是"第一次刷卡成功、第二次要等更久"的直接原因
这种问题难定位,是因为现象像"卡变慢了",实际是软件状态的遗留问题。建议读卡主流程用状态机而不是顺序调用,每次刷卡结束都把状态归位。
6. 从刷卡到产品:白名单、继电器联动和几个教训
6.1 让卡号驱动继电器开锁
产品化的最小模型是:读卡 → 查UID白名单 → 命中拉高GPIO → 驱动三极管 → 继电器动作 → 电磁锁开锁。
继电器驱动有几个必须注意的点:
- 继电器线圈要并联续流二极管,方向接反会打坏三极管。这个二极管的作用是吸收线圈断电时的反向电动势,别省
- GPIO不要直接驱动继电器,中间加S8050之类的NPN三极管,或者用ULN2003
- 电磁锁瞬间电流很大,单独供电,不要和STM32共用3.3V
我在原型阶段被继电器打过一次,续流二极管漏接了,程序跑着跑着就复位。用示波器看电源,继电器动作瞬间有大幅电压跌落。加了续流二极管、把电磁锁电源分开之后,问题消失。这类电源干扰问题在带射频模块的项目里尤其要小心,RC522对电源纹波本来就敏感,继电器再一干扰,读卡失败率直接飙升。
6.2 白名单的存储与安全问题
卡号少可以写死在代码里,但要管理几十上百个卡号,就得存Flash或外部EEPROM。STM32内部Flash的擦写寿命有限,频繁增删卡号建议用外部I2C EEPROM,比如AT24C02,几毛钱一片,256字节到2K容量可选。
存卡号时建议存成ASCII十六进制字符串,而不是裸二进制。好处是导出bin文件后直接能对照排查。用J-Flash或者STM32CubeProgrammer读出Flash内容,一眼就能看到卡号列表,不用写脚本解析。
安全方面再强调一次:不要只校验UID。M1卡的UID是明文的,市面上不少工具能改部分卡的UID。只比对UID,等于门禁没有锁。就算是毕设原型,也建议至少改掉默认密钥,并对卡内数据做简单校验。更严格的做法是对卡内数据做HMAC或AES加密,配合发卡器写入动态数据,但这些属于安防产品的范畴,超出原型阶段的讨论。
6.3 从驱动到工程的其他经验
最后整理几条散落的经验:
- 标准库例程先跑通,再移植到HAL/CubeMX工程。一上来就在HAL工程里手搓寄存器,出问题你分不清是SPI配置错还是RC522时序错
- 国产兼容RC522芯片越来越多,VersionReg可能不是0x92。初始化代码不要写死"必须等于0x92",把0x91、0xBB这类值都当正常,用"读不到0xFF/0x00"来判错
- Keil、VSCode+CMake都能正常开发STM32,工程框架不影响RC522工作。移植驱动时重点看SPI底层收发函数对应关系,这点比IDE选择重要得多
- 收一个10μF和100nF电容进物料盒,凡是带射频的模块都并上去,能解决大量偶发问题
- 调试串口多打日志,把读卡状态机的每一步输出出来,排错的效率比闷头试高很多
最后再分享一个个人习惯:RC522这类读卡模块的偶发问题,十次里有七八次跟供电和天线布局有关,而不是寄存器配置。遇到刷卡时好时坏,先量电压、看天线环境,再回头翻驱动代码。我自己的小门禁项目里,给模块供电并了电容、把天线挪离金属支架之后,读卡成功率从勉强能用提升到几乎百分百。这些硬件层面的细节,往往比纠结代码更值得花时间。
本文还有配套的精品资源,点击获取