简介:一套基于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写入命令字触发的,最常用的命令字如下:
| 命令 | 值 | 用途 |
|---|---|---|
| Idle | 0x00 | 取消当前命令 |
| Transceive | 0x0E | 发送数据并接收应答 |
| Authent1 | 0x0C | 认证第一步 |
| Authent2 | 0x0D | 认证第二步 |
| MFAuthent | 0x10 | Mifare认证(复用Authent1/2) |
| Receive | 0x08 | 只接收数据 |
| CalcCRC | 0x03 | 计算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卡场景中分两级:
- 发送
0x93(Level 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 = KeyA,0x61 = 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收发逻辑都不用重写。这也是为什么现在还有这么多老设备在用这颗芯片——逻辑一旦跑通,稳定得让人惊讶。
本文还有配套的精品资源,点击获取