简介:基于STM32与RC522射频模块的IC卡增删改查工程,主要面向嵌入式学习者、毕业设计学生以及有门禁、签到、消费等场景开发需求的工程师。代码基于STM32标准外设库,实现读卡、写卡、修改与删除等操作,配合配套博文中的使用规则(尤其3.2节)可快速上手。资源包共150个文件,大小约3.8MB,以H头文件与C源文件为主,另有Keil工程配置、启动文件、链接脚本、编译输出与README.TXT说明文档,目录结构完整,适合直接打开工程查看或二次开发。目前已有1264人学习下载,代码经过实际测试,关键部分留有注释,接线方式与注意事项集中在README.TXT中。建议使用前先阅读该文件,可减少硬件连接和环境配置错误,更好地理解RC522驱动流程与IC卡读写逻辑。 从毕业设计到真实产品, STM32+RC522 这套组合做IC卡管理的项目,我在不同阶段至少重写过三次。第一次只实现了“读卡号”,第二次加了写卡,最后一次才把真正意义上的“增删改查”跑通:添加新卡、修改卡内权限、删除失效卡、查询卡内数据,整个生命周期都能管理起来。这篇文章就把最后一次的完整经验拆给你,从硬件选型、存储结构、核心代码到排查坑点,按我实际做项目的顺序来聊。

1. 项目整体设计与思路拆解

1.1 为什么选择STM32+RC522这套组合

IC卡刷卡场景的硬件方案很多,有直接上NFC芯片的,也有用PN532的,但RC522依然是目前门槛最低、资料最全、性价比最高的选择。一块RC522模块十几块钱,内置13.56MHz RFID读写控制,支持Mifare系列卡片(也就是最常见的M1卡),平时门禁、打卡、图书借阅系统里遍地都是。

STM32则胜在资源够用、生态成熟。一个中低端的STM32F103C8T6,跑RC522 SPI通信、处理卡片数据、驱动屏幕或者继电器,绰绰有余。ST的固件库和HAL库资料非常多,网上能搜到大量可参考的工程模板。如果你手里已经有STM32开发板,整套硬件成本可以压在30块以内,特别适合快速验证功能。

我选择这套组合还有一个更实际的原因:RC522的通信协议和驱动代码非常稳健,它在 SPI 接口下可以跑到10Mbps,完全满足M1卡读写的速率需求。相比I2C和UART,SPI的信号时序更直观,出问题容易排查。STM32的硬件SPI或者软件模拟SPI都能稳定驱动,后期做产品化也不会有性能瓶颈。

1.2 业务层增删改查的定义

很多人提到RC522就只会“读卡号”,实际上一个能真正落地的IC卡管理系统,“增删改查”是四类不同性质的操作:

  • 增:分配一张新卡,往卡片的用户数据区写入卡编号、用户身份、权限级别、有效期等信息,同时把卡号登记到设备或者后台数据库。
  • 删:卡片丢失或离职注销,需要将卡内身份数据抹除或写入“注销”标记。
  • 改:卡片权限调整、续期、余额变动,需要重新写卡内特定扇区的数据。
  • 查:刷卡瞬间,读取卡号并校验卡内数据,决定是否开门、是否通过;也用于管理端检查一张卡当前写了什么内容。

这四个功能单独实现都不难,但合在一起,你需要设计好卡片存储布局、读写流程、错误处理机制。这也是这个项目最有意思的地方,它不是一个单纯的“驱动移植”,而是一个小型的嵌入式数据管理系统。

1.3 软硬件准备清单

我的实验环境如下,你可以直接照搬:

  • STM32最小系统板:STM32F103C8T6蓝色板(也可以换其它型号,只要有一个SPI接口)
  • RC522射频模块:某宝最常见的13.56MHz模块,板上带S50白卡
  • ST-Link V2下载器:用于烧录和调试
  • Keil MDK5或者STM32CubeIDE:我用的Keil 5 + 标准外设库,也推荐你用STM32CubeMX生成HAL库工程
  • 若干杜邦线、一个5V/3.3V可调电源

软件方面,RC522驱动代码我直接基于经典库移植,稍微改一下SPI底层和复位引脚定义就能用。如果你用CubeMX,直接把SPI配置为主模式,速率设置为1MHz左右最稳,然后在代码里调用驱动API即可。

2. 上手前的必懂基础:M1卡存储与RC522通信

2.1 M1卡的存储结构决定了你的设计

Mifare Classic 1K卡(S50)有1KB EEPROM,分为16个扇区,每个扇区4个块,每个块16字节。注意几个关键规律:

  • 扇区0块0是厂商块,出厂写入UID、校验位和厂商数据,默认只读,你在设计卡片格式时必须避开它。
  • 每个扇区的块3是特殊块,存放密钥A、访问控制位、密钥B。密钥默认通常是 FFFFFFFFFFFF ,但是一旦你改了访问控制位,这个扇区可能永远无法再写入。
  • 中间块(块1、块2,以及扇区15的块3等)可以用来存业务数据,比如卡号、姓名缩写、余额、权限代号。

我实际使用的数据布局是:扇区1的块1写4字节卡号+4字节用户ID+4字节权限标志,块2写有效期时间戳;扇区2用来写余额或者扩展信息。这样的分布方便以后扩展,也不影响M1卡默认的认证逻辑。

2.2 RC522的SPI链路与初始化要点

RC522模块引出6个有效引脚:SDA(片选)、SCK、MOSI、MISO、RST、IRQ。最常见的接法是SPI1:

STM32引脚 RC522模块引脚
PA4 SDA(CS)
PA5 SCK
PA6 MISO
PA7 MOSI
PB0 RST
3.3V VCC
GND GND

IRQ引脚在查询式轮询场景下可以不接,但如果你要做低功耗唤醒,需要接一个外部中断引脚。

初始化流程分为模块复位和天线开启两步。模块复位需要给RST引脚一个大于100us的低电平脉冲,再拉高;然后写RC522的CommandReg寄存器,执行软复位;最后打开天线。ST官方的代码模板里这部分封装成 RC522_Init() ,你只需要保证SPI时钟和GPIO配置正确。

2.3 “增删改查”映射到底层操作流程

  • 查: PCD_Request() 发起寻卡 → PCD_Anticoll() 防冲突拿到UID → PCD_Select() 选卡 → PCD_AuthState() 认证目标扇区 → PCD_Read() 读取块数据。
  • 增:寻卡选卡后,认证目标扇区 → PCD_Write() 写入用户信息 → 再读回来校验一遍。
  • 改:与增类似,只是写入的内容不同,可能是修改权限字节、改余额,甚至改写密钥。
  • 删:有两种做法,一是直接把扇区数据全部写成 0x00 ;二是保留数据但将状态位改成“已注销”。第一种干净,但无法追溯;第二种适合有审计需求的场景。

这四个操作看起来是十几个API的组合,真正的复杂度在于处理认证失败、写卡不完整、卡片异常拔出等场景。

3. 核心实现:从驱动移植到四个功能落地

3.1 驱动移植与初始化代码

我从网上下载过很多版本的RC522库,最终整理下来只需要5个头文件加2个C文件: main.c 、 rc522.c 、 rc522.h 、 spi.c 、 spi.h 。核心的读写寄存器函数如下:

void WriteRawRC(uint8_t reg, uint8_t val) {
    CS_LOW();
    SPI_WriteByte(0x80 | (reg << 1)); // 写命令:最高位置1
    SPI_WriteByte(val);
    CS_HIGH();
}

uint8_t ReadRawRC(uint8_t reg) {
    uint8_t val;
    CS_LOW();
    SPI_WriteByte(reg << 1);          // 读命令:最高位为0
    val = SPI_ReadByte();
    CS_HIGH();
    return val;
}

注意SPI读写这里有个容易踩的坑:MISO和MOSI是同一根线上分时复用,主机发出寄存器地址后要等一个时钟周期才能读数据,所以读操作实际上要发两次SPI传输。如果你用硬件SPI,务必在发送地址后调用一次 SPI_Receive ,否则读到的是上一个字节的残留数据。

初始化代码我推荐放在 main 最前面,并且加入返回状态判断:

if (RC522_Init() != MI_OK) {
    printf("RC522 init failed!\n");
    while(1);
} else {
    printf("RC522 init OK!\n");
}

然后开一个循环轮询寻卡。

3.2 “查”的实现:寻卡、防冲突与选卡

查询功能是整个系统最频繁的操作。经典的寻卡流程是:

uint8_t reqMode = PICC_REQALL;
uint8_t tagType[2] = {0};
uint8_t uid[5] = {0};
uint8_t status;

status = PCD_Request(reqMode, tagType);      // 寻卡
if (status != MI_OK) { /* 无卡,直接返回 */ }

status = PCD_Anticoll(uid);                  // 防冲突,得到4字节UID+1字节校验
if (status != MI_OK) { /* 处理冲突 */ }

status = PCD_Select(uid);                    // 选卡,返回卡容量类型

PCD_Request 模式有两种: PICC_REQALL 会唤醒感应区内的所有卡, PICC_REQIDL 只唤醒处于“空闲状态”的卡。如果你做过连续刷卡测试会发现,同一张卡读完一次后如果不执行休眠( PCD_Halt() ),第二次 PICC_REQIDL 可能找不到它。这是因为M1卡协议状态机还停留在“激活状态”。所以我在每次读取完数据后都会调用 PCD_Halt() 让卡片回到空闲状态,下一次寻卡才稳定。

拿到UID之后,把4字节UID转成整数或者字符串打印出来,这就是最流行的“查卡号”功能。但真正能用的查询,还需要读取卡片业务扇区的数据,这就涉及下面的认证和读块。

3.3 “增”的实现:写入新卡并校验

添加新卡时,我会先把卡放到感应区,先完成寻卡选卡,然后执行块认证:

uint8_t key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t blockData[16];

// 写扇区1块1,先用密钥A认证
status = PCD_AuthState(PICC_AUTHENT1A, 4, key, uid);
if (status != MI_OK) {
    printf("Auth block4 failed\n");
    return;
}

memset(blockData, 0, 16);
blockData[0] = (uint8_t)(cardNo >> 24);
blockData[1] = (uint8_t)(cardNo >> 16);
blockData[2] = (uint8_t)(cardNo >> 8);
blockData[3] = (uint8_t)cardNo;
blockData[4] = 0x01;  // 权限标志
status = PCD_Write(4, blockData);
if (status != MI_OK) {
    printf("Write block4 failed\n");
    return;
}

写入完成后,我会再调用 PCD_Read(4, blockData) 把数据读回来,和写入内容逐字节比对。这一步很重要,因为M1卡片在接近天线临界距离时可能出现“写入时报成功,实际数据没写完整”的情况。CRC校验和回读校验是写卡最稳妥的保障。

每次写一个扇区前都要重新认证,而且认证的密钥必须和卡片当前扇区的密钥一致。如果某天你改了该扇区的密钥A,旧程序就不能继续读写了,这也是大家经常犯迷糊的地方。

3.4 “改”的实现:修改扇区数据与权限

“改”本质上和“写”是一样的操作,但我在实际项目中会特别控制几个设计细节:

一是块地址和密码的组合。修改数据时,认证明细要和写入块在同一扇区。例如扇区1的块1地址是4,块2地址是5,认证时用的都是扇区1的密钥。如果你认证的是块4,然后尝试写块5,会因为密钥不匹配或者访问权限不对而失败。

二是访问控制位。M1卡每个扇区块3里有三位访问控制位,决定了该扇区中每个块是允许读、允许写还是允许用密钥A/密钥B。默认出厂访问控制位允许用密钥A读、密钥B写。如果你没有深入理解这个表,千万别乱改访问控制位,改错了这个扇区就变成“死区”,连默认密钥都打不开。

三是余额和权限字段最好加个简单的校验码。我在数据块最后一个字节放前面几个数据字节的异或校验值,读取时先做校验,能过滤掉脏数据。比如卡片在写入过程中被拿开,数据可能是一半新一半旧,校验码不一致时就判定“卡数据异常”,提示重新写卡。

3.5 “删”的实现:清空数据与逻辑注销

删除操作我推荐两层设计:

  • 物理删除:把整个用户数据扇区的所有块写成 0x00 。注意不要写扇区0块0,那是UID区,即使想写也写不进去。
  • 逻辑删除:把权限标志字节改成 0x00 或 0xEE ,程序读取时会识别为“已注销”。

我在门禁系统里用的是逻辑删除,原因是管理员需要知道这张卡曾经发给过谁。物理删除后卡片还能继续发给人用,但没有历史记录;逻辑删除后可以保留发卡记录、注销记录,同时卡片本身也能再次发放。

删除操作同样需要先认证再写块,认证失败时要给出明确提示,不要假装删除成功。

4. 实操中的常见问题与排查技巧实录

4.1 读不到卡?先查这5个地方

“卡放上去了,程序一直显示没有检测到卡”是我最常被问到的问题。按照我的排查顺序:

  1. 确认供电。RC522模块需要3.3V,部分模块板载3.3V稳压芯片可以接5V,但建议直接接3.3V,避免模块发热或电平异常。
  2. 确认SPI引脚接线。MISO/MOSI接反是最常见的错误,因为很多卖家标注的MOSI是对模块而言的,不是对STM32的。
  3. 确认RST引脚。RC522硬复位引脚拉低时,芯片处于复位状态,无法工作。初始化时一定要先拉低再拉高,程序中也要检查是否误配置成低电平。
  4. 确认天线周围没有金属。金属板会屏蔽射频信号,感应距离会骤降到1cm以内甚至完全无响应。
  5. 确认卡片类型和频率。RC522只支持13.56MHz的ISO14443A卡,部分老式125kHz低频卡是读不出来的。

还有一种隐蔽情况:你用杜邦线连接时,杜邦线过长或接触不良,SPI通信时序不稳定,会导致寻卡命令发出去但回包错误。这种情况我会在RC522_Init后打印 TestRegister 的值,正常读出来是 0x92 左右,如果是 0xFF 或 0x00 就说明SPI通信已经挂了。

4.2 认证失败:别急着怀疑代码

PCD_AuthState 返回 MI_ERR 时,90%是密钥或访问权限问题。常见原因有三个:

  • 密钥不对。新卡出厂默认是 FFFFFFFFFFFF ,但有些二次发行卡默认是 A0B0C0D0A1B1 。用错了自然认证失败。
  • 认证的扇区已经被改过访问位。之前没搞清楚就乱写,把访问控制位改成了禁止读写的模式。这种情况只能换一张新卡。
  • 卡片没选对上。某些M1卡在 PCD_Anticoll 和 PCD_Select 之后,必须马上认证,中间不要插入其他命令;插入打印调试代码有时会拖慢时序,导致卡片状态丢失。

排查认证失败,我建议把每个步骤的返回值都打印出来。寻卡OK、防冲突OK、选卡OK、认证FAIL,这样定位就很快。

4.3 连续刷卡时偶发无响应

实际项目里最影响体验的是:第一次刷卡正常,第二次刷卡偶发无响应,第三次又好了。这通常是因为上一次读卡后卡片没有回到IDLE状态,或者STM32的轮询间隔太短,卡片还没移走就发起了新一轮寻卡。

解决办法很简单,每次读卡后调用 PCD_Halt() + PCD_StopCrypto() 。前者让卡片休眠,后者清理当前认证状态。另外不要让程序一启动就疯狂刷寻卡,加一个50ms的延时再循环,我实测这个方案可以把识别成功率做到99%以上。

4.4 STM32下载与调试的常见报错

做这个项目时我还遇到过一个特别头疼的问题:Keil里点下载,报 error: no stm32 target found! ,不是代码问题,是连接问题。排查顺序:

  1. ST-Link和板子接线是否牢固,3.3V、GND、SWDIO、SWCLK四根线,SWDIO别接到PB3(那是JTDI,不是SWDIO)。
  2. 板子是否独立供电。有些蓝板通过ST-Link供电不稳定,下载前给板子接上外置3.3V电源。
  3. 芯片是否开了读保护。STM32被设置了RDP后,SWD会连接不上,需要用ST-Link Utility先解除读保护,但解除时会擦除Flash。
  4. 确认Keil里Debugger选项选择的不是CMSIS-DAP,而是ST-Link Debugger,并勾选 Reset and Run 。

另外,如果你用的是标准外设库和CubeMX混合生成的工程,注意RC522的SPI引脚不要和ST-Link的SWD引脚冲突。比如部分开发板把PA9、PA10用作串口,PA13、PA14用作SWD,这些不冲突,但如果把RC522的CS引到PA13或PA14,就会导致下载异常。所以我上面推荐的PA4-PA7引脚组合,都是避开下载调试引脚的,比较安全。

5. 项目扩展与个人经验

5.1 从demo到产品的安全升级路线

RC522配合M1卡做demo没问题,但要做到产品级,至少要考虑以下安全升级:

  • 更换密钥:发卡时用随机数作为扇区密钥,而不是全F默认密钥。建议把UID参与密钥生成,这样即使两张卡的密码算法相同,密文也不同。
  • 数据加密:卡内存储的用户ID、余额等关键字段,不要明文存。用AES散列或XOR混淆都行,能显著提高数据伪造难度。STM32F1系列没有硬件AES,纯软实现AES-128对主频72MHz来说压力不大。
  • MAC验证:对写卡命令增加MAC校验,防止别人用通用读卡器直接改写卡内数据。这是门禁、消费系统上线前的必修课。

5.2 接入上位机和数据库

如果想让“增删改查”变成真正意义上的后台功能,可以在STM32上跑一个简单的通信协议,把卡号和操作结果通过串口发到PC或者Linux板卡上。

我在项目里用的是固定帧格式:帧头 0xAA ,命令字(1字节:读卡/发卡/注销/修改),数据长度(1字节),数据区,CRC16校验(2字节)。STM32收到上位机下发的“发卡”命令后,执行寻卡、写卡、回读校验,再通过串口回传成功或失败状态。上位机一侧的 MySQL 或 SQLite 里,则维护一张用户卡表,包含卡号、姓名、权限、有效期、状态字段。这就是很多门禁系统的经典架构:终端负责卡片读写,后台负责业务管理。

5.3 几点实战心得

第一,RC522调试过程中最不值得浪费时间的地方是SPI模拟。一开始用硬件SPI,就算你暂时不熟,排错也相对简单;软件模拟SPI虽然能用,但一旦波形质量不好,会把问题带向“玄学”。

第二,写卡一定要回读校验。M1卡写块操作没有“擦写均衡”机制,频繁写同一块有理论寿命上限,而且写卡过程意外掉电可能留下半写状态。回读校验和CRC机制是成本最低的保险。

第三,设计数据布局时给未来留好扩展空间。我一开始只写了卡号,后来加权限、加余额、加有效期,被迫调整了整个扇区规划。提前留好块2和块3,后面加字段就不需要迁移老卡。

这个框架跑通之后,你往上面挂继电器就能做门禁,挂OLED屏就能做余额查询机,挂ESP8266就能变成联网打卡设备。从一块空白的RFID芯片到完整的生命周期管理, STM32+RC522 这套方案的价值就在于它足够简单,又足够接近真实产品。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

更多推荐