ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32+CLRC663多协议NFC读卡器:支持14443A/B与15693

STM32+CLRC663多协议NFC读卡器:支持14443A/B与15693 前几天熟人做闸机项目要求读三种卡小区门禁用的14443A、校园卡遗留的14443B、还有图书馆里那批15693标签。他一开始弄的是RC522结果B卡和15693完全没反应回头问我怎么办。我让他换成CLRC663国产NFC模块配合STM32F103C8T6最小系统板重写读卡逻辑两天就把样机调通了。如果你也在做类似的多协议读卡器这篇文章就是给你准备的。我会把选型、接线、初始化、三种协议读UID的流程、工程骨架还有第一次调板时踩过的坑全部过一遍。适合手里已经有一块STM32F103C8T6最小系统板、想低成本验证多协议NFC方案的工程师或者学生不需要精通射频前端跟着接线和调参就能跑起来。1. 为什么RC522不够用CLRC663在协议支持上是断层级的1.1 RC522只能解决80%的常规卡RC522这颗芯片在淘宝上到处都是配套资料也很全很多人一上来就会拿它做门禁。但RC522只支持ISO 14443A也就是M1卡、NTAG系列这类A卡。你拿它刷14443B的校园卡、15693的图书标签根本不会有响应。如果你只是做桌面读卡器读自家M1卡RC522完全够用。但一旦进入“一卡通兼容”场景比如小区门禁、校园一卡通、图书馆还书设备要同时面对老式A卡、B卡、ISO15693标签RC522再怎么改代码也救不回来。CLRC663这类芯片才是干这个活的。1.2 CLRC663模块到底强在哪CLRC663是NXP一颗13.56MHz前端芯片相比RC522最核心的升级是多协议支持ISO 14443A、ISO 14443B、FeliCaISO 18092以及ISO 15693。市场上很多国产NFC读卡模块就是基于CLRC663做的板上集成了天线和匹配电路买回来直接飞线接MCU就能用比拿裸片自己画射频板省事太多。简单对比一下项目RC522类模块CLRC663类模块支持协议ISO14443A、MifareISO14443A/B、FeliCa、ISO15693典型读卡距离3-5cm5-10cm取决于天线和调谐常用接口SPISPI/I2C/UART串口日志/断点调试难度低中寄存器偏多适合场景门禁、桌面M1读写多协议一卡通、预付费终端、图书馆设备模块价位很低中高但比RC522强大得多选型上我建议如果你是毕业设计或者小批量产品直接买带天线的CLRC663成品模块不要买裸芯片自己画天线。原因很简单CLRC663的天线匹配比RC522敏感线圈尺寸、调谐电容、地平面处理都会直接影响读卡距离。成品模块已经做好了这些你只需要关心数字侧。2. 连线前的三个决定电平、电源和SPI引脚分配2.1 引脚对照表与CubeMX怎么填STM32F103C8T6最小系统板上最容易用的就是SPI1对应PA5、PA6、PA7。建议再加一个软件CS脚PA4以及复位脚PB0、中断脚PB1。CLRC663模块上的引脚命名各家略有不同常见的有MOSI、MISO、SCK、CS、RST、IRQ也有标SI、SO、NSS的接线逻辑一样。CLRC663模块引脚接到STM32F103C8T6哪个脚说明VCC3.3V模块供电千万不要接5VGNDGND共地必须接SCKPA5SPI1时钟MOSI/SIPA7SPI1主机输出MISO/SOPA6SPI1主机输入CS/NSSPA4软件片选GPIO控制RST/NRSTPDPB0复位控制拉低复位拉高运行IRQPB1中断输出可用下降沿触发或轮询在CubeMX里把PA5、PA6、PA7配为SPI1PB0、PB1设为GPIO输出/输入PA4设为GPIO输出。开启USART1调试时把卡片UID信息打印到串口助手里不然你对着寄存器干看也不知道芯片在工作没有。2.2 电源和布局上的细节很多人在这里犯的第一个错误是直接拿STM32F103C8T6最小系统板上的3.3V引脚给CLRC663模块供电。在低负载调试时这样能跑但CLRC663天线驱动瞬间电流不低STM32板载LDO往往扛不住会导致读卡距离忽远忽近甚至复位重启。我实际测试后的建议是单独用一片AMS1117-3.3或者低压差LDO从USB 5V转3.3V给CLRC663模块供电地线跟STM32共地。在模块VCC和GND之间加一个100μF电解电容再并联一个0.1μF陶瓷电容滤波效果会好很多。跳线尽量短。SPI时钟在STM32F103上能跑到十几MHz但面包板上长跳线会带来振铃和误码。如果暂时只能面包板把SPI分频调低到4.5MHz或更低稳定优先。天线正下方不要走其他信号线也不要把卡直接放在金属桌面上测试。还有一个容易忽略的点有些CLRC663模块的RST脚默认是低电平需要拉高才能工作。如果固件初始化一直失败先量一下RST引脚电压有没有被一个10k电阻拉低。我第一次用的模块拿到手测RST只有0V还以为是芯片坏了实际上是模块出厂默认引脚悬空我程序里也没主动拉高。3. 让CLRC663开口说话寄存器读写和SPI时序3.1 SPI时序里的关键点CLRC663的SPI很像RC522但寄存器访问方式不太一样。进入正式操作前先把SPI1配置成Master、8位数据、MSB先行、CPOL0、CPHA0也就是常规模式0。硬件NSS关掉用PA4软件片选。片选时序上每次读写寄存器都拉低PA4发送首字节首字节里包含了寄存器地址和读写方向。CLRC663的地址比RC522要规整很多驱动库直接会这样构造首字节reg 1得到7位地址空间最低位或最高位作为读写标志。我用过的国产模块驱动写寄存器时首字节是地址左移一位读寄存器时在最高位加0x80。如果你拿到的芯片资料里画法不同以数据手册为准但总体思路一样。3.2 寄存器读写函数和最小驱动骨架在STM32CubeMX生成工程之后先写两个最基础的SPI读写函数void RC663_WriteReg(uint8_t reg, uint8_t val) { uint8_t addr (reg 1) 0x7F; uint8_t data val; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, addr, 1, 10); HAL_SPI_Transmit(hspi1, data, 1, 10); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } uint8_t RC663_ReadReg(uint8_t reg) { uint8_t addr ((reg 1) 0x7F) | 0x80; uint8_t data 0; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, addr, 1, 10); HAL_SPI_Receive(hspi1, data, 1, 10); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); return data; }严格说这不是完整寄存器操作封装但已经够用了。实际项目中我会再加一层连续读写比如RC663_WriteBuffer和RC663_ReadBuffer用于把ATQA、UID这类多字节数据一次性搬到FIFO里。接下来是CLRC663的初始化。不同模块的天线匹配参数不一样最好的办法是把模块卖家给的初始化数组直接焊进代码。如果卖家没给网上搜对应芯片的rc663_init数组也可以。初始化流程一般是这样RST拉低延时100ms再拉高让芯片完成复位。读取版本寄存器确认SPI通路正常。如果读到非0值说明通信没问题。按照芯片手册或厂家例程写调制解调、接收阈值、发送功率等相关寄存器。清空FIFO关掉不必要的中断。打开射频发射进入读卡状态。不要跳过版本寄存器这一步。我每拿到一块模块第一件事就是读版本号。如果读出来全是0xFF或者0x00先检查接线和SPI配置而不是一头扎进读卡调试。你连芯片都没唤醒后面谈协议没有意义。4. 三类卡片的UID提取逻辑命令帧构造并没有想想中复杂4.1 14443A的REQA加防冲突ISO14443A是最常用的一类。拿到一张A卡读UID的第一步是发REQA命令CLRC663收到后会返回2字节的ATQA。然后发防冲突命令典型的帧是0x93 0x20这个帧的意思是“防冲突级联级别1NVB0x20”。卡返回5字节数据其中前4字节是UID最后1字节是BCC校验。只读UID的话把这4字节存下来就可以了。0x93 0x70 [UID0] [UID1] [UID2] [UID3] [BCC]上面这个是选择命令返回SAK一字节。SAK决定UID是4字节还是7字节。如果SAK表明有级联你还要用0x95和0x97继续读第二、第三段UID。很多国产模块驱动库已经把这一步封装好直接调用函数就能返回完整UID。4.2 15693的Inventory命令ISO15693的读UID流程比A卡更直观。它不需要一帧一帧地防冲突直接发Inventory命令一次就能返回8字节UID。最常用的命令帧是0x01 0x01 0x00三个字节分别是请求标志、Inventory命令码、Mask长度。如果Mask长度为0就是让所有在场的15693标签响应。标签返回的帧通常是标志字节 DSFID UID也就是10字节数据里UID在第3到第10字节。很多15693标签支持双字节写模式但读UID不需要关心这些知道这个命令就能开始调板了。CLRC663的高层驱动里往往有专门的ISO15693Inventory命令底层寄存器帮你组帧。但调试时我习惯自己构造命令发到FIFO这样能更直观地判断是CLRC663没发出去还是标签没回应。4.3 B卡和F卡的快速上手要点ISO14443B和FeliCa在命令格式上跟A卡、15693差异很大。B卡发REQB命令时不能只发一个0x05。典型格式是0x05 AFI PARAM CRC其中AFI和PARAM都有具体含义。如果你用CLRC663的自动CRC功能FIFO里可能只需要放前面的字节但要注意不同驱动对CRC的处理方式不一样。B卡返回的ATQB里有一个4字节PUPI很多B类卡直接拿PUPI当卡号。FeliCa卡在CLRC663里也有独立轮询命令。FeliCa的UID不叫UID叫IDm长度是8字节。跟A卡那种“REQA 防冲突 SELECT”完全不是一个路子别混着用。轮询命令一般以0x00开头后面跟系统码和时隙参数。手头没有FeliCa卡的话这段可以先跳过等真正需要时再查FeliCa协议手册。总结成一张表卡片类型第一步命令关键返回字段拿哪个字段当卡号ISO14443AREQA 0x26ATQA 2字节UID通常4字节或7字节ISO14443BREQBATQB 12字节左右PUPI前4字节FeliCaPollingIDm 8字节IDmISO15693Inventory 0x01 0x01 0x00标志DSFIDUIDUID8字节5. 可用工程骨架CubeMX配置加一段能跑起来的读卡主循环5.1 CubeMX配置顺序不要小看这一节很多人就是死在配置顺序上。我推荐的顺序是选择STM32F103C8T6。配置RCC时钟用8MHz外部晶振系统时钟拉到72MHz。配置SPI1Master8位模式0分频设为16也就是4.5MHz。等跑通了再往高调。配置PA4、PB0、PB1为GPIOPA4输出高电平PB0输出低电平PB1输入上拉。配置USART11152008-N-1。生成Keil工程或STM32CubeIDE工程。SPI分频在初期不要追求极限。我试过用9MHz SPI跑CLRC663在面包板上能工作但线稍长就出错。回到4.5MHz后读卡距离反而更稳定因为误码少了。5.2 main.c中的读卡循环初始化之后写一个简单的“读UID”主循环。假设你已经有了RC663_Transceive这个函数它的作用是把发送帧写入FIFO启动Transceive命令等待IRQ然后从FIFO读回响应数据。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); if (RC663_Init() ! 0) { printf(CLRC663 init failed\r\n); while (1); } printf(CLRC663 ready\r\n); while (1) { uint8_t uid[16] {0}; uint8_t uidLen 0; // 先试A卡 if (RC663_ReadTypeA_UID(uid, uidLen) 0) { printf(A: ); PrintHex(uid, uidLen); } // 再试15693 else if (RC663_Read15693_UID(uid, uidLen) 0) { printf(15693: ); PrintHex(uid, uidLen); } HAL_Delay(300); } }中间两个读卡函数可以这样写关键就是调RC663_Transceiveuint8_t RC663_ReadTypeA_UID(uint8_t *uid, uint8_t *len) { uint8_t tx[2]; uint8_t rx[16]; uint8_t rxLen 0; // REQA tx[0] 0x26; if (RC663_Transceive(tx, 1, rx, rxLen) ! 0) return 1; if (rxLen 2) return 1; // 防冲突 CL1 tx[0] 0x93; tx[1] 0x20; if (RC663_Transceive(tx, 2, rx, rxLen) ! 0) return 1; if (rxLen 5) return 1; memcpy(uid, rx, 4); *len 4; return 0; } uint8_t RC663_Read15693_UID(uint8_t *uid, uint8_t *len) { uint8_t tx[3] {0x01, 0x01, 0x00}; uint8_t rx[16]; uint8_t rxLen 0; if (RC663_Transceive(tx, 3, rx, rxLen) ! 0) return 1; if (rxLen 10) return 1; memcpy(uid, rx[2], 8); *len 8; return 0; }这里有个细节RC663_Transceive里的超时一定要做不能傻等IRQ。没有卡片时如果CLRC663一直没有返回响应MCU会卡死在等待状态。我的做法是设置一个超时计数器比如100ms内IRQ没拉低就返回错误。实际测试中刷一次卡一般几十毫秒就完成了。6. 调板时最容易翻车的问题我的排查顺序和经验6.1 从“读不到卡”开始分析我第一次用CLRC663调板时先后遇到三个问题每个都很有代表性第一个问题是SPI读回来全是0xFF。排查了半小时发现是MOSI和MISO接反了。模块上MOSI标的是“主设备输出从设备输入”接到STM32的MISO脚上等于我把一条单行道修反了。这里强烈建议接线之前先看模块丝印SI不是“SPI输入给STM32”的意思它是“CLRC663从SPI总线接收数据”也就是接MCU的MOSI。第二个问题是IRQ一直不触发。程序里配置了PB1下降沿中断但红外中断标志永远没清掉。后来改用轮询PB1引脚发现也能正常工作。CLRC663的中断状态寄存器有多个每个中断源都要单独清你在初学阶段直接用轮询省心非常多。第三个问题是15693卡完全没反应但A卡正常。最后发现不是协议问题而是CLRC663的发射功率寄存器配置不对天线场覆盖太弱。把发送器相关寄存器改成厂家推荐的配置之后15693标签在三厘米外就能读到了。6.2 关于天线和环境的3个提醒天线周围有金属是最容易忽视频繁出现的坑。CLRC663模块天线的感应区很小你把模块放在铁皮外壳旁边会看到读卡距离瞬间掉一半。产品化时外壳开窗位置要避开天线正下方最好用泡棉把模块垫高。电源噪声也会让UID偶尔跳变。表面上看芯片复位正常SPI也能通信但卡的响应帧里某一位被干扰导致UID末尾一个字节时不时变化。把模块供电改成独立LDO之后问题就消失了。如果你实验时发现UID不稳定先别怀疑算法去查供电。还有一点CLRC663对天线参数比RC522敏感得多。你买的裸模块厂家已经把匹配调好了但如果你后来手痒去改天线线圈哪怕只是多加一根跳线匹配都会变。调试读卡距离的时候建议保持硬件不动只调寄存器。等哪天你真的要去设计自己的天线再单独学S11参数和天线手工调试。我自己的经验是这类多协议读卡器硬件连接和初始化比协议代码更容易出错。CLRC663的协议命令其实不复杂翻一下14443和15693的帧格式把REQA、防冲突、Inventory这几个命令跑通大半个项目就完成了。剩下的时间基本都花在排查供电、接线和天线干扰上。你现在如果也卡在某个“读不到卡”的状态可以先把手放在模块上方看一下距离有没有变化再用示波器抓一下SPI波形一般来说很快就能定位问题。
返回列表