PN532 NFC目标模式实战:从TgInitAsTarget配置到数据交换全解析

📅 2026/6/19 17:14:11
PN532 NFC目标模式实战:从TgInitAsTarget配置到数据交换全解析
1. PN532目标模式从芯片手册到实战应用如果你正在开发一个需要模拟NFC卡片比如门禁卡、公交卡或者构建一个NFC点对点通信的接收端那么PN532芯片的目标Target模式就是你绕不开的核心。官方手册UM0701里那几十页关于TgInitAsTarget、TgGetData的命令描述读起来就像天书各种缩写、状态机、时序图让人头大。我当年第一次啃这块硬骨头时也花了不少时间调试和踩坑。今天我就结合手册内容和实际项目经验把这些命令掰开揉碎了讲清楚让你不仅能看懂协议更能写出稳定可靠的目标端代码。简单来说目标模式就是让PN532芯片“扮演”一个被读写的对象。它可以是一张Mifare卡片ISO/IEC 14443 Type A一个FeliCa设备或者一个NFC点对点通信NFCIP-1的被动端。TgInitAsTarget就是这场“角色扮演”的初始化指令它告诉芯片“准备好有人要来连接你了这是你的新身份信息。”而后续的TgGetData、TgSetData等命令则负责在连接建立后的数据收发对话。很多人觉得这块难是因为它涉及多层协议栈的交互底层的射频激活SENS_REQ, POL_REQ、防碰撞、选择上层的传输协议激活ATR_REQ, RATS再到最终的应用数据交换DEP_REQ, I-Block。PN532的强大之处在于它用硬件和固件帮你处理了底下几层最复杂的部分你只需要通过几个高层命令与之交互。但如果你不理解它内部的状态机调试起来就会像盲人摸象。接下来我们就深入这些命令的细节。1.1 核心命令TgInitAsTarget配置你的目标身份TgInitAsTarget命令码0x8C是进入目标模式的钥匙。它的输入参数相当复杂但理解每个字段的含义是成功配置的关键。命令帧结构D4 8C [Mode] [MifareParams(6字节)] [FeliCaParams(18字节)] [NFCID3t(10字节)] [LEN Gt] [Gt[...]] [LEN Tk] [Tk[...]]1. Mode字节决定你的“人设”Mode字节的低3位bit 0-2是核心它们定义了PN532将接受哪种类型的激活bit 0 (PassiveOnly): 置1表示仅接受被动模式。在被动模式下目标设备你的PN532不产生自己的射频场而是从发起者如手机的场中获取能量并调制反射波进行通信。这是最常见的卡模拟场景。bit 1 (DEPOnly): 置1表示仅接受DEP数据交换协议激活。这意味着发起者必须发送ATR_REQ属性请求帧来建立NFCIP-1点对点连接。如果你的目标是做类似Android Beam的接收端就需要这个。bit 2 (PICCOnly): 置1表示仅接受ISO/IEC 14443-4 PICC接近式卡激活。这意味着发起者读卡器必须发送RATS请求ATS帧。这是模拟一张支持ISO 14443-4高层协议的智能卡比如银行CPU卡所必需的。重要提示DEPOnly和PICCOnly不能同时为1因为这是两种不同的高层协议。但你可以将它们都设为0这样PN532会接受任何一种首先到来的有效激活命令灵活性更高但也需要你的上层代码能处理两种协议。2. MifareParams伪装成Mifare卡当工作在106kbps被动模式模拟Mifare Classic或Ultralight时需要这6个字节SENS_RES (2字节): 这是对SENS_REQ感应请求的响应。通常设置为0x0400表示UID为单尺寸位帧防碰撞。NFCID1t (3字节): 这就是你的“卡号”UID。你可以自定义一个3字节的UID但要确保符合NFCID1的规范例如第一个字节不能是0x08。SEL_RES (1字节): 选择响应。这个字节告诉读卡器你的“卡类型”。0x20: 表示我是一个ISO/IEC 14443-4 PICC准备接收RATS。0x40: 表示我是一个DEP目标准备接收ATR_REQ。0x60: 表示我两者都支持更常见。3. FeliCaParams伪装成FeliCa卡当工作在212/424kbps被动模式模拟FeliCa卡如日本公交卡Suica时需要这18个字节NFCID2t (8字节): FeliCa系统的卡ID。PAD (8字节): 填充字节通常设为0x00。System Code (2字节): 系统码。当发起者的POL_REQ轮询请求第4个字节为0x01时这个系统码会被包含在POL_RES轮询响应中返回。用于标识卡的类型如公交、电子货币。4. NFCID3t, Gt, TkDEP协议的身份与能力协商这些参数主要用于DEP点对点模式NFCID3t (10字节): 在ATR_RES属性响应中发送给发起者的目标NFCID3。在被动212/424kbps模式下它必须等于NFCID2t加上两个0x00填充字节否则激活会被拒绝。Gt (General Bytes): 通用字节用于在ATR_RES中告知发起者目标支持的高级功能和参数如最大传输单元、额外协议。最多47字节。Tk (Historical Bytes): 历史字节用于ISO/IEC 14443-4 PICC模式的ATS选择应答中。最多48字节。可以包含一些厂商自定义信息。命令输出与状态解析发送TgInitAsTarget后PN532会进入自动轮询AutoColl状态等待外部发起者来激活它。一旦被成功激活它会返回一个响应帧D5 8D [Mode] [InitiatorCommand]。返回的Mode字节含义与输入不同它告诉你实际被激活的模式bit 2-0 (Framing Type):00代表Mifare帧01代表主动模式帧10代表FeliCa帧。bit 4 (DEP): 是否为DEP模式。bit 5 (ISO/IEC 14443-4 PICC): 是否为ISO 14443-4 PICC模式。bit 7-5 (Baudrate): 通信速率000106kbps, 001212kbps, 010424kbps。InitiatorCommand数组包含了PN532收到的第一个有效命令帧。这是极其关键的信息根据激活模式的不同它可能是ATR_REQ帧DEP模式RATS帧ISO 14443-4 PICC模式一个专有命令帧某些特殊读卡器的协议你的应用程序需要解析这个InitiatorCommand才能知道下一步该做什么。例如如果收到ATR_REQ你可能需要准备ATR_RES如果未设置自动响应如果收到RATS你可能需要准备ATS。1.2 数据交换的核心TgGetData与TgSetData一旦PN532被成功激活并进入目标状态TG_ACTIVATED或PICC_ACTIVATED数据交换就开始了。这里有两个核心命令TgGetData用于接收数据TgSetData用于发送数据。TgGetData命令码0x86的智慧这个命令看似简单输入只有命令码但其背后的机制是PN532目标模式稳定性的保障。它的工作流程如下主机发送TgGetData命令。PN532立即回复ACK然后开始等待来自发起者如手机的RF数据帧。当PN532收到一个完整的RF帧例如一个DEP_REQ或一个I-Block它不会立刻转发给主机而是先做一件事自动向发起者发送一个等待时间扩展请求。在DEP模式下发送S(TO)REQ超时请求默认请求扩展7 * 154ms ≈ 1.078s。在ISO/IEC 14443-4 PICC模式下发送S(WTX)REQ等待时间扩展请求同样默认扩展约1.078s。发起者回复S(TO)RES或S(WTX)RES同意等待。此时PN532才将收到的应用数据DataIn数组通过TgGetData的响应帧D5 87 [Status] [DataIn]返回给主机。为什么需要这个机制这是为了解决主机响应延迟问题。主控制器比如你的单片机从收到数据到处理完毕并准备回复可能需要几十甚至几百毫秒。而NFC协议有格的超时限制例如FDT for PICC通常在几毫秒到几十毫秒。如果没有这个自动的等待时间扩展发起者会在超时后认为通信失败。PN532的这个设计相当于在协议层为你争取了宝贵的处理时间。TgSetData命令码0x8E的使用当主机通过TgGetData拿到数据并处理完后就需要用TgSetData来回复。你只需要将准备好的响应数据DataOut数组最多262字节通过该命令发送给PN532。PN532会自动处理协议层的事务如组块Chaining、错误处理将数据封装成正确的DEP_RES或I-Block发送给发起者。一个典型的数据交换循环如下循环 { 1. 主机发送: TgGetData 2. PN532回复: ACK然后等待RF数据 3. PN532收到RF数据自动发送S(WTX)REQ等待响应 4. PN532回复主机: D5 87 [Status] [DataIn] 5. 主机处理 DataIn生成 DataOut 6. 主机发送: TgSetData [DataOut] 7. PN532回复: D5 8F [Status]并将DataOut发送给发起者 }1.3 高级数据交换与原始通信对于更复杂的场景PN532还提供了其他命令。TgSetMetaData命令码0x94大数据量发送TgSetData一次最多发送262字节。如果你要发送的数据超过这个限制就需要使用TgSetMetaData。它与TgSetData的关键区别在于当PN532发送最后一个数据块时会在协议数据单元PDU的PFB协议帧字节中设置“更多信息”More Information位为1告知发起者“我后面还有数据请继续用TgGetData来取”。你需要交替使用TgSetMetaData用于中间块和TgSetData用于最后一块来完成整个大数据包的传输。TgGetInitiatorCommand与TgResponseToInitiator绕过协议栈的原始通道这两个命令0x88和0x90是一对它们提供了绕过PN532内部DEP或ISO 14443-4协议处理的原始数据通道。TgGetInitiatorCommand: 获取从发起者发来的原始RF帧数据包括前导码、同步码等。PN532不做任何协议解析直接转发。TgResponseToInitiator: 向发起者发送原始RF帧数据。什么时候用它们当你想实现一个PN532固件不直接支持的私有协议或者需要对通信过程进行极低层的控制时。使用这对命令你需要自己在主机代码中实现完整的协议解析、组帧、超时和重试逻辑复杂度很高。对于绝大多数标准应用DEP, ISO 14443-4强烈建议使用TgGetData/TgSetData让PN532帮你处理协议细节。1.4 状态管理与错误处理在目标模式下清楚PN532处于什么状态至关重要。TgGetTargetStatus命令码0x8A就是你的“状态查询器”。它返回两个字节State (状态):0x00:TG_IDLE/TG_RELEASED- 空闲或已被释放射频场消失。0x01:TG_ACTIVATED- 已激活为NFCIP-1目标。0x02:TG_DESELECTED- 已被发起者取消选择如收到DSL_REQ。0x80:PICC_RELEASED- 作为PICC已被释放。0x81:PICC_ACTIVATED- 已激活为ISO/IEC 14443-4 PICC。0x82:PICC_DESELECTED- 作为PICC已被取消选择如收到S(DESELECT)。BRit (波特率信息): 仅在激活状态下有效。高4位Speed_Initiator和低4位Speed_Target分别表示发起者和目标使用的波特率000106kbps, 001212kbps, 010424kbps。这在PSL参数选择后速率发生变化时非常有用。常见的错误码Status Byte:在TgGetData、TgSetData等命令的响应中Status字节非0x00即表示错误。0x25: 目标处于错误状态无法执行此操作例如在非DEP/PICC模式下调用了数据交换命令。0x29: 目标已被释放射频场丢失。这是最常见的问题之一意味着物理连接中断你需要重新初始化目标。1.5 实战配置示例与避坑指南理论说了这么多我们来点实际的。假设我们要将PN532配置成一个支持DEP和ISO 14443-4 PICC的双模目标工作在106kbps被动模式并模拟一个特定的UID。步骤1配置TgInitAsTarget参数// 假设我们使用以下参数 uint8_t mode 0x00; // PassiveOnly0, DEPOnly0, PICCOnly0 (接受任何激活) uint8_t mifareParams[6] { 0x00, 0x04, // SENS_RES 0x0400 0xAA, 0xBB, 0xCC, // NFCID1t (UID) {0xAA, 0xBB, 0xCC} 0x60 // SEL_RES 0x60 (支持DEP和14443-4) }; // 因为我们主要工作在106kbpsFeliCaParams可以留空或填0 uint8_t felicaParams[18] {0}; // NFCID3t (DEP用)这里简单设置 uint8_t nfcid3t[10] {0}; uint8_t lenGt 0; // 无General Bytes uint8_t lenTk 0; // 无Historical Bytes // 构建命令帧 uint8_t command[256]; int idx 0; command[idx] 0xD4; // PN532命令前缀 command[idx] 0x8C; // TgInitAsTarget命令码 command[idx] mode; memcpy(command[idx], mifareParams, 6); idx 6; memcpy(command[idx], felicaParams, 18); idx 18; memcpy(command[idx], nfcid3t, 10); idx 10; command[idx] lenGt; // 如果lenGt0这里需要追加Gt数组 command[idx] lenTk; // 如果lenTk0这里需要追加Tk数组 // 通过I2C/SPI/UART发送command数组给PN532步骤2处理激活响应发送命令后等待并解析PN532的响应。响应数据会告诉你它被哪种方式激活了。// 假设收到响应数据 response[] if (response[0] 0xD5 response[1] 0x8D) { uint8_t activatedMode response[2]; uint8_t* initiatorCmd response[3]; // InitiatorCommand数组起始 if (activatedMode 0x04) { // Framing Type bits if (activatedMode 0x10) { // DEP bit set printf(Activated as DEP Target.\n); // initiatorCmd 应该是 ATR_REQ 帧 } else if (activatedMode 0x20) { // PICC bit set printf(Activated as ISO 14443-4 PICC.\n); // initiatorCmd 应该是 RATS 帧 } } else { // 可能是Mifare帧或专有命令 printf(Activated in other framing.\n); } }步骤3进入数据交换循环根据激活的模式进入相应的TgGetData/TgSetData循环。避坑指南与实操心得超时管理是重中之重TgInitAsTarget命令本身会阻塞直到有发起者来激活或超时。务必在你的主机代码中设置合理的命令超时例如3-5秒并在超时后重新发送命令或进行其他处理。PN532不会自动退出这个等待状态。fAutomaticATR_RES标志位在调用TgInitAsTarget之前通常需要通过SetParameters命令0x12设置参数。其中一个关键参数是fAutomaticATR_RES位于参数0x05的bit 4。如果设为1默认PN532在收到ATR_REQ后会自动回复ATR_RES。如果设为0则需要你在收到ATR_REQ后手动调用TgSetGeneralBytes命令来触发ATR_RES的发送。注意手动模式下从收到ATR_REQ到发送ATR_RES必须在NFC协议规定的时间内完成通常是几百毫秒否则发起者会超时。TgGetData的阻塞与中断TgGetData会一直等待直到收到数据或发生错误。如果你需要让PN532退出等待例如用户取消操作有两种方法向PN532发送一个新的命令帧任何命令这会中断当前的TgGetData。向PN532发送一个ACK帧0x00 0x00 0xFF这也会中断它。这是更优雅的方式。状态检查在进入数据交换循环前和每次数据交换后定期使用TgGetTargetStatus检查状态。如果状态变为RELEASED或DESELECTED说明连接已断开需要重新执行TgInitAsTarget。缓冲区管理TgGetData返回的DataIn和TgSetData发送的DataOut最大长度都是262字节。确保你的应用层协议数据单元APDU或自定义协议不超过这个限制或者正确实现分片Chaining逻辑。对于DEP模式PN532内部会处理分片你只需要关注应用层数据。调试技巧在开发初期使用一个标准的NFC读卡器App如NFC Tools或另一块PN532配置为发起者来测试你的目标设备。先确保基本的激活和简单数据交换能通再逐步增加复杂的应用逻辑。使用逻辑分析仪或示波器抓取PN532与主机之间的串行通信数据是定位问题最直接的方法。通过深入理解TgInitAsTarget的配置含义、掌握TgGetData/TgSetData的协作机制、并善用状态查询和错误处理你就能基于PN532构建出稳定可靠的NFC目标设备应用。无论是模拟一张门禁卡还是创建一个接收文件的NFC服务端这些命令都是你工具箱里最核心的部件。