STM32+RC522读取MIFARE 1K卡排坑实录

项目地址github.com/xbronze/rfi...

STM32+RC522读取MIFARE 1K卡:从UID到加密读写的"排坑"实录

前言

最近在做一个基于STM32F103C8T6和RC522的RFID门禁系统项目。由于使用现成的封装库,本以为读取UID、完成扇区密码认证和读写是分分钟的事,结果却在"认证失败"和"写块只写进一个字节"的泥潭里挣扎了好久。

本文将复盘整个调试过程,重点解析 ISO14443A 协议的状态机流转 、硬件CRC的陷阱 以及 底层收发时序截断 这三个经典大坑。如果你也在用 RC522 调 MIFARE 1K (M1) 卡,希望这篇避坑指南能帮你节省几个小时。


实验现象与硬件环境

  • 硬件:STM32F103C8T6 最小系统板、RC522 模块(3.3V供电)、MIFARE Classic 1K 白卡。
  • 软件:STM32CubeMX + Keil HAL库,串口波特率 115200。
  • 目标 :读取卡号 UID -> 使用默认密钥 FFFFFFFFFFFF 认证 -> 读块 -> 写块 -> 读回验证。

坑点一:UID能读出,但死活认证不过(Auth Failed)

现象 :寻卡、防冲突获取 UID 一切正常,但调用 MIFARE_Auth 时始终返回失败。起初怀疑是卡被加密了,用手机 NFC 软件(MCT)一测,发现卡确实出厂默认密钥 FFFFFFFFFFFF,根本没加密!

原因与解决: 根据 ISO14443A 协议,卡片的状态机是严格递进的:

  1. REQA (寻卡) -> 卡片进入 READY 态。
  2. Anticoll (防冲突) -> 获取 4 字节 UID,卡片依然停留在 READY 态。
  3. SELECT (选卡) -> 发送 UID 给卡片校验,卡片进入 ACTIVE 态。
  4. AUTHENT (认证) -> 只有进入 ACTIVE 态后,卡片才接受密码认证!

很多简陋的开源库省去了 SELECT 步骤,导致卡片一直处于 READY 态,你发认证指令它当然不理你。我们需要补上选卡逻辑:

c 复制代码
// 选卡函数
uint8_t RC522_Select(uint8_t *serNum)
{
    uint8_t status;
    uint16_t unLen;
    uint8_t buffer[7];
    uint8_t i;

    buffer[0] = 0x93;       /* Select 指令 */
    buffer[1] = 0x70;       /* 选卡 */
    for (i = 0; i < 4; i++) {
        buffer[2 + i] = serNum[i];
    }
    /* 计算 BCC(UID 4字节异或) */
    buffer[6] = serNum[0] ^ serNum[1] ^ serNum[2] ^ serNum[3];

    RC522_WriteReg(BitFramingReg, 0x00);
    status = RC522_ToCard(PCD_TRANSCEIVE, buffer, 7, buffer, &unLen);
    return status;
}

坑点二:加上选卡后,程序彻底卡死或通信超时

现象 :加上 Select 指令后,不仅认证没过,连串口都不打印信息了,或者打印 Select failed。

原因与解决 : 这涉及两个致命细节:CRC校验的开启时机 与 SPI通信速率。

  1. 硬件 CRC 陷阱 :在 ISO14443A 中,寻卡(0x52)和防冲突(0x9320)是不带 CRC 的;但是选卡(0x9370)、认证(0x0E)、读写(0x30/0xA0)必须带 CRC。如果你的代码全局开启或关闭 CRC,都会导致通信失败。
  2. SPI 速率过高:用面包板和杜邦线连接时,如果 SPI 速率跑到 9MHz,在带加密和 CRC 的复杂数据交换中极易发生时序错位(丢帧)。

解决思路:

  • 降低 SPI 速率 :在 CubeMX 中,将 SPI1 的 Baud Rate Prescaler 从 8 改为 64 或 128(降至 1MHz 左右,非常稳定)。
  • 动态控制 CRC :在底层发送函数 RC522_ToCard 中,根据指令动态修改 TxModeReg 和 RxModeReg。
c 复制代码
// RC522_ToCard 函数中的 CRC 动态判断
if (command == PCD_AUTHENT) {
    RC522_WriteReg(TxModeReg, 0x80); // 认证开启发送 CRC
    RC522_WriteReg(RxModeReg, 0x80); // 认证开启接收 CRC
} else if (command == PCD_TRANSCEIVE) {
    // 寻卡和防冲突不需要 CRC
    if ((sendData[0] == 0x93 && sendData[1] == 0x20) || sendData[0] == 0x52 || sendData[0] == 0x26) {
        RC522_WriteReg(TxModeReg, 0x00);
        RC522_WriteReg(RxModeReg, 0x00);
    } else {
        // 选卡和读写需要 CRC
        RC522_WriteReg(TxModeReg, 0x80);
        RC522_WriteReg(RxModeReg, 0x80);
    }
}

坑点三:认证和读块成功,但写块只写进了第一个字节

现象 :认证通过,读块数据全 00 正常;调用写块函数返回成功,但读回的数据却只有首字节变成了 32,后面全是 00(例如写入 32 30 32 33...,读回 32 00 00 00...)。

原因与解决: 这是 RC522 硬件底层的一个"时序截断"Bug。 MIFARE 卡的写块协议是半双工的:

  1. 主机发 0xA0 + 块地址 (2字节)。
  2. 卡片回复 ACK。
  3. 主机紧接着发 16字节数据。
  4. 卡片回复 ACK。

当我们用底层函数一次性把 18 字节全部塞进 FIFO 时,RC522 在发完前 2 个字节后收到了卡片的 ACK,此时中断标志位 RxIRq 置位。代码误以为发送已经结束,提前打断了发送过程,导致后面的 15 个数据字节全被丢弃。

解决思路 : 在底层等待中断的逻辑中"打个补丁":如果是写块指令,不等待接收中断,而是强等待发送完成中断(IdleIRq = 0x10),确保 18 字节全部吐完。或者,最稳妥的做法是将写块拆分为两次发送:

c 复制代码
// 最稳妥的写块函数实现(拆分法)
uint8_t MIFARE_WriteBlock(uint8_t blockAddr, uint8_t *data)
{
    uint8_t status;
    uint16_t unLen;
    uint8_t buffer[2];

    // 1. 发送写块指令和地址 (0xA0 + Addr)
    buffer[0] = 0xA0;
    buffer[1] = blockAddr;
    RC522_WriteReg(BitFramingReg, 0x00);
    status = RC522_ToCard(PCD_TRANSCEIVE, buffer, 2, NULL, &unLen);
    if (status != MI_OK) return status;

    // 2. 发送 16 字节数据
    status = RC522_ToCard(PCD_TRANSCEIVE, data, 16, NULL, &unLen);
    return status;
}

最终的完美输出

经过上述三道"手术",重新编译烧录。刷卡后,串口终于打出了令人极其舒适的完美回显:

text 复制代码
UART Direct Test OK
RC522 Version: 0x92
MFRC522 Version 2.0 detected.

--- New Card Detected ---
Card UID: 02 5B 0E 9E
--- 1. Read Test ---
Auth(default key FFFFFFFFFFFF) OK
Block 4 raw data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
--- 2. Write Test ---
Write block 4 OK
Read back block 4: 32 30 32 33 30 30 30 31 5A 68 61 6E 67 00 00 00 
--- 3. Wrong Key Test ---
Wrong key rejected, auth failed as expected

总结与思考

在使用 RC522 等 RFID 模块时,往往"能读出UID"只是个开始,真正的挑战在于:

  1. 吃透协议规范:ISO14443A 的状态机流转(READY -> ACTIVE)是死规定,不能省步骤。
  2. 硬件调试需降速:面包板长线连接时,SPI 降速往往能解决 90% 的玄学问题。
  3. 注意芯片特性:RC522 在收发非对称长度数据(如写块)时的中断打断问题,需要仔细查阅芯片手册(FIFO 与 中断控制)。
相关推荐
La Pulga1 小时前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
小李不想当小白2 小时前
WDG看门狗(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
La Pulga21 小时前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
Sakura_j_h_1 天前
51 单片机驱动四位数码管:动态扫描、限流电阻与消影的取舍
单片机·嵌入式硬件·51单片机·嵌入式·数码管·动态扫描·限流电阻
用户8351414049492 天前
const的四种位置,含义完全不同
嵌入式·源码
Zevalin爱灰灰3 天前
Linux入门第二部分(ARM裸机篇) 第二章 Cortex-A7 MPCore架构与ARM汇编基础
linux·汇编·arm开发·嵌入式
Zwawa3 天前
串口命令如何从字符串长成协议:帧头、长度、校验和与状态机
c语言·单片机·嵌入式
小李不想当小白3 天前
BKP备份寄存器与RTC实时时钟(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
La Pulga3 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos