两个 CH58X 芯片通过 NFC-A 实现 FIFO 数据透传
CH58X 系列(CH585/CH584)的 NFC-A 外设同时支持 PCD(Proximity Coupling Device,读卡器)和 PICC(Proximity Integrated Circuit Card,卡模拟)两种角色。FIFO(First In First Out,先进先出队列)是数据透传的核心——双方各维护一个发送队列,上层应用往里塞数据,通信层在每次交互时取出并发送。SDK 没有提供开箱即用的"透传模式",但底层的 PCD(读卡器)/PICC(卡模拟)API 允许在 ISO 14443-3A 传输层之上构建任意数据交换。一块芯片做读卡器发起命令,另一块做卡响应,双方通过自定义命令码通信。
核心思路
PCD(读卡器)主动发起通信,PICC(卡模拟)被动响应。ISO 14443-3A 的标准选择流程——REQA(Request,寻卡指令)→ ANTICOLL(Anti-Collision,防冲突)→ SELECT(选中)——完成后,PICC(卡模拟)进入 ACTIVE 状态。在此状态下,data_handler 回调可以识别自定义命令码,读写任意数据缓冲区。每次数据交换由 PCD(读卡器)发起一帧,PICC(卡模拟)回复一帧。
两端如何收发数据
PCD(读卡器)端——主动发、等回复:
PCD(读卡器)是通信的发起方。先把要发送的数据填入 g_nfca_pcd_send_buf,调用 nfca_pcd_communicate(data_bits_num, recv_mode, offset)——前两个参数指定要发送的比特数和接收模式,第三个参数 offset 是首比特在首字节中的偏移(0-7),通常填 0。芯片会自动把数据调制成 13.56 MHz 射频信号发射出去,发完后自动切换到接收模式,等待 PICC(卡模拟)回复。轮询 nfca_pcd_get_communicate_status 直到状态变为 DONE,回复数据就从 g_nfca_pcd_recv_buf 里读,长度用 nfca_pcd_get_recv_data_len 获取。
整个过程是:填发送缓冲 → 调 communicate → 等状态结束 → 读接收缓冲。
PICC(卡模拟)端——被动收、填回复:
PICC(卡模拟)靠读卡器的射频场供电,不能主动发数据。SDK 通过回调机制处理通信:注册一个 nfca_picc_cb_t 结构体,其中 data_handler 是核心回调,在中断里被调用。每当读卡器发来一帧数据,g_picc_data_buf 里就是收到的内容,bits_num 是比特数。你在这个回调里解析命令、准备响应:把回复数据写入同一个 g_picc_data_buf,校验位写入 g_picc_parity_buf,最后返回回复的比特数,芯片自动完成后续的射频回复。
整个过程是:data_handler 被触发 → 读 g_picc_data_buf → 写回复到同一缓冲 → 返回比特数。
通信流程
- PCD(读卡器)发 REQA(寻卡,0x26)/WUPA(唤醒,0x52)→ PICC(卡模拟)回 ATQA(寻卡应答)→ PICC(卡模拟)进入 READY 状态
- PCD(读卡器)执行 ANTICOLL(防冲突,0x93)+ SELECT(选中)→ PICC(卡模拟)回 UID(唯一标识符)+ SAK(选中确认)→ PICC(卡模拟)进入 ACTIVE 状态
- ACTIVE 状态下,PCD(读卡器)可发送任意自定义命令码,PICC(卡模拟)的
data_handler处理并回复
PICC(卡模拟)端可以参考 SDK 中 T2T(Type 2 Tag,NFC Forum 定义的标签标准之一)示例(wch_nfca_picc_t2t.c)的 NFCA_PICC_T2T_STATE_ACTIVE 分支,把 READ/WRITE 等标准命令替换为自己的协议即可。
FIFO 交换帧格式
PCD(读卡器)必须持续轮询才能维持通信,每次轮询本身就是一次数据交换机会。一个实用的做法是双方各维护一个发送 FIFO,每次帧交互时顺带捎上自己的 FIFO 数据:
PCD → PICC 帧:
| 字段 | 字节 | 说明 |
|---|---|---|
| 命令码 | 1 | 自定义值(如 0xF0),标识这是数据交换帧 |
| 序号/确认 | 1 | 高 4 位:本次发送的序号(0-15);低 4 位:已收到的对方上一帧序号(ACK) |
| 数据长度 | 1 | 本次携带的 FIFO 数据字节数,无数据时填 0 |
| 数据 | 变长 | FIFO 中取出的数据,长度由上字段决定 |
| CRC | 2 | ISO 14443-3A 标准 CRC_A |
PICC → PCD 响应帧(不含命令码,其余结构相同):
| 字段 | 字节 | 说明 |
|---|---|---|
| 序号/确认 | 1 | 高 4 位:本次发送的序号;低 4 位:已收到的对方上一帧序号(ACK) |
| 数据长度 | 1 | 本次携带的 FIFO 数据字节数,无数据时填 0 |
| 数据 | 变长 | FIFO 中取出的数据 |
| CRC | 2 | ISO 14443-3A 标准 CRC_A |
序号和确认的设计:
- 一字节掰两半:高 4 位是自己的序号(0-15 循环),低 4 位是对上一帧的确认。NFC 单帧数据量有限,每字节都要省着用,合并到一个字节里零额外开销。
- 捎带确认:第一帧的确认在第二帧里完成。发送方每发出一帧,同时告诉对方"你上一帧我收到了"。如果序号连续递增且超时没收到 ACK,发送方就知道上一帧丢了。
- PICC(卡模拟)响应帧不需要命令码——PICC(卡模拟)是被动方,收到 PCD(读卡器)命令后回复,双方都知道当前处于哪一轮交互。
关键点:
- 没有数据也要轮询。长度字段填 0,仍然发完整帧,序号照常递增、ACK 照常捎带。通道始终保持活跃,上层随时可以塞数据进来。
- 每一轮都是双向的。PCD(读卡器)发一帧带走自己的 FIFO 数据,PICC(卡模拟)回复时顺带捎上自己的 FIFO 数据。一轮轮询完成一次双向交换。
- PCD(读卡器)侧在主循环里反复调用
nfca_pcd_communicate(每次调用会阻塞直到通信完成或超时),PICC(卡模拟)侧在data_handler里识别 0xF0 命令码,读出对方数据、填入自己的 FIFO 数据、返回响应。
断连与恢复
PICC(卡模拟)离开射频场后,PCD(读卡器)的 nfca_pcd_communicate 会超时返回错误。PCD(读卡器)侧需要在主循环中检测这个状态:超时后停止对当前卡的轮询,回到 REQA 重新寻卡。
PICC(卡模拟)侧离场后回调 offline 被触发,状态回到 IDLE。重新进入射频场时回调 online 被触发,之后必须重新走完整的选卡流程(REQA → ANTICOLL → SELECT)才能再次进入 ACTIVE 状态接收数据帧。
这意味着上层协议要能接受通信中断:数据 FIFO 在断连期间可能积压,恢复后需从断点继续,而不应假设通道永远在线。
传输中途断开的数据保护
断连最棘手的情况发生在发送中途——已经从 FIFO 取出了数据,但 NFC 发送失败,数据不能丢,又不能塞回 FIFO。用暂存区解决:
- 从内部 FIFO 取出一定量数据,放到一个暂存区(staging buffer)
- 用暂存区里的数据组帧,尝试 NFC 发送
- 发送成功 → 暂存区清空,下次从 FIFO 取新数据
- 发送失败(超时/断连)→ 暂存区数据保留,下次轮询时直接用暂存区里的数据重试,无需动 FIFO
这样数据流向是单向的:FIFO → 暂存区 → NFC 发送。数据从 FIFO 取出后只往前走,失败就停在暂存区里等下次重试,不需要回退。
至于链路层的丢包或 CRC 校验失败,不在这一层处理——保持通信层轻量,数据完整性校验和重传策略交给上层应用决定。
限制
- PICC(卡模拟)只能响应,无法主动发起传输,双向通信必须由 PCD(读卡器)端轮询
- 单帧大小受 SDK 默认缓冲限制——PCD(读卡器)发送缓冲默认 32 字节(
NFCA_PCD_DATA_BUF_SIZE),PICC(卡模拟)数据缓冲默认 20 字节(PICC_DATA_BUF_LEN)。如需更大帧可自行增大这些宏,但需确保 RAM 足够 - 速率固定 106 kbps(ISO 14443A 标准),不支持更高波特率
- PICC(卡模拟)的
data_handler在中断中运行,不可长时间阻塞;ATQA 回复必须在约 90 μs 内发出