
简介面向51单片机与RFID学习者的一套RC522实践资料围绕13.56MHz非接触通信与ISO/IEC 14443A标准展开适合做门禁、读卡器、电子标签等课程设计或毕业项目时对照查阅。内容可覆盖RC522与51单片机的SPI硬件连接、通信时序配置、复位初始化与命令框架、卡片检测和防冲突、读写流程、错误处理与调试思路并涉及示例程序、天线调谐及低功耗设计等关键环节。压缩包约91.47MB文件总数与类型明细上游暂未给出目录与文件构成需下载后确认。已有1219人学习可作为从接口联调到读写功能验证的参考材料帮助读者梳理RC522开发中的知识脉络和常见问题排查方向。1. RC522 基于 51 单片机资料从灯能亮到块能读之间那道坎很多人第一次把 RC522 模块接到 51 最小系统上接线没错、程序能烧、电源灯亮着串口却一个字节都不吐。换第二块模块、换第三块结论一样最后往往归到模块是坏的。rc522 这类 13.56MHz 读卡前端跟 51 的组合难点从来不在模块本身而在两个地方一是 SPI 时序靠软件模拟时那几微秒的相位关系二是卡片从应答走到认证通过之间的状态机。这套资料的价值是把已经被验证过的初始化序列、收发流程和时序参数直接摆出来让你不用从数据手册第一百页开始猜寄存器。它适合手上有 51 最小系统、要做门禁或考勤原型又不想在底层时序上耗掉一整周的开发者也适合已经能读到卡号、但卡在读块和换卡认证这一步的人。2. 硬件对位与供电边界先确认这 8 根线没有接反在动代码之前先把物理层的事情钉死。RC522 模块对外一般引 8 个脚SDA有的板子标 NSS 或 CS、SCK、MOSI、MISO、IRQ、GND、RST、3.3V。51 最小系统那边P1 口是最常被拿来做软件 SPI 的因为它紧挨着走线短。但短不等于对下面这三件事必须逐条对过。2.1 引脚映射与软件 SPI 的相位CPOL0、CPHA0 到底怎么落地RC522 的 SPI 是标准四线从机模式空闲时 SCK 保持低电平主机在 SCK 低电平期间改变 MOSI从机在 SCK 上升沿采样反过来从机在下降沿把数据推到 MISO 上主机在上升沿之后读取。换成教科书说法就是 CPOL0、CPHA0。51 没有硬件 SPI 外设只能靠翻转 IO 来凑这个时序于是先放数据再拉高 SCK这一句必须写死在代码里顺序反了就是全零或者全 FF。模块引脚接到 51方向说明SDA / NSSP1.3输出低电平选中一次通信全程拉低SCKP1.0输出空闲低上升沿采样MOSIP1.1输出主机发从机收MISOP1.2输入读之前先把该位写 151 准双向口才能当输入RSTP1.4输出低电平复位上电后要先拉低再拉高IRQ可不接输入查询方式下用不上接了也建议先屏蔽3.3V / GND独立 3.3V电源不要跟 5V 器件共用一条细排线表格里最后一行值得多说两句。51 的 IO 是 5V 电平RC522 的 IO 是 3.3V 域。MISO 这根线是模块往单片机送数据3.3V 高电平对 51 来说通常能识别成高TTL 阈值 2.0V问题不大但 MOSI、SCK、NSS、RST 这四根是单片机往模块打 5V虽然不少模块的输入端带了限流电阻能扛住长期这么用属于赌运气。我一般的做法是四根线上各串一个 1k 电阻成本几毛钱能挡掉大部分意外如果做的是要出货的东西直接上电平转换芯片更稳。2.2 供电与天线读卡距离忽远忽近八成不是代码问题RC522 标称 3.3V 供电静态电流二十几毫安但发射瞬间的峰值能上到一百毫安以上。很多人图省事从 51 板上的 3.3V 排针引一根杜邦线过去那根排针背后可能只是一个几十毫安的线性稳压器寻卡瞬间电压一跌模块内部复位表现就是有时候能读有时候读不到。血泪经验是单独给 RC522 一路 3.3V模块的 GND 和 51 的 GND 一定要共地且走短线如果非要共用在模块电源脚旁边并一个 10uF 电解加一个 0.1uF 瓷片能压住大部分毛刺。天线部分是另一回事。模块自带的 PCB 环形天线是印在板子上的它对面就是读卡距离。天线正上方贴金属、贴电池、贴手机会让它失谐距离从 3cm 掉到几毫米都正常。还有一点常被忽略天线增益是可以用寄存器调的但调大之后电流也跟着涨供电撑不住的时候反而更不稳定。这个取舍放到第 4 章细说。2.3 上电时序与复位VersionReg 是第一个验证点上电之后不能立刻发命令芯片内部还在复位状态。规范的初始化顺序是RST 拉低、延时 10ms 以上、拉高、再延时 50ms然后发软复位命令再延时最后才开始写定时器和天线寄存器。软复位之后所有寄存器回到默认值所以定时器、TxASK、Mode 这些必须放在软复位之后写写在前面会被冲掉。/* rc522_init.c —— 上电与复位顺序不能调换 */ void rc522_init(void) { RC522_RST 0; /* 硬复位拉低 */ delay_ms(20); RC522_RST 1; /* 释放 */ delay_ms(50); rc522_write_reg(Reg_Command, PCD_RESETPHASE); /* 软复位 */ delay_ms(50); rc522_write_reg(Reg_TMode, 0x8D); /* 定时器自动启动停止时也计数 */ rc522_write_reg(Reg_TPrescaler, 0xA9); /* 分频值后面第 4 章算 */ rc522_write_reg(Reg_TReloadH, 0x03); rc522_write_reg(Reg_TReloadL, 0xE8); /* 重装值 1000 */ rc522_write_reg(Reg_TxASK, 0x40); /* 强制 100% ASK 调制 */ rc522_write_reg(Reg_Mode, 0x3D); /* CRC 预设 0x6363标准模式 */ rc522_write_reg(Reg_RxSel, 0x86); /* 内部接收器增益拉高 */ rc522_antenna_on(); /* TxControlReg 置位开天线 */ }这段里唯一值得逐行解释的是最后几行。TxASK 写 0x40 是因为 ISO14443A 要求 100% 调制深度不写这一句有些模块能读到卡号但读不了块Mode 写 0x3D 是把 CRC 初值设成协议规定的 0x6363RxSel 写 0x86 是打开内部接收链路并选用较高的接收增益近距离读卡时这一位影响很明显。写完这一串马上去读 VersionReg地址 0x37正常情况下能读到 0x91 或 0x92读到 0x00 或者 0xFF 就说明 SPI 根本没通先回去查线别往下写业务逻辑。提示把 VersionReg 的读取放在 main 的第一行用串口打出来。这是我每次上手一块新板子的固定动作能省掉后面大量的错误归因。3. 从寻卡到读块把驱动拆成五步单步验证的链路资料包里的驱动代码通常是把寻卡、防冲突、选卡、认证、读写全塞在一起跑通了皆大欢喜跑不通就完全不知道断在哪。我的习惯是把它拆成五个可以独立验证的环节每个环节都有一个明确的输出前一个不通就不看后一个。3.1 寄存器读写原语地址字节最高位那个 1RC522 的寄存器地址是 6 位拼接成 SPI 字节时要做两件事左移一位最低位留空最高位表示方向1 为读、0 为写。所以读寄存器的地址字节是((addr 1) 0x7E) | 0x80写是(addr 1) 0x7E。这一句写错现象是读什么都是 0x00或者写进去读出来对不上。/* 软件 SPI 单字节收发mode 0SCK 空闲低上升沿采样 */ static unsigned char spi_rw(unsigned char byte) { unsigned char i, recv 0; for (i 0; i 8; i) { if (byte 0x80) RC522_MOSI 1; else RC522_MOSI 0; byte 1; RC522_SCK 1; /* 从机在此上升沿采样 MOSI */ spi_delay(); recv 1; if (RC522_MISO) recv | 0x01; /* 同一边沿读回 MISO */ RC522_SCK 0; /* 拉低期间主机换数据 */ spi_delay(); } return recv; } unsigned char rc522_read_reg(unsigned char addr) { unsigned char val; RC522_NSS 0; spi_rw(((addr 1) 0x7E) | 0x80); /* 最高位 1 读 */ val spi_rw(0x00); /* 补一个字节把数据移出来 */ RC522_NSS 1; return val; } void rc522_write_reg(unsigned char addr, unsigned char val) { RC522_NSS 0; spi_rw((addr 1) 0x7E); /* 最高位 0 写 */ spi_rw(val); RC522_NSS 1; }spi_rw里的spi_delay()是整段代码唯一可以调的地方。51 在 11.0592MHz 下一个机器周期约 1.08us函数调用加循环的开销已经能撑住几百 kHz 的 SCK一开始不要追求快先让它稳。等到读卡正常了再考虑把延时压掉压太快会出现大部分时候正常、偶尔读不到的玄学这种问题最难查。验证方法很土但有效往一个没用到的测试寄存器写 0x55再读回来写 0xAA再读回来。两次都对说明读写原语没问题。RC522 有一部分寄存器是只读的别拿 VersionReg 做这个测试。3.2 寻卡与防冲突0x52 是 7 位命令最后一位别忘了寻卡命令 REQA 是 0x26WUPA 是 0x52两个都是 7 位长度。RC522 的 BitFramingReg 低三位表示最后一个字节发几位所以要在发命令前把它设成 0x07否则会多发一位有的卡能容忍有的卡直接不理你。防冲突阶段发的 0x93 0x20 是完整两字节BitFramingReg 要清 0x00。/* 返回 0 表示成功1 表示无卡2 表示冲突或校验错 */ unsigned char pcd_request(unsigned char reqMode, unsigned char *atqa) { unsigned char status; unsigned int backLen 0; rc522_write_reg(Reg_BitFraming, 0x07); /* 最后字节只发 7 位 */ status pcd_transceive(PCD_TRANSCEIVE, reqMode, 1, atqa, backLen, 0x07); if (status ! 0) return 2; if (backLen ! 2) return 2; /* ATQA 固定两字节 */ return 0; } /* 防冲突拿 4 字节 UID第 5 字节是 BCC 异或校验 */ unsigned char pcd_anticoll(unsigned char *uid) { unsigned char buf[2] {0x93, 0x20}; unsigned int backLen 0; unsigned char status, i, bcc 0; rc522_write_reg(Reg_BitFraming, 0x00); status pcd_transceive(PCD_TRANSCEIVE, buf, 2, uid, backLen, 0x00); if (status ! 0 || backLen ! 5) return 1; for (i 0; i 4; i) bcc ^ uid[i]; if (bcc ! uid[4]) return 2; /* 校验不过就是没读稳 */ return 0; }pcd_transceive是整个驱动的核心逻辑是写命令寄存器为 IDLE、清中断标志、清 FIFO 指针、把待发数据塞进 FIFO、写 BitFraming、启动命令、轮询 ComIrqReg 等 RxIRq 或 IdleIRq、最后按 FIFOLevelReg 把数据搬出来。等待那一段必须带超时计数不能while(!(irq 0x30));死等——没卡的时候真会卡死在这里看门狗都救不回来。BCC 校验这一句很多人省掉结果是偶尔拿到一个错的 UID后面认证怎么都不对。加上这三行错误会在防冲突阶段就暴露。3.3 选卡、认证与 Crypto1认证成功之后必须看 Status2Reg选卡用 0x93 0x70 加 4 字节 UID 加 BCC共 7 字节卡片返回 SAK。SAK 的最低位能告诉你这是不是一张符合 ISO14443A 的卡读到 0x08 或者 0x18 基本就是 1K 容量的常见卡。认证是最容易翻车的一步。命令 0x60 表示用密钥 A 认证0x61 表示密钥 B后面跟 1 字节块地址、6 字节密钥、4 字节 UID一共 12 字节。认证通过之后Status2Reg 的 bit3Crypto1 已开启会被硬件置起来此后所有读写都在这条加密链路上进行。关键在换卡这个动作上。Crypto1 一旦打开模块就认死了当前这张卡的密钥流重新寻卡之前必须把它关掉否则新卡的认证会一直失败而 ErrorReg 里看不出任何异常。清的方式是rc522_write_reg(Reg_Status2, rc522_read_reg(Reg_Status2) ~0x08);通常还会顺手把 CommandReg 写成 IDLE。这条经验几乎每个做过读卡器的人都踩过一次单卡测试一切正常一换成两张卡轮流刷就完蛋。3.4 块读写与钱包操作块 0 只读扇区尾块碰不得读块用 0x30 加块地址卡片回 16 字节数据加 2 字节 CRC所以backLen是 18。写块用 0xA0 加块地址加 16 字节数据加 2 字节 CRC。1K 的卡分成 16 个扇区每扇区 4 块块 0 是厂商块写保护只能读每扇区的第 3 块块 3、7、11……是尾块里面放着密钥 A、访问控制位、密钥 B写错这一个块整个扇区就再也进不去了。/* 读一个块recvBuf 至少 18 字节 */ unsigned char pcd_read_block(unsigned char block, unsigned char *recvBuf) { unsigned char cmd[2]; unsigned int backLen 0; cmd[0] 0x30; cmd[1] block; rc522_write_reg(Reg_BitFraming, 0x00); if (pcd_transceive(PCD_TRANSCEIVE, cmd, 2, recvBuf, backLen, 0x00) ! 0) return 1; if (backLen ! 18) return 2; /* 16 字节数据 2 字节 CRC */ return 0; } /* 写一个块dataBuf 必须是 16 字节CRC 由芯片自动补 */ unsigned char pcd_write_block(unsigned char block, unsigned char *dataBuf) { unsigned char buf[18]; unsigned int backLen 0; unsigned char i; buf[0] 0xA0; buf[1] block; for (i 0; i 16; i) buf[2 i] dataBuf[i]; rc522_write_reg(Reg_BitFraming, 0x00); if (pcd_transceive(PCD_TRANSCEIVE, buf, 18, buf, backLen, 0x00) ! 0) return 1; delay_ms(12); /* 卡片内部写 Flash 需要时间 */ return 0; }写块之后的那个delay_ms(12)不是可有可无的礼貌。卡片收到写命令后要在内部完成擦写这段时间里再发任何命令都会失败用 TReload 超时等也可以但软件延时更好读。钱包操作 0xC0减和 0xC1加的报文格式是 1 字节命令加 1 字节块地址加 4 字节数值加 1 字节数值取反只发 6 字节别按 16 字节去拼。4. 参数调到能复现定时器、天线增益与超时窗口代码跑通是一回事能不能稳定复现是另一回事。同一份代码在两张桌子上表现不一样通常是三个参数没调好定时器重装值、天线驱动、SPI 速率。4.1 TPrescaler 与 TReload超时窗口不是越大越好RC522 内部有一个 16 位递减定时器用途是给收发命令兜底。它的时钟来自 13.56MHz 晶振分频公式是 f 13.56MHz / (2 × TPrescaler 1)。取 TPrescaler 169得到约 40kHz也就是一个 tick 25 微秒左右。TReload 写 1000超时窗口就是 25 毫秒。参数寄存器常用值影响TPrescaler0x2B0xA9169决定 tick 长度改了它所有超时都变TReloadH/L0x2C/0x2D0x03E81000决定超时窗口太小寻不到卡太大无卡时卡顿TMode0x2A0x8D自动启动、停止时也计数25 毫秒对寻卡够用对写块偏紧。我一般的做法是给读写操作单独把 TReload 临时改到 4000约 100ms操作完再改回来。要提醒一句这个定时器的超时和pcd_transceive里那个软件轮询计数是两套东西前者防卡片不响应后者防模块不响应两个都得有。4.2 天线增益与读卡距离加电流换距离代价是稳定性TxControlReg 的低两位控制两路天线驱动写 0x03 两路全开写 0x01 只开一路。软复位之后这两位是 0天线是关的必须先置位再寻卡——这是软复位后突然读不到卡最常见的原因。想再拉远一点可以动 GsNReg0x26和 CWGsPReg0x27、ModGsPReg0x28它们控制发射和调制的增益。但增益一上去峰值电流跟着上去前面说的供电问题就会被放大。我的经验值是普通 USB 供电、杜邦线连接的情况下把读卡距离做到 3cm 左右就收手再往上调稳定性下降得比距离涨得快。真要 5cm 以上先换供电、缩短地线再谈寄存器。4.3 用串口把中间变量打出来最小调试口51 上的调试手段有限串口是性价比最高的一个。11.0592MHz 晶振配 9600 波特率几乎零误差TH1 写 0xFDSMOD 置 0初始化四行就够。别用 printfKeil C51 里那个格式化库会吃掉不少空间写一个小函数把字节转成十六进制字符更实在。/* 串口打点把关键阶段的返回值和 UID 打出来 */ void uart_init(void) { SCON 0x50; /* 方式 1允许接收 */ TMOD | 0x20; /* 定时器 1 方式 2 */ TH1 0xFD; /* 11.0592MHz 下 9600bps */ TL1 0xFD; TR1 1; SM0 0; } void log_uid(unsigned char *uid, unsigned char len) { unsigned char i; for (i 0; i len; i) { uart_send_hex(uid[i] 4); uart_send_hex(uid[i] 0x0F); uart_send_byte( ); } uart_send_byte(\r); uart_send_byte(\n); }打点的位置建议固定四个VersionReg 读出值、寻卡返回值、防冲突返回的 5 个字节、认证后的 Status2Reg。这四个点一路对下来问题基本定位到具体某一环比拿万用表量半天管用。5. 避坑与排查六个让人怀疑硬件的现场下面这几条都是我在不同板子上反复撞见的按现象、原因、解决写遇到对得上的直接跳过去看。5.1 通信与供电类现象一VersionReg 读回 0x00 或 0xFF串口一片空白。 原因SPI 完全没通。可能是 NSS 没拉低、MISO 没先写 151 准双向口不写 1 读回来永远是 0也可能是地址字节的方向位拼反了。还有一种隐蔽情况杜邦线太长SCK 和 MOSI 之间串扰示波器看波形是圆的。 解决先把spi_delay加到最大把线缩到 10cm 以内用写 0x55 读 0x55的测试寄存器法确认读写原语再往下走。现象二白天能用晚上或者插上第二个 USB 设备就时好时坏。 原因供电余量不足。发射瞬间的电流尖峰把 3.3V 拉塌模块内部复位但主循环还在跑表现就是随机失败。 解决RC522 单独一路 3.3V模块电源脚并 10uF 加 0.1uFGND 用短线单独回到电源地。这一条改完很多玄学就消失了。现象三手一碰天线附近就读不到离开就好。 原因人体和金属让天线失谐谐振点跑偏发射效率掉下来。 解决这是物理限制软件救不了。把模块远离金属底板天线正下方不要走铜箔外壳用非金属。如果结构上躲不开把增益调低反而更稳。5.2 卡片状态类现象四能寻到卡UID 也对但认证永远返回失败。 原因三种可能。一是密钥不对1K 卡出厂默认密钥是 6 个 0xFF但很多白卡被改过二是块地址和扇区对不上认证传的是要访问的那个块的地址不是扇区号三是上一次认证留下的 Crypto1 没关模块还在用旧的密钥流。 解决先读 Status2Reg 看 bit3 是不是 1是的话先清掉再确认块地址落在目标扇区的 0 到 3 之间最后换一个已知能通的默认密钥验证卡本身。现象五单卡测试正常两张卡轮流刷第二张就死。 原因寻卡之前没有清 Crypto1也没有发 HALT 让前一张卡休眠两张卡同时在场上还会引发防冲突。 解决每次操作完发一次 0x50 0x00 让卡进 HALT重新寻卡前清 Status2Reg 的 bit3并且把 CommandReg 写回 IDLE。这三步做成一个pcd_halt()函数在每次循环开头调。现象六写尾块之后这个扇区再也读不出来了。 原因密钥 A、访问位、密钥 B 三部分是有写入顺序的先改了访问位或者先写了一个自己都不记得的密钥 B扇区就锁死了。 解决没有后悔药。写尾块之前先把原内容读出来存一份写新密钥 A 之后立刻用新密钥重新认证一次认证通过再写访问位。第 6 章会给一个按这个顺序走的函数。5.3 编译与显示类现象七程序烧进去LCD1602 第一行是黑块单片机像跑飞了。 原因Keil C51 里大数组默认放在 data 区51 的片内 RAM 只有 128 或 256 字节一个 18 字节的收发缓冲加几个字符串就溢出了表现是变量互相覆盖。 解决把缓冲区显式声明成xdata或者在工程选项里把内存模型改成 Large。改完如果还乱检查 LCD 的忙检测有没有写对延时不够会显示花屏。6. 进阶批量发卡时把写尾块拆成三段单张卡调试的时候怎么写都行一旦要批量初始化几十张卡尾块写入的顺序就成了决定成败的东西。我的做法是把一次尾块修改拆成三段每一段都以一次成功的认证作为放行条件任何一段失败就停在那里绝不往下写。/* 安全改写扇区尾块keyA 用新密钥keyB 保持原样访问位用 0xFF0F0878 的常见值 */ unsigned char change_sector_key(unsigned char sector, unsigned char *oldKey, unsigned char *newKey, unsigned char *uid) { unsigned char block sector * 4 3; /* 尾块编号 */ unsigned char tail[16], i; /* 第一段验证旧密钥能进 */ if (pcd_auth(0x60, block, oldKey, uid) ! 0) return 1; /* 第二段读出原尾块只改密钥 A 那 6 个字节 */ if (pcd_read_block(block, tail) ! 0) return 2; for (i 0; i 6; i) tail[i] newKey[i]; /* 第三段写回然后用新密钥重新认证一次 */ if (pcd_write_block(block, tail) ! 0) return 3; pcd_halt(); rc522_clear_crypto(); if (pcd_auth(0x60, block, newKey, uid) ! 0) return 4; return 0; }这段代码有三个地方值得说。block sector * 4 3是尾块定位写错就写到数据块上去了。读出来的 16 字节里前 6 字节是密钥 A中间 4 字节是访问位最后 6 字节是密钥 B只覆盖前 6 个字节意味着访问位和密钥 B 原封不动风险最小。最后那次用新密钥重新认证是整个流程里唯一能证明改对了的动作认证不过就说明这张卡已经被写坏了立刻停下来别继续写下一张。批量跑的时候还有个细节每张卡处理完必须发 HALT 并清 Crypto1中间加 50ms 以上的间隔让卡片彻底离场。少了这个间隔机械臂换卡的瞬间两张卡同时在场上防冲突返回的 UID 可能是前一张的。我现在的固定习惯是任何涉及尾块的操作先在一张废卡上跑三遍把串口日志留着确认三次都能认证通过再上批量。从那以后我每次写扇区密钥都强制走一遍读原值、只改密钥 A、写完立刻重认证这三步一次都没再锁死过卡。希望帮到你。本文还有配套的精品资源点击获取