
简介面向物联网与智能卡开发者的CLRC663读写器工程包覆盖14443A与ISO/IEC 15693两种主流非接触式协议适用于门禁、交通卡、库存管理、商品防伪等场景解决UID读取、数据块读写、卡片认证及ATS解析等关键问题。包内共有896个文件以C/H源码、Keil工程文件uvprojx/uvoptx、编译生成的O/CRF中间文件为核心辅以汇编、包含文件、PDF与CHM说明文档等便于在STM32平台上直接移植、编译与调试。压缩包整体28.72MB目录组织清晰可快速定位驱动层、应用层及参考文档。已有782人学习下载。借助该工程读者既能掌握14443A协议下防冲突、选卡、认证与块读写的完整流程也能理解15693协议对EPC、TID及用户数据块的访问方法重点代码如ATS响应解析、CLRC63302读取流程均有对应实现能显著缩短非接触式读写模块的开发周期适合有一定STM32基础并希望深入NFC/RFID底层交互的工程师。1. 从 ATS 到读写CLRC663 为什么能同时吃下 14443A 和 15693门禁、图书借阅、工业托盘管理这类设备里CLRC663 是很常见的一颗 NFC 前端。它支持 ISO 14443A/B、ISO 15693还能兼容 MIFARE 系列卡片这意味着同一块电路板既可以读身份证式的小卡也能批量清点货架上的超高频标签。标题里把 14443A 读写块、15693 和读取 ATS 放在一起正好对应项目落地时最常踩的三个坑协议切换时指令帧对不上、块地址映射搞混、ATS 解析不到位。ATSAnswer To Select是 14443A 卡片被选中后回给读写器的协议参数帧决定后续帧的 CID、NAD 以及位速率能否切换。CLRC63302 这类型号在寄存器层与 CLRC663 完全一致所以下文所有代码可以直接照搬到同系列芯片上。先从最底层的命令交互说起。2. CLRC663 寄存器与命令层读写 14443A 块之前先把宿主总线打通2.1 三种主机接口选型SPI/I2C/UART 各自的初始化差异CLRC663 的 EA1 和 EA0 引脚决定主机接口。复位时芯片会锁存这两个引脚的电平组合常见做法是优先选 SPI因为 CLRC663 的 SPI 最高能到 10 Mbps而 I2C 只有 400 kHzUART 模式还要额外组装专用的串口帧反而更麻烦。只做门禁刷卡这种慢速操作I2C 也够用但一旦涉及 15693 多标签轮询或连续多块读取FIFO 里的数据会频繁搬移SPI 的时序余量明显更足。EA1EA0接口最大速率典型场景LLSPI10 Mbps多标签轮询、高速块读写HLI2C400 kHz低功耗门禁、单卡操作LHUART1228.8 kbps旧平台串口直连初始化 SPI 时NSS 不要接硬件片选而是用 GPIO 手动控制。CLRC663 的寄存器读写要求在 NSS 低电平期间完成地址和数据两个字节的交替发送硬件 NSS 的自动翻转会在某些 MCU 上提前拉高导致读回来的数据整体错位。下面是最小读写例程以 STM32 HAL 库为例// CLRC663 SPI 读寄存器地址字节低位置 0 表示读 uint8_t clrc663_read_reg(uint8_t addr) { uint8_t tx[2] { (addr 1) | 0x00, 0x00 }; uint8_t rx[2] { 0, 0 }; CLRC663_NSS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 10); CLRC663_NSS_HIGH(); return rx[1]; } // CLRC663 SPI 写寄存器地址字节低位置 1 表示写 void clrc663_write_reg(uint8_t addr, uint8_t val) { uint8_t tx[2] { (addr 1) | 0x01, val }; CLRC663_NSS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, 10); CLRC663_NSS_HIGH(); }地址左移一位是因为 CLRC663 的总线协议把地址和读写标志位交织在一个字节里。拿到总线读写能力后第一件事是读 ChipID 寄存器确认时序工作正常CLRC663 的 ChipID 固定为 0x07。如果读出来不是 0x07先查 NSS 时序、SPI 模式CPOL0/CPHA0和 3.3V 供电是否稳定不要急着往下调试射频。2.2 FIFO 缓冲与 Command 寄存器的配合——写命令、读状态的基本时序CLRC663 内部有一个深度 512 字节的 FIFO所有对外发送的数据都要先写进 FIFO再通过 TransactionCommand 寄存器触发动作。同理收到的响应也会出现在 FIFO 里。整个交互过程可以封装成一个transceive函数后续 14443A 和 15693 的读写都复用它// 将 data 写入 FIFO 并触发 Transceive等待响应后读回 int clrc663_transceive(const uint8_t *data, uint16_t len, uint8_t *resp, uint16_t max_rsp_len, uint16_t *resp_len) { // 0x0D 是 FIFO 清空命令对应的 Command 值 clrc663_write_reg(REG_COMMAND, CMD_CLEAR_FIFO); for (int i 0; i len; i) { clrc663_write_reg(REG_FIFO_DATA, data[i]); } // 发送并等待响应Transceive 命令 clrc663_write_reg(REG_COMMAND, CMD_TRANSCEIVE); // 轮询 FIFO 长度等待响应数据写入 uint32_t start HAL_GetTick(); while (clrc663_read_reg(REG_FIFO_LENGTH) 0) { if (HAL_GetTick() - start 50) return -1; // 50ms 超时 } uint16_t rlen clrc663_read_reg(REG_FIFO_LENGTH); *resp_len rlen; for (int i 0; i rlen i max_rsp_len; i) { resp[i] clrc663_read_reg(REG_FIFO_DATA); } return rlen; }每次发送前先执行 FIFO 清空是为了避免上一次残留数据混进本次命令。CMD_TRANSCEIVE触发后CLRC663 会自动处理帧起始、CRC 校验和接收窗口MCU 不用介入底层位流。响应数据读出来后还需要检查 ErrorFlag 寄存器如果该寄存器不为 0说明通信过程中发生了 CRC 错误、奇偶校验错误或帧格式错误。这段封装是后面所有示例代码的基础移植时只需要根据具体头文件把REG_COMMAND、REG_FIFO_DATA等宏定义替换成实际地址即可。3. 14443A 协议流程与 CLRC663 读取 ATS 的实现3.1 从 REQA 到 ATS14443A 激活窗口的字节级拆解ISO 14443A 卡片从进入射频场到能够交换数据中间隔着 REQA → ATQA → 防碰撞 → SEL → SAK → RATS → ATS 这一串步骤。REQA 由 CLRC663 自动完成MCU 只需要在收到 SAK 后判断下一步动作。SAK 的 bit6值为 0x20如果被置位说明卡片支持 ISO 14443-4 协议层那就需要发 RATS 并等待 ATS。很多人在这里偷懒SAK 拿到后直接发 APDU结果卡片毫无回应。原因就是跳过了 RATS/ATS 这个“握手”动作。RATS 是一个两字节帧第一个字节固定为 0xE0第二个字节低四位是 CID卡片标识高四位是 FSDI读写器最大帧尺寸指数。我一般发0xE0 0x80即 CID0、FSDI8表示读写器能接收 256 字节的帧。读写器 - 卡片: RATS (E0 80) 卡片 - 读写器: ATS (TL T0 TA TB TC 历史字节)ATS 的第一个字节 TL 表示整个 ATS 的长度第二个字节 T0 的位含义很关键高四位表示后面跟的历史字节数bit30x08表示存在 TAbit20x04表示存在 TBbit10x02表示存在 TC。这些字段决定后续传输的帧尺寸、等待时间和 CID 使用规则。3.2 用 CLRC663 发 RATS 并解析 ATS 响应的 C 代码框架读取 ATS 的完整流程分两步先通过transceive发送 RATS再把响应的字节填充到结构体里。下面是可直接用于项目的代码typedef struct { uint8_t tl; uint8_t t0; uint8_t ta; uint8_t tb; uint8_t tc; uint8_t hist[15]; uint8_t hist_len; } clrc663_ats_t; // 发送 RATS 并读取 ATS 原始字节 int clrc663_read_ats(uint8_t *ats_buf, uint8_t *ats_len) { uint8_t rats[2] { 0xE0, 0x80 }; // CID0, FSDI8, FSD256 uint8_t rsp[32]; uint16_t rsp_len 0; int ret clrc663_transceive(rats, 2, rsp, sizeof(rsp), rsp_len); if (ret 0) return -1; // 检查协议错误标志 if (clrc663_read_reg(REG_ERROR_FLAG) ! 0) return -2; memcpy(ats_buf, rsp, rsp_len); *ats_len (uint8_t)rsp_len; return 0; }解析 ATS 时要注意 T0 的位映射关系。历史字节数存放在 T0 的高四位不要直接用t0 0x0F去取长度那样会把 TA/TB/TC 的标记位混进来// 解析 ATS 结构体buf 是 ATS 原始字节 int clrc663_parse_ats(const uint8_t *buf, uint8_t len, clrc663_ats_t *ats) { if (len 2) return -1; ats-tl buf[0]; ats-t0 buf[1]; ats-ta ats-tb ats-tc 0; ats-hist_len 0; uint8_t idx 2; if (ats-t0 0x08) ats-ta buf[idx]; // TA 存在 if (ats-t0 0x04) ats-tb buf[idx]; // TB 存在 if (ats-t0 0x02) ats-tc buf[idx]; // TC 存在 // 高四位是历史字节数最多 15 字节 ats-hist_len (ats-t0 4) 0x0F; if (ats-hist_len 15) ats-hist_len 15; for (int i 0; i ats-hist_len; i) { ats-hist[i] buf[idx]; } return 0; }clrc663_read_ats里等待响应时依赖transceive中的 FIFO 轮询如果卡片不支持 RATS部分 SAK 不含 0x20 的卡函数会一直等到超时。实际使用时建议把超时时间从 50ms 缩短到 20ms避免在轮询多张卡时卡顿。ATS 的 TL 字段可以用于校验响应是否合法TL 最小为 1最大为 20超过 20 字节直接判定为非法帧。3.3 ATS 里 TL/T0/TA/TB/TC 的意思与参数边界ATS 各字段的用处要在实际项目里落到寄存器配置上。TA 中的 FSCI 字段表示卡片能接收的最大帧尺寸取值从 0 到 8对应 16 到 256 字节。TB 中的 FWI 表示帧等待时间指数SFGI 表示防卡死保护时间指数。TC 的 bit0 表示卡片是否支持 CIDbit1 表示 NAD。这些参数会直接影响后续 APDU 传输的超时设置和分帧逻辑。ATS 字段含义常见值实际影响TLATS 总长度1~20超过 20 判为非法T0 高四位历史字节数0~15决定解析偏移TA帧尺寸和类型FSCIFSD 上限TB等待和保护时间FWI/SFGI超时计算TCCID/NAD 支持bit0/bit1命令帧构造FWI 换算成实际时间要知道 CLRC663 的基准单位1/fc 约等于 73.7nsFWT 等于 256 乘以 2 的 FWI 次方再除以 fc。FWI8 时 FWT 约 4.83msFWI13 时约 154.6ms。所以读 ATS 时如果拿到 FWI8后续命令的超时至少留 6ms否则高速传输时会在等待中断上白白丢卡。4. 15693 读写与 14443A 读写块地址与命令帧的差异4.1 15693 的 Inventory/Read/Write 命令和 14443A 块读写的差异ISO 15693 的帧格式与 14443A 完全不同。15693 的命令帧由命令码、UID 和参数三部分组成读写的最小单位是块Block大多数厂商的块大小固定为 4 字节。而 14443A 的块读写往往走 MIFARE 或 ISO 14443-4 的 APDU块地址、块大小因卡型而异。两者不能直接混用。对比项14443AMIFARE 类15693读取命令READ 0x30Read Single Block 0x20块大小16 字节Classic4 字节常见块地址范围0~63页面翻倍0~255UID 长度4/7/10 字节8 字节指令头命令码 块地址命令码 8 字节 UID 块地址15693 的 Inventory 命令返回的 UID 是 8 字节这比 14443A 的 UID 多出不少导致驱动代码里 buf 操作很容易越界。另一个常见差异是错误响应15693 对不存在的块号返回特定错误码而 14443A 通常是直接无响应。所以写代码时要把超时当成一种正常分支来处理不能只依赖返回值。4.2 用 CLRC663 完成 15693 单块读写的最小代码15693 的 Read Single Block 命令格式是命令码 0x20 加 8 字节 UID 再加 1 字节块号。CLRC663 会自动生成 CRCMCU 只需要填充数据// 15693 读取单个块uid 为 8 字节数组 int nfc15693_read_block(const uint8_t *uid, uint8_t block_no, uint8_t *out) { uint8_t cmd[10]; cmd[0] 0x20; // READ_SINGLE_BLOCK memcpy(cmd 1, uid, 8); cmd[9] block_no; // 块号 0~255 uint8_t rsp[8]; uint16_t rsp_len 0; int ret clrc663_transceive(cmd, 10, rsp, sizeof(rsp), rsp_len); if (ret 0) return -1; // 响应第一个字节为状态0x00 成功0x02 块不存在 if (rsp[0] ! 0x00) return rsp[0]; memcpy(out, rsp 1, 4); // 4 字节块数据 return 0; }Write Single Block 的格式与读类似命令码改为 0x21指令里带上要写入的 4 字节数据响应只有一个状态字节。注意15693 写操作完成后有些标签需要几十毫秒的内部写周期此时不能立即再发下一条命令否则标签可能不响应。我一般会在写操作后加一个 5 到 20ms 的延时具体值看标签数据手册。15693 的多块读取命令是 0x23可以一次读多个块响应数据按块顺序排列。使用多块读取时FIFO 长度可能超过 32 字节要确认transceive函数的接收缓冲区足够大并且把 FIFO 水位中断打开否则数据可能被截断。15693 的 256 字节最多 64 块读满整卡的操作需要分多次进行。4.3 14443A 块读写实操与常见误区14443A 的块读写比 15693 多一层复杂度。以 MIFARE Classic 为例读取一个块要先通过认证认证通过后才能用 READ 命令读 16 字节数据。CLRC663 的认证命令与普通 Transceive 不同它是芯片内部完成密钥计算后直接发认证帧MCU 只需要配置密钥寄存器和认证命令的参数。下面这段代码是在完成认证之后读取块数据的框架// 14443A MIFARE Classic 读块块地址为 block_no int mifare_read_block(uint8_t block_no, uint8_t *out) { // MIFARE Classic 读命令0x30 块地址 CRC uint8_t cmd[4] { 0x30, block_no, 0x00, 0x00 }; // CRC 由 CLRC663 自动追加发送两字节即可 uint8_t rsp[18]; uint16_t rsp_len 0; int ret clrc663_transceive(cmd, 2, rsp, sizeof(rsp), rsp_len); if (ret ! 16) return -1; // 有效响应是 16 字节数据 memcpy(out, rsp, 16); return 0; }这里的关键坑在于 MIFARE Classic 的块地址是从 0 开始按扇区折叠的而 MIFARE Ultralight/NTAG 的块地址直接对应页地址读写都是 4 字节。把 16 字节的 Classic 块大小和 4 字节的 UL 页大小搞混是 14443A 读写出错率最高的原因。另外MIFARE Classic 认证失败时 CLRC663 不会抛出明显的中断只会让后续 Transceive 超时调试时优先确认认证阶段是否成功。5. CLRC663 多协议切换、超时参数和天线调优5.1 协议切换时的复位与模式重配从 14443A 切到 15693或者反过来不能只改命令帧格式。CLRC663 的 TxMode 和 RxMode 寄存器里写死了当前帧的调制方式、位速率和 SOF 模板切换协议时这两个寄存器必须重新配置。我一般会先对芯片执行一次 Soft Reset把内部状态机切回初始态再重新写协议相关的寄存器组最后清空 FIFO。// 协议切换统一入口 void clrc663_switch_to_15693(void) { clrc663_write_reg(REG_COMMAND, CMD_SOFT_RESET); HAL_Delay(5); // 配置 15693 需要的 TxMode/RxMode clrc663_write_reg(REG_TX_MODE, 0xAB); // 15693 高速率 clrc663_write_reg(REG_RX_MODE, 0xAB); // 接收端同步适配 clrc663_write_reg(REG_TX_CONTROL, 0x05); // 打开天线驱动 clrc663_write_reg(REG_COMMAND, CMD_TRANSCEIVE); }tx_mode和rx_mode的具体取值取决于帧格式不要把 14443A 的值沿用过来。Soft Reset 会花掉几毫秒但对多协议切换场景来说比后续丢帧排查要省时间。5.2 超时与位速率参数对照不同协议和不同卡片对响应时间的容忍度差异很大。ATS 里的 FWI 决定事后等待时间15693 自己也有固定等待参数。CLRC663 有专门的定时器寄存器可以设置自动超时避免 MCU 死等。协议典型位速率建议超时对应寄存器14443A106/212/424 kbpsFWT 20% 余量TReload 定时器15693 低速26.48 kbps30 ms同上15693 高速53.97 kbps20 ms同上实际项目里我倾向于把超时做成可配置参数ATS 解析出来以后动态更新定时器初值。FWI8 的卡把超时从 5ms 改到 6ms15693 标签从 20ms 改到 30ms这样既不拖慢轮询也不会在低温或天线欠谐时丢卡。注意 CLRC663 的定时器计数基于内部 13.56MHz 时钟分频配置时要算好分频系数。5.3 一块板子上验证 ATS 和多协议读写的完整流程拿一块 CLRC663 核心板做验证时建议按下面三步走。先读 ChipID 确认 SPI 链路再切到 15693 做一次 Inventory 拿到 UID最后切回 14443A 激活卡片读取 ATS。三步都跑通说明硬件收发和协议切换都正常可以开始业务逻辑开发。# 伪代码顺序用于验证协议切换 # 1. 读 ChipID确认 SPI 正常 chip_id clrc663_read_reg(0x05) # 应为 0x07 # 2. 切到 15693做 Inventory clrc663_switch_to_15693() uid nfc15693_inventory() # 获取 8 字节 UID nfc15693_read_block(uid, 0, buf) # 读第 0 块 # 3. 切回 14443A读 ATS clrc663_switch_to_14443a() clrc663_read_ats(ats_buf, ats_len) # 解析 ATS验证时最容易忽略的是天线匹配。CLRC663 的发射功率取决于天线谐振是否调到 13.56MHz用示波器探头靠近天线线圈看波形只能粗略判断。建议直接用一张已知良好的 14443A 卡片做读 ATS 测试如果 ATS 经常超时但卡片静置时又能偶尔读取成功优先检查天线的 Q 值和匹配电容而不是改软件超时。读写块操作和 ATS 读取用到的底层链路一致链路稳定后所有协议都能正常跑通。本文还有配套的精品资源点击获取