简介:这份资源是面向物联网与嵌入式开发者的ST25R3911 NFC控制器演示开发套件,适合在Linux环境下进行NFC读写器、卡模拟及近场通信原型设计的工程师与学习者。压缩包共106个文件,以51个.h头文件和44个.c源文件为主体,另含少量txt说明、html文档、chm手册及png图示,整体约2.74MB,结构紧凑便于快速查阅。内容围绕RFAL射频抽象层展开,涵盖轮询、ISO-DEP、NFC-DEP、NFC-A、NFC-V等协议实现,并附带Poller轮询与NDEF读写等示例,可帮助读者理解ST25R3911的射频接口调用方式,减少底层寄存器操作,加速智能家居配对、门禁控制等物联网场景的验证与原型搭建。目前已有482人学习下载,适合希望借助现成库与示例代码缩短开发周期的中高级开发者参考。
1. 从一块 ST25R3911 评估板说起:NFC 读卡器固件到底该怎么落地
手里拿到一块印着 ST25R3911 的评估板,USB 插上电脑,设备管理器里多出一个串口,然后呢?很多人卡在这一步——知道它是 NFC 前端芯片,知道支持 ISO14443A/B、ISO15693、FeliCa,但真要从零跑通一张卡片的寻卡、防冲突、读写块,中间隔着一整套寄存器配置、中断处理和协议状态机。这份 NFC ST25R3911 demo SDK 就是干这个的:它把芯片的底层驱动、协议栈和几个典型应用场景打包在一起,让你不用从数据手册第一页开始啃寄存器。适合谁?做物联网终端、门禁考勤、产线工装、嵌入式 NFC 模块的固件工程师,尤其是那些需要在 STM32 或类似 MCU 上快速验证 ST25R3911 方案的人。它解决的不是“NFC 是什么”的问题,而是“这块芯片怎么驱动、卡怎么读、数据怎么传”的落地问题。
2. 拆开 SDK 看架构:驱动层、协议层和应用层怎么分工
2.1 目录结构与模块划分
拿到一个 SDK,我习惯先看目录树,而不是急着编译。这份 demo SDK 的典型结构大致如下(不同版本可能有细微差异,但分层逻辑一致):
st25r3911_demo_sdk/ ├── drivers/ │ ├── st25r3911.c/h # 芯片寄存器操作、SPI 读写、中断处理 │ ├── st25r3911_com.c/h # 通信抽象层,SPI/I2C 接口适配 │ └── st25r3911_interrupt.c/h # 中断回调注册与分发 ├── middleware/ │ ├── rfal/ # RF 抽象层,协议状态机 │ │ ├── rfal_nfc.c/h # NFC 模式切换、寻卡、防冲突 │ │ ├── rfal_iso14443a.c/h │ │ ├── rfal_iso14443b.c/h │ │ ├── rfal_iso15693.c/h │ │ └── rfal_felica.c/h │ └── utils/ # 定时器、队列、日志 ├── applications/ │ ├── poller/ # 轮询寻卡示例 │ ├── reader/ # 读写器示例,含块读写 │ └── card_emulation/ # 卡模拟示例(部分 SDK 提供) ├── platform/ │ ├── stm32/ # STM32 平台适配,含 SPI 初始化、GPIO、延时 │ └── generic/ # 通用平台模板 └── docs/ └── api_reference.pdf # 接口说明这个分层的核心思想是:驱动层只负责“把寄存器值写进去、把中断读出来”,协议层负责“按照 ISO 标准组帧、跑状态机”,应用层负责“我要读哪张卡、读哪个块”。三层之间通过函数指针和回调解耦,换 MCU 只需要改 platform 目录,换协议只需要在 middleware 里加文件。
2.2 关键数据结构与状态机
RFAL(RF Abstraction Layer)是这套 SDK 的灵魂。它定义了一个rfalNfc上下文结构体,里面维护了当前协议、卡状态、冲突等级、接收缓冲区等信息。核心状态机大致是:
IDLE → DISCOVERY → ACTIVATED → DATA_EXCHANGE → DEACTIVATED → IDLE每个状态对应一组 API:
| 状态 | 典型 API | 作用 |
|---|---|---|
| DISCOVERY | rfalNfcDiscover() | 启动寻卡,配置协议列表和冲突等级 |
| ACTIVATED | rfalNfcActivate() | 激活选中的卡,获取 UID |
| DATA_EXCHANGE | rfalNfcDataExchange() | 发送 APDU 或读写块 |
| DEACTIVATED | rfalNfcDeactivate() | 释放卡,回到 IDLE |
我一般会先跑applications/poller,因为它只做寻卡和打印 UID,不涉及块读写,最容易验证硬件链路是否通。如果 poller 能稳定输出 UID,说明 SPI 时序、中断配置、天线匹配都没大问题。
2.3 平台适配层要改哪几个地方
换一块新板子,platform 目录下通常要改四个地方:
- SPI 初始化:ST25R3911 支持 SPI 最高 10 MHz,但实际跑 4-6 MHz 更稳。CPOL=0, CPHA=0,MSB first。
- GPIO 配置:至少需要 IRQ(中断输入)、SS(片选输出)、EN(使能输出)三个引脚。IRQ 建议用外部中断下降沿触发。
- 延时函数:
platformDelay()和platformGetTick()要基于 MCU 的 systick 或定时器实现,精度到毫秒即可。 - 中断回调注册:把 MCU 的外部中断服务函数里调用
st25r3911Isr(),让驱动层去读中断寄存器并分发。
// platform/stm32/platform.c 典型适配代码 void platformInit(void) { // 1. SPI 初始化,模式 0,时钟 4 MHz spiInit(SPI1, SPI_MODE0, 4000000); // 2. GPIO 初始化 gpioInit(IRQ_PIN, GPIO_INPUT_PULLUP); gpioInit(SS_PIN, GPIO_OUTPUT_PP); gpioInit(EN_PIN, GPIO_OUTPUT_PP); // 3. 使能芯片 gpioWrite(EN_PIN, 1); platformDelay(10); // 等待芯片内部上电复位 // 4. 注册中断回调 extiRegisterCallback(IRQ_PIN, st25r3911Isr); }这段代码的逻辑是:先配 SPI 和 GPIO,再拉高 EN 让芯片上电,最后把外部中断绑到驱动层的 ISR。参数上,SPI 时钟不要一上来就拉满,4 MHz 是个稳妥起点;EN 拉高后的 10 ms 延时不能省,否则芯片内部寄存器还没复位完,后续读写全是 0xFF。
3. 跑通第一个寻卡 demo:从编译到串口输出 UID
3.1 编译环境与依赖
这份 SDK 通常提供 Makefile 和 IAR/Keil 工程模板。我一般用 Makefile + arm-none-eabi-gcc,因为方便在 CI 里跑。依赖只有标准外设库或 HAL 库,不需要 RTOS。编译前确认三个宏:
# Makefile 关键配置 PLATFORM = stm32 PROTOCOLS = ISO14443A ISO14443B ISO15693 APP = poller DEBUG = 1PROTOCOLS决定编译哪些协议文件,如果只做门禁卡,只留 ISO14443A 能省不少 flash。DEBUG=1会打开串口日志,方便看状态机流转。
3.2 寻卡流程与代码走读
poller 示例的主循环简化后是这样:
// applications/poller/main.c int main(void) { platformInit(); rfalNfcInit(); // 初始化 RFAL,配置协议列表 while (1) { rfalNfcDiscover(); // 启动寻卡,超时 500ms if (rfalNfcGetState() == RFAL_NFC_STATE_ACTIVATED) { uint8_t uid[10]; uint8_t uidLen = rfalNfcGetUid(uid); printf("UID: "); for (int i = 0; i < uidLen; i++) { printf("%02X ", uid[i]); } printf("\n"); rfalNfcDeactivate(); // 释放卡,准备下一轮 } platformDelay(100); } }逻辑说明:rfalNfcInit()会初始化芯片寄存器、配置天线、打开中断。rfalNfcDiscover()是一个阻塞调用,内部会跑防冲突循环,直到找到一张卡或超时。找到卡后状态变为 ACTIVATED,rfalNfcGetUid()从上下文里取 UID。最后必须调rfalNfcDeactivate(),否则卡会一直处于激活态,下一轮寻卡会失败。
参数上,rfalNfcDiscover()的超时时间可以改,默认 500 ms。如果现场卡片多、冲突频繁,可以加到 1 s。但不要超过 2 s,否则用户体验明显变差。
3.3 串口日志怎么看
DEBUG=1 时,串口会输出类似:
[RFAL] Discovery start, protocols: A/B/V [RFAL] ISO14443A: REQA sent [RFAL] ISO14443A: ATQA received: 0x0004 [RFAL] ISO14443A: UID CL1: 0x32 0x45 0x67 0x89 [RFAL] Card activated, UID len: 4 UID: 32 45 67 89如果卡在REQA sent之后没有ATQA received,说明天线没调好或者卡没靠近。如果ATQA received但 UID 读出来全是 0,检查 SPI 读时序,尤其是 MISO 采样边沿。
4. 避坑与排查:SPI 时序、天线匹配和中断丢失
4.1 现象:SPI 读回来全是 0xFF 或 0x00
原因:SPI 模式不对,或者片选时序有问题。ST25R3911 要求 CPOL=0, CPHA=0,且片选在每字节传输前拉低、传输后拉高。有些 MCU 的硬件 SPI 片选是自动控制的,但 ST25R3911 需要手动控制 SS。
解决:把 SS 配成普通 GPIO 输出,在st25r3911SpiTransfer()里手动拉低/拉高。另外检查 MISO 上是否有上拉,ST25R3911 的 MISO 是推挽输出,不需要外部上拉,但如果走线长,加一个 10 kΩ 上拉能改善波形。
4.2 现象:寻卡距离短,只有 1-2 cm
原因:天线匹配网络没调好。ST25R3911 的天线端需要 LC 匹配,典型值 1 μH 电感 + 几十 pF 电容,具体值取决于天线线圈的电感量。如果匹配电容偏差大,谐振频率偏离 13.56 MHz,读卡距离会急剧下降。
解决:用网络分析仪测天线端的 S11,确保在 13.56 MHz 处回波损耗小于 -20 dB。没有网分的话,用示波器看天线驱动波形,调整匹配电容直到幅度最大。我一般会预留两个可调电容位置,方便现场微调。
4.3 现象:中断偶尔丢失,寻卡时好时坏
原因:IRQ 引脚是电平触发还是边沿触发没配对。ST25R3911 的 IRQ 是低电平有效,且中断寄存器是“读后清零”。如果 MCU 配成边沿触发,而中断服务函数里没有及时读中断寄存器,IRQ 会一直拉低,导致后续中断丢失。
解决:把 IRQ 配成下降沿触发,并在 ISR 里第一时间调用st25r3911Isr(),让它读中断寄存器并清除标志。如果 MCU 支持电平触发,用低电平触发更稳,但要注意 ISR 里必须清中断,否则会反复进中断。
4.4 现象:多张卡同时靠近时,只能读到一张
原因:防冲突等级设低了。RFAL 默认的冲突等级是 1,只能处理单卡。如果现场有多张卡,需要把冲突等级调到 2 或 3。
解决:在rfalNfcInit()之后调用rfalNfcSetConflictLevel(2)。但注意,冲突等级越高,寻卡时间越长。实际项目中,如果只是门禁场景,单卡足够;如果是产线工装,可能需要多卡同时读,那就得权衡。
4.5 现象:读块数据时返回 CRC 错误
原因:块读写时,数据长度和 CRC 校验没对齐。ISO14443A 的块读写需要先发送命令帧,再发送数据帧,每帧末尾自动附加 CRC。如果手动组帧时多算或少算了一个字节,CRC 就会错。
解决:直接用 RFAL 提供的rfalNfcDataExchange(),不要自己组帧。如果必须手动组帧,确保 CRC 计算覆盖从命令字节到数据最后一个字节。常见做法是调用rfalCrcCalculate()并附加到帧尾。
5. 进阶用法:把 ST25R3911 当成物联网节点的一环
5.1 低功耗轮询与唤醒策略
物联网终端往往电池供电,ST25R3911 的功耗直接决定续航。芯片支持多种低功耗模式,我一般这样配:
| 模式 | 电流 | 唤醒方式 | 适用场景 |
|---|---|---|---|
| Ready | 10 mA | 寄存器写 | 持续寻卡 |
| Idle | 1 mA | 定时器 | 间歇寻卡 |
| Sleep | 10 μA | 外部中断 | 待机唤醒 |
| Power-down | 1 μA | EN 引脚 | 长期休眠 |
实际策略是:MCU 定时器每 500 ms 唤醒一次,拉高 EN,调rfalNfcDiscover()超时 100 ms,没卡就立刻进 Sleep。这样平均电流能压到 200 μA 以下,一颗 2000 mAh 电池撑一年没问题。
5.2 把 UID 上传到 MQTT 的完整链路
寻到卡只是第一步,物联网场景要把 UID 传到云端。我一般用 ESP32 或 STM32+ESP8266 做网关,ST25R3911 通过 SPI 接 MCU,MCU 通过 UART 接 Wi-Fi 模组。数据流是:
// 寻到卡后,组 JSON 并通过 MQTT 发布 void onCardDetected(uint8_t *uid, uint8_t uidLen) { char payload[64]; snprintf(payload, sizeof(payload), "{\"uid\":\"%02X%02X%02X%02X\",\"ts\":%lu}", uid[0], uid[1], uid[2], uid[3], platformGetTick()); mqttPublish("nfc/uid", payload); }这里的关键是 UID 格式统一:读卡器输出十六进制字符串,云端按同样格式解析。时间戳用 MCU 的 tick,精度到秒即可。如果现场有多台读卡器,payload 里还要加设备 ID,避免数据混淆。
5.3 验证方法:用逻辑分析仪抓 SPI 波形
调 ST25R3911 最怕“玄学”问题——代码没改,昨天能读今天不能读。我的血泪经验是:任何 SPI 通信异常,先抓波形,别猜。逻辑分析仪接上 SCK、MOSI、MISO、SS 四根线,触发条件设成 SS 下降沿,抓一帧完整的寄存器读操作。正常波形应该是:SS 拉低 → 发送地址字节 → 接收数据字节 → SS 拉高。如果 MISO 在数据字节期间是高的,说明芯片没响应,检查 EN 和晶振。如果 SCK 频率明显偏高,检查 SPI 分频系数。
从那以后我每次改完 platform 层,都强制走一遍“抓波形 → 对数据手册时序图 → 确认无误再跑代码”的流程。希望帮到你。
本文还有配套的精品资源,点击获取