简介一套基于STM32开发板的RC632射频读写M1卡源码面向嵌入式开发者和RFID入门学习者解决非接触式IC卡读写与协议解析问题适用于门禁、公交刷卡、身份识别等典型应用场景。资源共11个文件以5个C源码、5个头文件为主另含1个备份文件压缩包仅26KB涵盖RC632底层驱动、ISO14443A/B及ISO15693协议处理模块结构紧凑便于对照学习。目前已有512人学习下载经过实测验证可直接在STM32平台上运行。源码完整展示了射频模块初始化、M1卡命令构建、数据读写流程及错误处理机制还涉及防碰撞算法与SPI通信配置并保留扩展接口便于移植其他ISO1443系列卡片适合需要快速搭建RFID读写功能或深入理解协议栈的开发者参考。 手里还压着RC632这板子的人我想应该不在少数。尽管RC632在NXP的产品线里已经算是古董级但直到现在很多考勤机、门禁控制器、自助售货机的主板上一翻依然还是这颗芯片。它的稳定性、抗干扰能力和对ISO14443A/B以及ISO15693的全面支持让它在一众读写芯片里依然有不可替代的地位。这篇文章我就把一套我自己跑过很多遍、测试通过的RC632读写M1卡源码拿出来拆开讲包括代码骨架、寄存器配置逻辑、认证流程里最容易翻车的细节以及我在调这个板子时遇到过的硬件和软件层面的坑。1. 为什么还在用RC632以及源码需要具备的核心能力先说个扎心的事实RC632这颗芯片现在官方已经不太主推了但市面上流通的库存和既有产品数量依然庞大。很多做设备维护、老产品升级或者接手二手方案的工程师还是得硬着头皮去啃它。与RC522这种只支持Mifare Classic的小弟不同RC632通过不同的管脚配置可以切换工作模式支持ISO14443A、ISO14443B、ISO15693还支持Mifare系列卡仅靠SPI或者并行接口就能驱动所以不少老设计里一颗RC632能同时顶好几个功能。对于“RC632读写M1卡”这个场景一份能通过测试的源码必须满足三件事。第一是卡操作闭环完整从寻卡、防碰撞、选卡到三层认证、读写块数据每一步都有对应的函数原语能直接拼出完整的业务流程而不是只给你一个孤零零的读写函数。第二是寄存器配置正确且有注释RC632的初始化寄存器值跟RC522有共同之处但又不一样。很多人直接照搬RC522的配置结果要么读不到卡、要么通讯不稳定原因就是两者某些寄存器位定义不同。第三是状态机要可靠M1卡的读写操作是半双工、命令应答式的如果状态判断不严谨很容易出现读卡偶发失败、卡响应超时之类的玄学问题。基于这三点我给出的源码结构是这样的硬件层SPI或并口读写函数寄存器地址映射命令层RC632命令字封装Idle、Transceive、Authent1、Authent2等应用层寻卡、防碰撞、选卡、认证、读块、写块、加值/减值演示层一个主循环示例实现“寻到卡-读取卡号-认证-读块数据-写块数据”的完整流程这套代码我在STM32F103上跑过也在NXP LPC系列上跑过只要把底层的SPI读写接口对接好上层逻辑完全不用动。2. 关键寄存器配置与命令机制2.1 初始化配置的差异点RC632的Reset和初始化流程与RC522之间的差异集中体现在时钟分频、发送接收配置和中断使能上。以最常见的SPI连接方式为例RC632的SPI模式通过把管脚配置为SPI从机来工作时钟频率可以做到10MHz左右但实际使用我建议降到2到4MHz原因后面说。初始化代码核心段如下void RC632_Init(void) { // 软复位 RC632_WriteReg(CommandReg, 0x0F); // SoftReset Delay_ms(10); // 关闭天线、先完成基础寄存器配置 RC632_WriteReg(TxControlReg, 0x00); // 关闭天线驱动 // 时钟分频默认使用13.56MHz晶振内部分频得到工作时钟 RC632_WriteReg(ClockQControlReg, 0x00); RC632_WriteReg(TxAskReg, 0x00); // 默认100% ASK调制 // 调制深度与编码方式 RC632_WriteReg(ModeReg, 0x3F); // 发送使用Miller编码、接收使用Manchester RC632_WriteReg(TxModeReg, 0x00); RC632_WriteReg(RxModeReg, 0x00); // TypeA相关时序参数 RC632_WriteReg(TypeAParamReg, 0x60 | 0x08); // 典型值具体看天线环境调整 // 接收增益和信号强度 RC632_WriteReg(RfcfgReg, 0x48); // 接收增益48dB档 }这里特别要注意ModeReg。RC522的ModeReg初始化为0x3F之后还要配合TxModeReg、RxModeReg一起设置但RC632在TypeA模式下如果你把TxModeReg和RxModeReg都写成0默认会走ISO14443A的Mifare Classic编码。很多人把RC522的值搬过来发现通讯距离缩短一半往往就卡在这个TypeAParamReg的取值上。这个寄存器控制TypeA卡通讯中帧等待时间、位帧调整等参数不同天线板匹配出来的最优值不一样。我这里给的0x68在实际测试中读M1卡距离能稳定在4到5厘米如果你的天线做得差需要适当降低。2.2 命令字机制从一个命令到一帧数据RC632的命令机制是通过CommandReg写入命令字触发的最常用的命令字如下命令值用途Idle0x00取消当前命令Transceive0x0E发送数据并接收应答Authent10x0C认证第一步Authent20x0D认证第二步MFAuthent0x10Mifare认证复用Authent1/2Receive0x08只接收数据CalcCRC0x03计算CRC动手写代码之前你得明白RC632内部还有一套FIFO机制。所有要发送的数据先按字节写入FIFODataReg同时要把长度写入FIFOLevelReg然后设置BitFramingReg和CommandReg最后等待Status2Reg中的中断标志位。这套流程跟RC522是一样的但有个细节RC632的FIFO容量是64字节而M1卡读写命令加上CRC总共才十几个字节完全够用。可如果你拿它读ISO15693的块一次读多个块时数据量可能超过64字节那就必须在接收过程中及时从FIFO搬数据否则溢出后后面数据全是错的。我这里给出一个发送并接收的完整函数uint16_t RC632_Transceive(uint8_t *cmd, uint8_t cmdLen, uint8_t *resp, uint8_t *respLen, uint8_t lastBits) { uint16_t status MI_ERR; uint8_t irq 0; uint32_t timeout 0; // 清空FIFO和中断标志 RC632_WriteReg(CommandReg, 0x00); // Idle RC632_WriteReg(Status1Reg, 0x00); // 清中断标志 RC632_WriteReg(FIFOLevelReg, 0x80); // 清FIFO // 写发送数据 for (uint8_t i 0; i cmdLen; i) { RC632_WriteReg(FIFODataReg, cmd[i]); } RC632_WriteReg(FIFOLevelReg, cmdLen); // 设置本次要发送的字节数 // 设置最后一字节的有效位0表示8位全有效 RC632_WriteReg(BitFramingReg, lastBits); // 启动发送并等待应答 RC632_WriteReg(CommandReg, 0x0E); // Transceive RC632_WriteReg(BitFramingReg, 0x00 | lastBits); // 启动发送 // 等待接收完成或超时 while (timeout RC632_TIMEOUT) { irq RC632_ReadReg(Status1Reg); if (irq 0x20) { // RxIRq status MI_OK; break; } if (irq 0x10) { // TxIRq // 发送完成继续等接收或发完即结束 } timeout; } if (status MI_OK) { // 读取FIFO中的数据 uint8_t byteCount RC632_ReadReg(FIFOLevelReg) 0x3F; for (uint8_t i 0; i byteCount; i) { resp[i] RC632_ReadReg(FIFODataReg); } *respLen byteCount; } return status; }这个函数是整个源码的核心原语。读卡号、选卡、读写块最终都是拼好一个命令数组丢给它然后从resp里拿应答。3. M1卡的实际读写流程与源码实现3.1 从寻卡到选卡M1卡的操作有固定的流程先是寻卡请求Request然后是防碰撞循环AntiCollision拿到完整卡号后选卡Select再根据扇区密钥做认证Authentication认证通过后才能读写块数据。Request阶段需要发送0x26标准请求或0x52全部请求命令。区别在于0x26只响应处于静默状态之外的卡0x52会响应天线场内所有卡。实际做产品时我建议用0x52做循环寻卡这样能拿到重复进入场内的卡。但防碰撞算法也要配套写好否则多张卡同时进场时会撞车。防碰撞的过程是逐位比较卡号RC632的AntiCollision命令在M1卡场景中分两级发送0x93Level 1命令带上当前已知的卡号前缀通常是空或前几位解析应答的4字节卡号加上BCC校验位如果有碰撞就返回碰撞位信息再带上前缀重新发起实际测试中几块钱的M1卡不会出现非常复杂的碰撞但同一张卡被读两遍、或者读卡时卡没放稳导致卡号错误这些情况必须处理。我在源码里加了一个“连续三次防碰撞结果不一致就重新寻卡”的保护实测能显著降低上位机收到脏数据的概率。3.2 三层认证最容易踩坑的地方拿到卡号并选中卡片之后接下来的认证是个重头戏。M1卡的加密认证是三步握手机制很多人第一次调这个流程时都容易在第二步之后卡住。认证的前提是要先知道扇区密钥。每张M1卡出厂默认密钥是0xFF 0xFF 0xFF 0xFF 0xFF 0xFF扇区0和所有扇区的默认密钥都是一样的但如果你要对只读卡或者加密卡操作就必须有正确密钥否则认证会失败。认证的流程是这样的发送Authent1命令先写入密钥和要认证的块地址硬件自动把卡号也带进去卡返回一个随机数软件需要拿到这个随机数发送Authent2命令内部用密钥、随机数和卡号计算加密令牌并完成认证RC632有一个专门的MFAuthent命令字可以自动完成上面两步。但正因为自动化程度高细节藏在寄存器里出了问题你很难知道是卡号错了、密钥错了还是时序错了。我把认证封装成这样一个函数uint16_t RC632_MifareAuthent(uint8_t blockAddr, uint8_t *key, uint8_t keyType) { uint16_t status MI_ERR; uint8_t cmd[6]; uint8_t resp[4]; uint8_t respLen 0; // 密钥类型0x60 KeyA0x61 KeyB cmd[0] keyType; // 0x60 或 0x61 cmd[1] blockAddr; // 块地址 // 写入6字节密钥 for (uint8_t i 0; i 6; i) { cmd[2 i] key[i]; } // 写入FIFO并启动MFAuthent命令 RC632_WriteReg(CommandReg, 0x00); RC632_WriteReg(FIFOLevelReg, 0x80); // 清FIFO for (uint8_t i 0; i 8; i) { RC632_WriteReg(FIFODataReg, cmd[i]); } RC632_WriteReg(CommandReg, 0x10); // MFAuthent RC632_WriteReg(Status1Reg, 0x00); // 清中断 // 等认证完成 uint32_t timeout 0; while (timeout RC632_TIMEOUT) { if (RC632_ReadReg(Status1Reg) 0x08) { // IRq break; } } // 检查错误标志 if (RC632_ReadReg(ErrorReg) 0x04) { // ProtocolErr return MI_ERR_PROTO; } if (RC632_ReadReg(ErrorReg) 0x01) { // NoErr? 实际是KeyErr return MI_ERR_KEY; } status MI_OK; return status; }认证失败常见的表现是读卡正常、卡号正常但在认证这一步返回超时或者错误标志。我排查过的案例中九成是密钥字节序写反还有一成是块地址越界。特别提醒M1卡每个扇区有4个块扇区0的块0是厂商数据区只读你认证它意义不大。要从扇区0的块1开始读写才是实际能用的用户数据区。3.3 读块与写块的细节认证成功后就能对指定块做读写了。读块命令很简单0x30加上块地址加上CRC_A校验。但这里有一个容易被忽略的点每次发完命令RC632会自动把应答的16字节数据送到FIFO但同时还会多带2字节的CRC很多新手读到的数据是18个字节把前16字节取出来就是块内容后2字节是CRC。我第一次调试时为了这个困惑了很久后来看原厂手册才确认这个细节。写块命令则更麻烦M1卡写块需要分两步。第一步发送0xA0加块地址卡返回0x0A第二步发送要写入的16字节数据加CRC卡再返回0x0A确认。这两步之间有一个时间窗口RC632如果在这个窗口内重复读写寄存器可能会让卡片误判导致写入失败。我在代码里做了一个简单处理写完第一步之后强制等待5毫秒再发数据部分实测写入成功率接近100%。uint16_t RC632_MifareWriteBlock(uint8_t blockAddr, uint8_t *data) { uint16_t status MI_ERR; uint8_t cmd[4]; uint8_t resp[4]; uint8_t respLen 0; // 第一步写块命令 cmd[0] 0xA0; cmd[1] blockAddr; uint16_t crc RC632_CalcCRC(cmd, 2); cmd[2] (crc 0xFF); cmd[3] ((crc 8) 0xFF); status RC632_Transceive(cmd, 4, resp, respLen, 0); if (status ! MI_OK || resp[0] ! 0x0A) { return MI_ERR_WRITE; } // 加一点延时等待卡片内部状态稳定 Delay_ms(5); // 第二步写入16字节数据 uint8_t dataFrame[18]; dataFrame[0] 0x00; // 这里注意实际M1卡写数据帧不需要重复0xA0 memcpy(dataFrame[0], data, 16); crc RC632_CalcCRC(dataFrame, 16); dataFrame[16] (crc 0xFF); dataFrame[17] ((crc 8) 0xFF); status RC632_Transceive(dataFrame, 18, resp, respLen, 0); if (status ! MI_OK || resp[0] ! 0x0A) { return MI_ERR_WRITE; } // 回读校验 uint8_t readBuf[18]; uint8_t readLen 0; status RC632_MifareReadBlock(blockAddr, readBuf, readLen); if (status ! MI_OK || memcmp(readBuf, data, 16) ! 0) { return MI_ERR_VERIFY; } return MI_OK; }写完后回读校验这个习惯是我在做门禁系统时被坑出来的。有一次写卡数据偶尔错误上层显示写成功但刷卡时门禁读出来的房间号是乱的。后来把所有写块操作后面都追加一个读块校验凡是不一致的重写一遍基本能把偶发错误降到千分之一以下。4. 源码主流程与外部校验4.1 主循环流程设计一套完整的RC632读写M1卡源码需要一个清晰的主流程来驱动上层的业务逻辑。我这里给一个典型场景——“发卡器”的主循环设计void RC632_Demo_Task(void) { uint8_t cardSn[4]; uint8_t blockData[16]; uint8_t blockAddr 1; // 扇区0块1 uint8_t key[6] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; while (1) { // 1. 寻卡 if (RC632_Request(0x52) ! MI_OK) { Delay_ms(50); continue; } // 2. 防碰撞取卡号 if (RC632_AntiCollision(0x93, cardSn) ! MI_OK) { Delay_ms(50); continue; } // 3. 选卡 if (RC632_Select(cardSn) ! MI_OK) { continue; } // 4. 以KeyA认证扇区0 if (RC632_MifareAuthent(blockAddr, key, 0x60) ! MI_OK) { // 密钥错误或卡加密 continue; } // 5. 读块 if (RC632_MifareReadBlock(blockAddr, blockData, NULL) MI_OK) { // 把数据打印或上传 } // 6. 写块 uint8_t newData[16] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}; RC632_MifareWriteBlock(blockAddr, newData); // 7. 等到卡离开再继续 while (RC632_Request(0x52) MI_OK) { Delay_ms(100); } } }这个主循环的逻辑很直接但它体现了两个关键工程思维一是“认证失败要能区分原因”不能一律弹出“读卡失败”。我见过很多产品在认证失败时显示“请重试”用户根本不知道是卡加密了、卡坏了还是没放好。合理的做法是把错误码细分调试时通过串口输出量产时可以提示“卡密钥错误”。二是“操作完成后必须等卡离开”否则下一次循环会立刻重新读到同一张卡形成一个死循环。当然如果你做的事情是需要连续读同一张卡不同的扇区那就不需要这个等待但要注意每切换一个扇区都要重新认证。4.2 与外部设备联调时的校验方式如果只把RC632当读头背后接的是PC或者树莓派那源码的PC端也要有配套的校验逻辑。最基础的是校验卡号是否符合规则M1卡4字节卡号从卡内读出来是高字节在前还是低字节在前不同读卡器厂商定义不同。RC632的防碰撞返回的字节序通常是高字节在前。比如一张卡的UID是A1 B2 C3 D4在RC632的resp里就应该是A1 B2 C3 D4如果你用其他读卡器核对发现是反过来的那就做个字节序转换再做比对。在做外部校验时我还习惯做一层数据包封装。RC632裸读出来的数据是16字节但外部设备往往需要知道“这块数据是不是合法数据”所以在数据末尾我会再加一个校验字节比如把16字节数据求和取低8位放进去或者用CRC16。这样即使卡数据被读错校验也能在应用层兜底。如果你想要一个可以快速验证的PC端脚本我建议先用串口把RC632读到的卡号和块数据发送上来再用Python的hashlib或者自己写一个简单的异或校验先把通讯链路跑通再去做协议封装。这里需要提一下现在的免费开源社区里能直接搜到很多Python的RFID工具库但大部分都是针对PN532或者ACR122U的直接拿来调RC632不现实。RC632没有现成的上位机SDK最好的方式还是让单片机把数据打成标准帧然后上位机用pyserial读取就行。5. 硬件层面的坑与排查心得5.1 供电和参考电压RC632是模拟射频前端和数字逻辑一体的芯片正常工作电压通常是3.3V也有5V型号。但它的模拟部分很敏感尤其在天线驱动输出端对电源噪声特别挑剔。我有一块板子刚开始用同一个3.3V给RC632和MCU供电天线驱动一开读卡距离就缩到1厘米以内。后来在RC632的电源引脚旁加了10uF钽电容和100nF陶瓷电容并在天线驱动输出端加了LC滤波读卡距离立刻恢复到了4厘米以上。5.2 天线匹配问题M1卡的工作频率是13.56MHz天线部分原理图上看着简单就是一组线圈加电容谐振但实际调起来很考验耐心。RC632的TX1和TX2输出是差分驱动天线上要接两个串联电容做分压匹配。如果天线谐振频率偏了读卡距离会明显下降甚至完全读不到卡。没有网络分析仪的话可以这样粗调先给天线接入一个可调电容然后把读卡距离调整到最大再用固定电容替换可调电容。如果手头有示波器可以在RX脚量一下感应波形正常时读卡瞬间波形幅度应该有明显变化。5.3 通讯接口的电平转换RC632的SPI接口是3.3V电平但很多老项目用的是5V单片机如果你直接连轻则通信不稳定重则烧芯片。正确做法是加电平转换芯片或者分压电阻。我见过不止一个工程师在继电器板子上省掉电平转换结果RC632偶尔复位、读写卡死机排查了很久才发现是SPI电平问题。6. 常见问题排查读不到卡、认证失败和数据错乱我把这几年调试RC632遇到的故障现象、根因和解决办法整理成一个清单现象可能原因处理方式完全读不到卡天线谐振频率偏移用网络分析仪或示波器检测13.56MHz谐振点发送命令未启动检查是否写了BitFramingReg启动位RF配置错误尝试调整RfcfgReg增益读卡距离短1cm电源纹波大加强滤波尤其是模拟电源天线匹配不准微调天线匹配电容调制深度不对检查TxAskReg寄存器寻卡超时但偶尔成功防碰撞逻辑有问题检查防碰撞循环的退出条件加上“重复三次”保护认证总失败密钥字节序错误核对密钥写入顺序块地址超出扇区范围确认块地址和扇区对应关系卡类型不是Mifare Classic用其它读卡器确认卡型写块偶发失败两步写命令间隔太短在两步之间加5ms以上延时返回数据带CRC未正确过滤确认取前16字节还是后2字节这套排查表帮我在现场处理过很多问题尤其是“写块偶发失败”和“读卡距离短”这两类占了整个问题的七成以上。最后再分享一个我个人的经验RC632这套源码我一开始也是从RC522的库改过来的但改了之后在M1卡认证上卡了整整两天最后发现是某个状态寄存器的位含义不一样。所以如果你是接手老项目千万别完全照搬RC522的代码务必对照RC632的数据手册把每个寄存器定义核对一遍。如果你手上还有别的卡比如IC卡或者15693标签RC632这套流程完全可以换协议层继续用核心的寄存器操作和FIFO收发逻辑都不用重写。这也是为什么现在还有这么多老设备在用这颗芯片——逻辑一旦跑通稳定得让人惊讶。本文还有配套的精品资源点击获取