☰
ST25R3911 NFC读卡Demo SDK实战:从寻卡到低功耗设计
2026/10/15 16:27:01 网站建设 项目流程

简介:这份资源是面向嵌入式与物联网开发者的NFC开发套件,基于意法半导体ST25R3911高性能NFC控制器,适用于读写器与卡模拟应用场景。包内共106个文件,以51个.h头文件与44个.c源文件为主体,另含txt说明、html文档、chm帮助手册及少量配置与图片文件,压缩包约2.74MB,整体结构紧凑,便于在Linux环境下快速搭建工程。内容覆盖RFAL射频抽象层、ISO-DEP与NFC-DEP协议栈、NFC-A/NFC-V轮询、NDEF读写与卡模拟等模块,并附带轮询与NDEF演示示例,可帮助开发者理解底层射频通信流程,减少直接操作寄存器的重复工作。已有482人学习下载,适合具备一定嵌入式基础、希望将NFC功能集成到智能家居配对、门禁控制或数据交换等物联网设备中的开发者参考。

1. 从一块 ST25R3911 评估板说起:NFC 读卡 Demo 到底难在哪

很多人第一次拿到 ST25R3911 的评估板,插上 USB、跑通厂商给的 GUI,看到能读出卡片 UID,就觉得 NFC 读卡不过如此。但真正把这块芯片做进产品、自己写固件、自己调天线、自己处理协议栈的时候,才会发现 Demo 和量产之间隔着一整条鸿沟。ST25R3911 是一颗高性能 NFC 通用读写芯片,支持 ISO14443A/B、ISO15693、FeliCa 等多种协议,发射功率和接收灵敏度都做得不错,但它本质上只是一颗模拟前端加协议加速器,寄存器配置、FIFO 管理、中断处理、低功耗状态机全都要靠主控 MCU 去驱动。所谓 NFC ST25R3911 Demo SDK,核心价值就在于把「芯片怎么初始化、怎么发命令、怎么收响应、怎么解析协议」这条链路用可复现的代码固定下来,让开发者不用从数据手册第一页啃到最后一页。这篇文章面向的是准备用 ST25R3911 做读卡器、门禁、标签读写或者支付终端的嵌入式工程师,我会把 SDK 的结构、关键寄存器配置、天线调谐、协议层实现和常见翻车点拆开讲清楚,让你拿到板子之后能少走弯路。

2. 拆解 ST25R3911 Demo SDK 的目录结构与启动链路

2.1 SDK 里到底有哪些模块,各自负责什么

一个典型的 ST25R3911 Demo SDK 不会只有一个 main.c,它通常按职责分成几个层次。最底层是寄存器访问层,负责通过 SPI 读写 ST25R3911 的内部寄存器,这一层要处理 SPI 时序、片选控制、以及芯片上电后的复位序列。往上一层是芯片驱动层,封装了初始化、发射功率设置、接收增益设置、振荡器启动、场开启关闭这些操作。再往上是协议层,针对 ISO14443A 这类协议实现防冲突、选卡、认证、读写块等流程。最上面是应用层,也就是 Demo 的主循环,通常做的是轮询寻卡、读 UID、打印信息。

我一般会把 SDK 的目录理解成四块:bsp/放 SPI 和 GPIO 的板级支持,st25r3911/放芯片寄存器和驱动,iso14443a/放协议实现,app/放业务逻辑。这种分层的好处是换主控的时候只需要改bsp/,换协议的时候只需要改协议层,芯片驱动基本不动。

2.2 上电到寻卡:一条完整的启动链路

ST25R3911 上电之后不能直接发寻卡命令,必须按顺序完成几个动作。第一步是硬件复位,拉低再拉高复位引脚,或者通过 SPI 写复位寄存器。第二步是配置振荡器,ST25R3911 支持外部晶振和内部 RC 两种时钟源,Demo 里通常用外部晶振,因为 NFC 载波对时钟精度要求高。第三步是设置发射和接收参数,包括发射调制深度、接收增益、AGC 策略。第四步是开启射频场,让天线开始辐射 13.56MHz 载波。第五步才是发寻卡命令。

下面这段代码展示的是初始化序列的核心部分,用伪代码风格写,方便你对照自己的平台移植:

/* st25r3911_init: 芯片上电初始化序列 */ int st25r3911_init(st25r3911_dev_t *dev) { /* 1. 硬件复位:拉低复位脚至少 100us 再拉高 */ bsp_reset_pin_low(); delay_us(200); bsp_reset_pin_high(); delay_ms(5); /* 等待内部稳压和振荡器稳定 */ /* 2. 检查芯片 ID,确认 SPI 通信正常 */ uint8_t ic_id = 0; st25r3911_read_reg(dev, ST25R3911_REG_IC_IDENTITY, &ic_id); if ((ic_id & 0x0F) != 0x05) { return -1; /* IC_IDENTITY 低四位应为 0x05 */ } /* 3. 配置振荡器:使能外部晶振,等待稳定 */ st25r3911_write_reg(dev, ST25R3911_REG_OSC, 0x00); st25r3911_write_reg(dev, ST25R3911_REG_OSC, 0x01); /* 使能晶振 */ delay_ms(10); /* 晶振起振时间,典型 5-10ms */ /* 4. 设置发射和接收参数 */ st25r3911_write_reg(dev, ST25R3911_REG_TX_DRIVER, 0x00); /* 发射驱动配置 */ st25r3911_write_reg(dev, ST25R3911_REG_RX_GAIN, 0x08); /* 接收增益,视天线而定 */ /* 5. 开启射频场 */ st25r3911_write_reg(dev, ST25R3911_REG_OP_CONTROL, 0x80); /* 使能场 */ delay_ms(5); /* 等待场稳定 */ return 0; }

这段代码里最关键的参数是ST25R3911_REG_IC_IDENTITY的返回值,如果读出来不是 0x05,说明 SPI 通信有问题,后面所有操作都不用做了。晶振起振时间给 10ms 是保守值,实际用示波器看 OSC 引脚,稳定时间通常在 3 到 5ms 之间,但不同批次芯片和不同负载电容会有差异,宁可多等。接收增益RX_GAIN不是越大越好,增益太高会把环境噪声也放大,导致寻卡不稳定,这个后面避坑章节会细说。

2.3 SPI 访问的时序细节与寄存器读写封装

ST25R3911 的 SPI 接口支持最高 10MHz 时钟,但实际跑的时候要看主控和走线。Demo SDK 里通常会把 SPI 模式设成 Mode 0 或者 Mode 3,具体看数据手册的时序图。读写寄存器的时候,第一个字节是地址加读写标志位,读操作最高位为 1,写操作为 0,后面跟数据字节。

/* 读寄存器:地址最高位置 1 表示读 */ uint8_t st25r3911_read_reg(st25r3911_dev_t *dev, uint8_t addr, uint8_t *val) { uint8_t tx_buf[2]; uint8_t rx_buf[2]; tx_buf[0] = addr | 0x80; /* 读标志 */ tx_buf[1] = 0x00; /* 占位,读操作时忽略 */ bsp_spi_cs_low(); bsp_spi_transfer(tx_buf, rx_buf, 2); bsp_spi_cs_high(); *val = rx_buf[1]; return 0; } /* 写寄存器:地址最高位为 0 */ uint8_t st25r3911_write_reg(st25r3911_dev_t *dev, uint8_t addr, uint8_t val) { uint8_t tx_buf[2]; tx_buf[0] = addr & 0x7F; /* 写标志 */ tx_buf[1] = val; bsp_spi_cs_low(); bsp_spi_transfer(tx_buf, NULL, 2); bsp_spi_cs_high(); return 0; }

这里有个容易忽略的点:片选拉低到第一个时钟沿之间要有足够的建立时间,ST25R3911 要求至少 50ns,一般 SPI 外设都能满足,但如果你用 GPIO 模拟 SPI,就要在代码里加延时。另外连续读写多个寄存器的时候,ST25R3911 支持地址自增模式,可以一次片选传输多个字节,能显著提高 FIFO 读写效率。

3. 用 Demo SDK 跑通 ISO14443A 寻卡与读 UID 的完整流程

3.1 寻卡命令 REQA 与防冲突的实现

ISO14443A 的寻卡流程分两步:先发 REQA(短帧,7 位),卡片返回 ATQA;再发防冲突命令,卡片返回 UID 的级联部分。ST25R3911 的 FIFO 和协议加速器能帮你处理一部分位级时序,但防冲突的循环逻辑还是要自己写。

/* 发送 REQA 并读取 ATQA */ int iso14443a_request(st25r3911_dev_t *dev, uint8_t *atqa) { uint8_t cmd = 0x26; /* REQA 命令码 */ uint8_t response[2]; /* 清空 FIFO,写入命令 */ st25r3911_write_reg(dev, ST25R3911_REG_FIFO_CTRL, 0x80); /* 清 FIFO */ st25r3911_write_fifo(dev, &cmd, 1); /* 配置为短帧发送,7 位 */ st25r3911_write_reg(dev, ST25R3911_REG_TX_BITS, 0x07); /* 启动发送,等待接收 */ st25r3911_write_reg(dev, ST25R3911_REG_CMD, 0xC0); /* 发送并接收 */ /* 等待中断或轮询状态寄存器 */ if (st25r3911_wait_rx_complete(dev, 10) != 0) { return -1; /* 超时,没有卡片响应 */ } /* 读取 ATQA,2 字节 */ st25r3911_read_fifo(dev, response, 2); atqa[0] = response[0]; atqa[1] = response[1]; return 0; }

ST25R3911_REG_TX_BITS设为 0x07 表示只发 7 位,这是 REQA 短帧的要求。如果你设成 8 位,卡片不会响应,这是新手最容易踩的坑之一。等待接收的超时时间给 10ms 是经验值,ISO14443A 规定卡片必须在 5ms 内响应,但实际环境里留点余量更稳。

3.2 从 ATQA 到完整 UID:级联处理的代码结构

拿到 ATQA 之后,根据 ATQA 的 bit 位可以判断 UID 长度是 4 字节、7 字节还是 10 字节。防冲突流程要用 SEL 命令逐级获取 UID,每一级都要处理冲突位的检测和选择。

/* 简化的防冲突流程:获取完整 UID */ int iso14443a_anticollision(st25r3911_dev_t *dev, uint8_t *uid, uint8_t *uid_len) { uint8_t sel_cmd[2] = {0x93, 0x20}; /* SEL + NVB=0x20,请求完整 UID */ uint8_t response[5]; uint8_t cascade_level = 0; uint8_t uid_index = 0; while (cascade_level < 3) { /* 发送 SEL 命令 */ st25r3911_write_reg(dev, ST25R3911_REG_FIFO_CTRL, 0x80); st25r3911_write_fifo(dev, sel_cmd, 2); st25r3911_write_reg(dev, ST25R3911_REG_TX_BITS, 0x00); /* 完整字节 */ st25r3911_write_reg(dev, ST25R3911_REG_CMD, 0xC0); if (st25r3911_wait_rx_complete(dev, 10) != 0) { return -1; } /* 读取响应:4 字节 UID + 1 字节 BCC */ st25r3911_read_fifo(dev, response, 5); /* 检查 BCC 校验 */ if ((response[0] ^ response[1] ^ response[2] ^ response[3]) != response[4]) { return -2; /* BCC 校验失败 */ } /* 判断是否级联:response[0] 为 0x88 表示还有下一级 */ if (response[0] == 0x88) { /* 级联标志,实际 UID 从 response[1] 开始 */ memcpy(&uid[uid_index], &response[1], 3); uid_index += 3; cascade_level++; /* 更新 SEL 命令的级联级别 */ sel_cmd[0] = 0x93 + cascade_level * 2; } else { memcpy(&uid[uid_index], &response[0], 4); uid_index += 4; break; } } *uid_len = uid_index; return 0; }

这段代码里 BCC 校验是必须做的,它能帮你过滤掉一部分误读。级联级别从 0x93 到 0x95 再到 0x97,对应 Cascade Level 1/2/3。如果你的卡片是 4 字节 UID,循环一次就结束了;7 字节 UID 会走两次;10 字节 UID 走三次。实际调试的时候,建议把每一级读到的原始字节都打印出来,方便对照逻辑分析仪抓到的波形。

3.3 读块与写块:用 Demo SDK 操作 Mifare Classic 数据区

寻卡拿到 UID 之后,如果要读写 Mifare Classic 的块数据,还需要经过认证流程。认证用的是 Crypto1 算法,ST25R3911 内部有硬件加速,但密钥和认证流程还是要软件控制。

/* Mifare Classic 认证与读块 */ int mifare_read_block(st25r3911_dev_t *dev, uint8_t block_addr, uint8_t *key, uint8_t *data) { uint8_t auth_cmd[12]; uint8_t response; /* 1. 发送认证命令 0x60(Key A)或 0x61(Key B) */ auth_cmd[0] = 0x60; /* 用 Key A 认证 */ auth_cmd[1] = block_addr; memcpy(&auth_cmd[2], key, 6); /* 后面 4 字节是卡片返回的随机数,由硬件处理 */ st25r3911_write_reg(dev, ST25R3911_REG_FIFO_CTRL, 0x80); st25r3911_write_fifo(dev, auth_cmd, 2); /* 先发命令和块地址 */ st25r3911_write_reg(dev, ST25R3911_REG_CMD, 0xC0); /* 等待卡片返回随机数,然后硬件自动完成 Crypto1 认证 */ if (st25r3911_wait_rx_complete(dev, 20) != 0) { return -1; } /* 2. 认证通过后发送读命令 0x30 */ uint8_t read_cmd = 0x30; st25r3911_write_reg(dev, ST25R3911_REG_FIFO_CTRL, 0x80); st25r3911_write_fifo(dev, &read_cmd, 1); st25r3911_write_reg(dev, ST25R3911_REG_CMD, 0xC0); if (st25r3911_wait_rx_complete(dev, 10) != 0) { return -2; } /* 3. 读取 16 字节块数据 */ st25r3911_read_fifo(dev, data, 16); return 0; }

认证超时给 20ms 是因为 Crypto1 认证涉及多次随机数交换,比普通寻卡慢。读块命令 0x30 返回固定 16 字节,不需要额外判断长度。写块命令是 0xA0,流程类似,但写完之后要等卡片返回 ACK 才能发下一个命令。Mifare Classic 的密钥认证是很多门禁项目的基础,但要注意 Key A 和 Key B 的权限区别,有些块只能用 Key B 写。

4. 天线调谐与低功耗设计:Demo 之外必须自己补的课

4.1 天线匹配网络的三个关键参数

Demo SDK 通常不会告诉你天线怎么调,但这是决定读卡距离和稳定性的核心。ST25R3911 的天线接口是差分输出,需要匹配网络把芯片输出阻抗匹配到天线的谐振频率 13.56MHz。匹配网络一般是 LC 低通加谐振电容,关键参数有三个:谐振频率、品质因数 Q、以及匹配阻抗。

谐振频率由天线电感和并联电容决定,公式是 f = 1/(2π√(LC))。实际调试的时候,用网络分析仪看天线端的阻抗曲线,谐振点应该落在 13.56MHz,偏差超过 500kHz 就会明显影响读卡距离。品质因数 Q 影响带宽和选择性,Q 太高带宽窄,对卡片频率偏移容忍度低;Q 太低则发射效率下降。一般 Q 值控制在 20 到 40 之间比较合适。匹配阻抗方面,ST25R3911 的差分输出阻抗典型值是 4 欧姆左右,需要通过匹配网络变换到天线的 50 欧姆或者直接匹配到天线线圈的阻抗。

我一般会先用仿真工具算一遍匹配参数,然后打样测试,用示波器看天线两端的电压峰峰值。如果峰峰值偏低,说明匹配没做好或者发射功率设置不够;如果波形失真严重,说明 Q 值太高或者驱动能力不足。

4.2 低功耗模式:场关闭、待机和唤醒的取舍

ST25R3911 支持多种低功耗模式,Demo SDK 里通常只用了最简单的场开启和关闭。实际产品里,如果是电池供电的门禁或者手持机,低功耗设计直接决定续航。

场关闭模式是最省电的,芯片大部分模块断电,只保留 SPI 接口和少量寄存器状态,电流可以降到微安级。待机模式保留振荡器和部分接收链路,能快速响应外部唤醒,但电流在毫安级。唤醒方式有几种:定时器唤醒、外部引脚唤醒、以及卡片接近唤醒。卡片接近唤醒需要保持接收链路工作,功耗最高,但用户体验最好。

/* 进入场关闭低功耗模式 */ void st25r3911_enter_power_down(st25r3911_dev_t *dev) { /* 1. 关闭射频场 */ st25r3911_write_reg(dev, ST25R3911_REG_OP_CONTROL, 0x00); /* 2. 关闭振荡器 */ st25r3911_write_reg(dev, ST25R3911_REG_OSC, 0x00); /* 3. 配置唤醒源:使能外部引脚唤醒 */ st25r3911_write_reg(dev, ST25R3911_REG_EXT_WAKEUP, 0x01); /* 4. 进入 power down */ st25r3911_write_reg(dev, ST25R3911_REG_OP_CONTROL, 0x01); }

这里要注意,进入低功耗之前必须确保没有正在进行的收发操作,否则芯片可能卡在中间状态。唤醒之后要重新初始化振荡器和射频场,不能直接发命令。实际项目里,我会在唤醒后加一个 5ms 的稳定延时,再开始寻卡。

4.3 用中断替代轮询:降低主控负担的实践

Demo SDK 里常见的是轮询状态寄存器,简单但浪费 CPU。实际产品里建议用中断方式,ST25R3911 的中断引脚可以配置成接收完成、发送完成、错误等事件触发。

/* 中断服务函数:处理接收完成事件 */ void st25r3911_irq_handler(void) { uint8_t irq_status = 0; st25r3911_read_reg(dev, ST25R3911_REG_IRQ_STATUS, &irq_status); if (irq_status & 0x04) { /* 接收完成 */ /* 设置标志位,主循环处理 */ g_rx_complete = 1; } if (irq_status & 0x08) { /* 发送完成 */ g_tx_complete = 1; } if (irq_status & 0x10) { /* 错误 */ g_error_flag = 1; } /* 清中断标志 */ st25r3911_write_reg(dev, ST25R3911_REG_IRQ_STATUS, irq_status); }

中断方式能把主循环解放出来做其他事情,但要注意中断服务函数里不要做太耗时的操作,读 FIFO 和解析协议放到主循环里做。另外中断标志的清除时机很关键,必须在读取状态之后、处理数据之前清除,否则会丢事件。

5. 避坑与排查:ST25R3911 Demo 调试中最容易翻车的五个点

5.1 寻卡距离短或者时好时坏

现象是卡片贴近才能读到,稍微远一点就没反应,或者同一张卡有时候能读有时候读不到。原因通常是天线匹配没调好,或者接收增益设置不合理。先检查天线谐振频率是否在 13.56MHz,用网络分析仪看阻抗曲线。然后检查RX_GAIN寄存器,增益太高会放大噪声,太低则信号不够。我一般会从中间值开始试,比如 0x08,然后根据实际读卡距离微调。另外电源纹波也会影响读卡稳定性,ST25R3911 的模拟电源最好加 LC 滤波。

5.2 SPI 通信失败,读芯片 ID 返回 0x00 或 0xFF

现象是初始化的时候读IC_IDENTITY寄存器,返回值不对。原因可能是 SPI 模式不对、片选时序不对、或者硬件连接有问题。先确认 SPI 模式是 Mode 0 还是 Mode 3,ST25R3911 两种都支持但配置要匹配。然后用示波器看片选、时钟、数据线的波形,确认建立时间和保持时间满足要求。如果用的是 GPIO 模拟 SPI,检查延时是否足够。还有一种可能是复位引脚没有正确拉低再拉高,芯片没有完成上电复位。

5.3 防冲突流程卡死或者 UID 读出来是乱的

现象是寻卡能拿到 ATQA,但防冲突的时候超时或者读到的 UID 字节顺序不对。原因可能是TX_BITS设置不对,REQA 是 7 位短帧,SEL 是完整字节,搞混了就会失败。另外级联级别的 SEL 命令码要正确,0x93、0x95、0x97 分别对应不同级联层。UID 字节顺序方面,ISO14443A 的 UID 是低位在前,但有些卡片返回的顺序需要根据级联标志调整。建议把每一级读到的原始数据打印出来,对照逻辑分析仪抓的波形逐字节核对。

5.4 低功耗唤醒后第一次寻卡失败

现象是芯片进入低功耗模式后,通过外部中断唤醒,第一次寻卡总是失败,第二次才正常。原因是唤醒后振荡器和射频场没有稳定,直接发命令会导致时序错误。解决办法是在唤醒后加足够的稳定延时,一般 5 到 10ms,然后再初始化振荡器和开启射频场。另外唤醒中断的标志要及时清除,否则会重复进入中断。

5.5 Mifare Classic 认证失败或者读块返回全 0

现象是寻卡和防冲突都正常,但认证过不去,或者认证过了读块数据全是 0。原因可能是密钥不对、块地址超出范围、或者认证流程的时序不对。先确认密钥是 Key A 还是 Key B,默认密钥通常是全 0xFF 或者全 0x00,但实际卡片可能被改过。块地址方面,Mifare Classic 1K 有 16 个扇区,每个扇区 4 个块,最后一个块是尾块不能直接读。认证流程里,发送认证命令后要等卡片返回随机数,然后硬件自动完成加密握手,如果中间超时或者 FIFO 操作有误,认证就会失败。读块返回全 0 有时候是因为认证没真正通过,但芯片没有报错,所以读出来的数据无效。

6. 从 Demo 到产品:把 ST25R3911 读卡稳定性做上去的几个进阶技巧

Demo SDK 能让你跑通基本功能,但产品化还需要在几个地方下功夫。第一个技巧是动态调整接收增益。固定增益在近距离和远距离之间很难兼顾,我一般会实现一个简单的 AGC 逻辑:先以中等增益寻卡,如果超时则提高增益重试,如果读到但误码率高则降低增益。这样能在不同距离和不同卡片之间取得平衡。

第二个技巧是天线分时复用。有些产品需要多个天线覆盖不同区域,ST25R3911 支持多路天线切换,但切换之后需要重新调谐和稳定时间。我的做法是在切换后加 2ms 延时,然后发一个短帧探测命令确认场稳定,再开始正常寻卡。

第三个技巧是数据校验和重试。NFC 通信在复杂电磁环境里误码率不低,尤其是 Mifare Classic 的加密通信。我一般会在协议层加 CRC 校验和最多三次重试,重试之间加随机退避,避免和附近其他读卡器冲突。

第四个技巧是温度补偿。天线谐振频率会随温度漂移,尤其是用陶瓷电容做谐振电容的时候。如果产品工作温度范围宽,建议用 NP0 材质的电容,或者在固件里根据温度传感器读数微调匹配网络的切换电容。

最后一个技巧是日志和诊断。产品出厂前建议留一个诊断模式,能输出每次寻卡的信号强度、误码率、重试次数。这些数据在排查现场问题的时候非常有用,比用逻辑分析仪方便得多。

我自己在做第一个 ST25R3911 项目的时候,花了整整两周才把天线调好,中间换了三次匹配电容值,最后发现是 PCB 走线的寄生电容没算进去。后来养成的习惯是,每次打样之前先用仿真工具把寄生参数估进去,打样之后第一件事就是测天线阻抗曲线,确认谐振点对了再调固件。这个顺序反过来做,会浪费很多时间在软件上找硬件的问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询