简介:FM17XX系列非接触式读卡芯片的参考代码包,面向嵌入式开发及智能门禁、读卡设备调试人员,解决ISO14443A/B协议下寻卡和底层驱动实现问题。资源内含基于STM32F10x的完整工程,涵盖TypeA/TypeB寻卡流程、PcdRequest等函数调用、SPI读写时序、ADC与TIM外设配置,并附带常见门锁电机控制示例,便于理解读卡逻辑与硬件协同。整个压缩包共259个文件,4.33MB,以C源码、头文件、汇编启动文件及编译中间文件为主,其中50个.h和43个.c构成主要代码框架,另含工程配置文件、hex烧录文件及文档,适合直接参考或二次开发。目前已有1173人浏览学习,门禁/读卡项目开发者可借此快速上手FM17XX系列芯片,缩短底层驱动调试周期。
1. 项目概述与核心价值
1.1 这个参考代码到底解决什么问题
FM17XX系列是复旦微电子推出的非接触式读写卡芯片,常见型号包括FM1702SL、FM1712SL、FM1722SL等,主要面向13.56MHz频段的RFID/NFC应用。这颗芯片的底层操作并不复杂,但初次接触的人很容易被寄存器配置、时序控制、防碰撞流程这些细节搞懵。我最初接手这个项目时,手头只有一份芯片数据手册和几段零散的示例代码,光是把一张ISO14443A类型的M1卡读到UID,就折腾了两三天。后来把读卡流程理顺、把参考代码整理成可复用的模块后,才发现很多坑其实是可以提前避开的。
这次分享的FM17XX读卡参考代码,覆盖ISO14443A和ISO14443B两种协议类型,包含从芯片初始化、天线开启、请求卡片、防碰撞、选卡到读取UID/ATQB的完整流程。适合三类人:一是刚接触FM17XX系列芯片、想快速跑通读卡功能的嵌入式开发者;二是需要把FM17XX移植到不同MCU平台(STM32、51、GD32等)的工程师;三是做门禁、读卡器、小额支付终端等项目,需要理解NFC底层交互细节的软硬件开发人员。
1.2 为什么选择FM17XX而不是其他读卡芯片
市面上13.56MHz的读卡芯片方案很多,比如NXP的RC522、RC522A、RC523,恩智浦的PN532,复旦微的FM17XX系列等。从成本角度看,FM17XX在国内市场有优势,供货稳定,价格比同类进口芯片低不少。从功能上看,FM17XX支持ISO14443A/B双协议,部分型号还支持ISO15693,这让它在门禁、读卡器等对成本敏感的产品中非常有竞争力。
不过FM17XX和RC522的寄存器地址并不完全一致,虽然都是SPI/I2C/串口接口,但内部寄存器映射有差异。如果之前写过RC522的驱动,迁移到FM17XX时不能直接替换,需要对照数据手册逐项核对寄存器操作。这也解释了为什么网上很多RC522的例程不能直接用于FM17XX——指令集和寄存器定义确实不同。另外,FM17XX的模拟参数配置(比如发射增益、接收灵敏度)是通过一组特殊的寄存器配置的,这些参数的调整对读卡距离和稳定性影响很大,后面我会详细展开。
2. 整体设计思路与协议背景
2.1 ISO14443A/B的关键差异
ISO14443是13.56MHz非接触式IC卡的国际标准,分为A、B两种类型。两者在物理层调制方式、编码方式、帧格式上都有区别。Type A采用ASK调制(100%调制深度),Miller编码,通信速率通常为106kbps;Type B采用ASK调制(10%调制深度),NRZ编码,同样支持106kbps及更高速率。简单类比,Type A像是对讲机里说话的人,信号通断明显但容易受干扰;Type B像戴着耳机听音乐,信号平稳但幅度变化小,对接收电路的信噪比要求更高。
FM17XX设计上同时支持这两种类型,但芯片在初始化时需要分别配置对应的寄存器。我们常见的门禁卡大多是Type A的M1卡(如S50、S70),公交卡和部分银行IC卡则可能采用Type B协议(比如第二代身份证、部分CPU卡)。所以一套代码能不能同时兼容A/B,直接决定读卡器能否覆盖更多应用场景。
2.2 读卡流程的抽象分层
参考代码的核心思路是把读卡操作抽象成几个独立的步骤,每一层职责单一,方便复用和调试。我建议把整个流程划分为四层:
- 物理层:负责SPI/I2C/UART通信,读写FM17XX寄存器。
- 命令层:封装FM17XX的请求、防碰撞、选卡等指令,对应ISO14443规定的命令帧。
- 协议层:处理卡片返回的状态字节、CRC校验、位冲突检测等。
- 应用层:最终拿到卡片序列号(UID或ATQB)或数据块内容,供业务逻辑使用。
这种分层方式最大的好处是:当底层MCU更换时,只需要改物理层;当需要新增对某种卡片的支持时,只需要在命令层增加对应的命令帧构造与解析函数。参考代码里我正是按照这个思路组织的,实测下来维护成本比单个大而全的读卡函数低很多。
2.3 为什么参考代码选择SPI作为默认接口
FM17XX支持SPI、I2C和UART三种接口,我提供的参考代码默认使用SPI接口,主要原因是SPI通信速率高、实现简单、占用引脚少(SCK、MOSI、MISO、CS即可)。在多数MCU平台上,SPI外设都是标配,硬件SPI或者软件模拟SPI都能快速跑通。如果项目里SPI总线被别的设备占用,也可以考虑I2C,但I2C速率通常限制在400kHz以下,对防碰撞这种需要快速响应的场景略有压力。UART接口则适合用串口透传方式控制芯片,但需要额外的板级配套。
参考代码里提供了SPI读写寄存器的底层函数,协议部分不依赖具体MCU的SPI寄存器,所以移植时只需要替换几个底层宏和字节收发函数,上层代码可以原封不动。
3. 核心细节解析与实操要点
3.1 初始化阶段容易被忽略的寄存器
FM17XX的初始化不只是配置几个通信寄存器那么简单。我见过不少人在初始化时只设置Mode寄存器、TMode寄存器和Timer寄存器,结果天线能打开,但读卡距离极近,或者根本读不到卡。这里有几个关键点要特别注意。
首先,FM17XX内部有发送器和接收器,发送器输出由TxControl寄存器控制,天线驱动由所谓的“Analog Select”寄存器控制。中文数据手册里通常建议在初始化时按顺序写入一组固定模拟参数,这些参数决定了发射信号的波形和接收放大倍数。以FM1702SL为例,关键的模拟配置寄存器和推荐值如下:
| 寄存器 | 推荐值 | 作用说明 |
|---|---|---|
| RegChannel | 0x0E | 设置发射通道和接收通道的功率/增益 |
| RegGsNHigh | 0x00 | 控制天线输出高压侧驱动强度 |
| RegCWGsP | 0x32 | 控制天线输出低压侧驱动强度 |
| RegModWidth | 0x26 | 设置调制脉冲宽度,影响Type A调制特性 |
| RegRxThreshold | 0x55 | 接收器比较器阈值,影响灵敏度 |
| RegDemod | 0x24 | 解调器配置,影响信号解调质量 |
这些值直接照抄RC522的初始化值是不行的,因为FM17XX的寄存器地址和位定义有差异。比如ModWidth寄存器在RC522里地址是0x24,但在FM17XX里同样是0x24但位宽含义有所区别。所以建议以FM17XX的数据手册为准,最好用逻辑分析仪观察天线信号波形,调节GsN和GsP使ASK调制深度符合规范。
3.2 ISO14443A读卡的完整请求序列
Type A协议的读卡流程严格遵循“请求-防碰撞-选卡”三步曲。参考代码里分别封装了以下函数:
fm17xx_request_a():发送REQA命令(0x26),进入Type A识别模式。卡片在有效场内会应答ATQA(2字节)。fm17xx_anticoll_a():发送防碰撞命令(0x93 + 0x20)读取4字节UID,若有多张卡同时在场,会检测到冲突位,返回冲突位置。fm17xx_select_a():发送选卡命令(0x93 + 0x70)完成选卡,卡片返回SAK确认。
这里有个细节:防碰撞命令分为两层。第一级使用级联标签(如0x93处理4字节UID的第一段,0x95处理第二段,0x97处理第三段)。常见的M1 S50卡UID是4字节,所以只用0x93即可;但一些CPU卡或者双界面卡的UID可能是7字节或10字节,就需要级联处理。参考代码里实现了完整的级联逻辑,可以应对不同长度的UID。
实操中,发送防碰撞命令后,CC(CRC校验)可以置0由硬件自动计算,也可以手动计算。FM17XX有硬件CRC模块,在寄存器配置正确的前提下,软件只需将要发送的数据帧写入FIFO,然后启动发送,硬件会在帧尾自动附加CRC。这一点非常方便,省去了软件计算CRC_A的时间和代码量。
3.3 ISO14443B读卡的关键差异
Type B协议与Type A完全不同。Type B通过REQB命令(0x05)启动通讯,卡片回复ATQB,其中包含PUPI(类似于UID)、协议信息和厂商信息。读取ATQB的过程不像Type A那样有“防碰撞”命令,而是通过参数来区分不同卡片。
FM17XX对Type B的支持,需要在初始化时把Mode寄存器中的ProtocolType设置成Type B模式。参考代码里通过fm17xx_set_protocol(FM17XX_PROTOCOL_ISO14443B)切换。随后发送REQB命令后,从FIFO读取ATQB数据帧。ATQB长度通常为12字节(含CRC),但也可能因为厂商数据扩展而更长,所以读取FIFO时要根据接收长度寄存器动态判断,不能固定只读12字节。
这里有个坑:Type B的传输速率虽然也是106kbps,但其帧格式是SOF+数据+EOF,SOF和EOF的时序与Type A完全不同。FM17XX内部解调器能自动识别,但寄存器里有个RxNoise位,如果环境中噪声较大,需要调整接收阈值寄存器。我在实际测试中,靠近电机、电源适配器这些强干扰源时,Type B的读卡成功率明显下降,后来把RxThreshold调高了一档,情况明显改善。
3.4 天线匹配与EMC设计
FM17XX的读卡距离和稳定性,很大程度上取决于天线设计。参考代码只能保证芯片逻辑正确,天线匹配不好照样读卡失败。FM17XX典型的天线是PCB线圈或铜绕线圈,并联一个谐振电容,使13.56MHz频率谐振。我这里给出一个常用的匹配计算方式:
天线电感L通过阻抗分析仪测得,比如常见的PCB天线测得约1.4μH,那么并联谐振电容C由公式 f = 1 / (2π√(LC)) 推导,C ≈ 1 / (4π² × f² × L)。对于13.56MHz:
C ≈ 1 / (39.48 × (13.56×10⁶)² × 1.4×10⁻⁶) ≈ 98.7pF
实际取100pF即可。但要注意,这个电容需要考虑FM17XX的输出电容和PCB寄生电容,所以最终取值可能需要微调。调试时用网络分析仪或示波器观察天线两端波形,调整到正弦波幅度最大、波形无畸变就好。
另外,天线走线尽量远离MCU时钟线和电源线,最好铺地隔离。FM17XX的模拟电源和数字电源要分开,避免数字噪声耦合到天线。我在项目板上电源设计比较随意,结果发现读卡距离从4厘米掉到1.5厘米,后来加了磁珠和100nF的电容滤波才恢复正常。
4. 实操过程与核心环节实现
4.1 硬件连接准备
以STM32F103和FM1702SL为例,SPI接线如下:
| FM1702SL引脚 | STM32F103引脚 | 说明 |
|---|---|---|
| SCLK | PA5 | SPI时钟 |
| MOSI | PA7 | 主发从收 |
| MISO | PA6 | 主收从发 |
| CS | PA4 | 片选,低有效 |
| RST | PB0 | 复位输入 |
| IRQ | PB1 | 中断输出(可不接) |
FM1702SL工作电压3.3V,STM32F103也可3.3V供电,注意不要直接用5V电平驱动SPI,以免损伤芯片。如果MCU是5V供电,需要加电平转换。
硬件连接完毕后,上电复位时序也需要注意。FM1702SL上电后需要至少10ms稳定时间,然后拉低RST再释放,等待芯片Ready信号。参考代码里实现了软件延时,保证时序稳定。
4.2 初始化代码的工程化实现
参考代码里将初始化函数命名为fm17xx_init(),内部做了四件事:复位芯片、配置通信模式、设置模拟参数、开启天线。关键片段如下:
uint8_t fm17xx_init(void) { fm17xx_reset(); /* 硬件复位 */ fm17xx_cmd(0x01); /* 软件复位命令 */ fm17xx_write_reg(RegMode, 0x3F); /* 设置基本模式 */ fm17xx_write_reg(RegTxControl, 0x83); /* 开启天线 */ /* 写入一组推荐模拟参数 */ fm17xx_write_reg(RegChannel, 0x0E); fm17xx_write_reg(RegGsNHigh, 0x00); fm17xx_write_reg(RegCWGsP, 0x32); fm17xx_write_reg(RegModWidth, 0x26); fm17xx_write_reg(RegRxThreshold, 0x55); fm17xx_write_reg(RegDemod, 0x24); fm17xx_set_protocol(FM17XX_PROTOCOL_ISO14443A); return 0; }需要注意的是,reg_config顺序不能乱,有些寄存器必须在芯片空闲时写入。比如设置协议类型要在开启天线之前完成,否则可能出现配置失败。另外,软件复位命令(0x01)执行后,需要等待至少1ms再写其他寄存器,不然芯片可能还没准备好。
4.3 ISO14443A读卡主流程实现
下面这段代码实现了完整的A卡读UID流程,可以直接作为参考:
uint8_t fm17xx_read_card_a(uint8_t *uid) { uint16_t atqa; uint8_t len; /* 请求 Type A 卡片 */ if (fm17xx_request_a(&atqa) != 0) return 0; /* 防碰撞,读取 UID 级联1 */ if (fm17xx_anticoll_a(uid) != 0) return 0; /* 选卡 */ if (fm17xx_select_a(uid) != 0) return 0; return 1; }fm17xx_request_a发送REQA后,读取FIFO返回的ATQA数据。需要注意,FM17XX接收到的数据都带有CRC,需要验证CRC正确性。硬件接收时如果开启CRC校验,错误帧会被硬件丢弃,但我们仍然要检查状态寄存器中的错误标志位。
防碰撞函数中,如果检测到冲突,参考代码会返回冲突位coll_pos,接着可以做多卡处理。但大多数应用只需要读取一张卡,所以简化为只要有冲突就返回失败,由上位机提示用户移开多张卡。
4.4 ISO14443B读卡流程实现
Type B的流程相对简单,参考代码如下:
uint8_t fm17xx_read_card_b(uint8_t *atqb) { uint16_t len; uint8_t buf[32]; fm17xx_set_protocol(FM17XX_PROTOCOL_ISO14443B); /* 发送 REQB,AFI=0x00,参数=0x08(单卡请求) */ buf[0] = 0x06; /* 长度字节 */ buf[1] = 0x00; /* 首字节,未用 */ buf[2] = 0x08; /* REQB */ buf[3] = 0x00; /* AFI */ buf[4] = 0x08; /* PARAM */ fm17xx_transceive(buf, 5, 0); /* 发送并接收 */ len = fm17xx_read_fifo(atqb, 32); if (len == 0) return 0; /* 在这里可以解析ATQB中的PUPI,前4字节 */ return 1; }这里有一个关键点:Type B的REQB帧长度字节len是包括自身字节和后续所有字节的,所以长度应为5(示例中0x06是意味着到CRC之前的长度)。实际上ISO14443B的帧格式比较繁琐,需要仔细对照协议文档。参考代码简化了长度计算,读者可根据自己需求调整。
接收到的ATQB数据从FIFO读出后,需要判断帧是否以CRC正确结束。FM17XX会在状态寄存器中报告CRC错误,如果错误,一般需要重新发送REQB。
4.5 移植到其他平台的注意事项
参考代码的底层函数只有以下几个与平台相关:
fm17xx_spi_write_byte(uint8_t data)fm17xx_spi_read_byte(void)fm17xx_cs_low()/fm17xx_cs_high()fm17xx_delay_us(uint32_t us)
只要实现这四个函数,整个协议栈就能跑起来。以51单片机为例,可以这样软件模拟SPI:
void fm17xx_spi_write_byte(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { SCLK = 0; if (data & 0x80) MOSI = 1; else MOSI = 0; data <<= 1; SCLK = 1; } }软件模拟SPI的速率不能太快,FM17XX最大SPI时钟一般为10MHz,但软件模拟建议控制在1MHz以下,否则可能因为时序不稳定导致读写错误。实测STM32硬件SPI的2MHz频率没有问题,4MHz也行,但再高就可能出现偶发读错寄存器的问题。
5. 常见问题与排查技巧实录
5.1 读不到卡或读卡距离极近
这个问题占我遇到的故障中最常见的七成以上。排查思路按优先级排列:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 完全读不到卡 | 天线未开启 | 检查TxControl寄存器,Bit1必须为1 |
| 完全读不到卡 | SPI通信错误 | 用示波器看SPI时序,确认CS/SCK/MOSI电平正确 |
| 读卡距离小于1cm | 模拟参数配置不当 | 检查RegCWGsP和RegGsNHigh,尝试增大发射功率 |
| 读卡距离小于1cm | 天线谐振频偏 | 用网络分析仪测天线谐振点,调整谐振电容 |
| 间歇性读卡失败 | 电源纹波过大 | 在FM17XX电源端加100nF去耦电容 |
另外,一个常见低级错误是FM17XX的复位引脚没有正确释放,芯片一直处于复位状态。上电后要确保RST为高电平,至少保持延时后再开始通信。
5.2 读A卡正常,但读B卡失败
如果代码同时适配A/B协议,读A卡正常,切换B卡后始终失败,多半是协议切换不彻底。FM17XX并不是自动识别A/B卡的,必须要在切换时重新配置对应协议相关的寄存器。有些工程师只改了Mode寄存器,但没有重新设置发射控制、接收配置等项,导致B卡信号调制解调不匹配。
检查参考代码中的fm17xx_set_protocol函数,确保它写入了如下寄存器:
void fm17xx_set_protocol(uint8_t proto) { if (proto == FM17XX_PROTOCOL_ISO14443B) { fm17xx_write_reg(RegMode, 0x3F); /* 重置模式 */ fm17xx_write_reg(RegTxControl, 0x80); /* 关闭天线 */ /* 设置B类相关寄存器 */ fm17xx_write_reg(RegBitFraming, 0x00); /* 其他B类专属配置 */ fm17xx_write_reg(RegTxControl, 0x83); /* 重新开启天线 */ } else { /* A类配置 */ } }记得在切换协议时关闭天线,改完配置后再重新开启,否则芯片状态可能残留,导致新协议帧无法正常发送。
5.3 FIFO溢出导致数据丢失
当卡片返回的数据超过FIFO容量(64字节)时,FM17XX会产生FIFO溢出中断。如果读取过程中没有及时清空FIFO,后面的数据会被丢弃。这通常发生在读取ATQB较长或读取M1卡扇区数据时。
解决办法是在发送命令前清空FIFO,并在接收过程中监控状态寄存器的FIFO Level。参考代码在fm17xx_transceive函数中加入了FIFO水位检查,接收前把FIFO清空,接收过程中如果FIFO快满了,立即读出数据到缓冲区,避免溢出。
5.4 卡响应超时和重试机制
FM17XX的定时器寄存器控制着发送等待时间。如果TMode设为0x80,TAuto设为0x20,就可以启用自动定时功能。超时值TPrescaler和TReload共同决定等待时长。常见配置为:
- TMode = 0x80(使能定时器)
- TPrescaler = 0xA9
- TReloadHi = 0x03
- TReloadLo = 0xE8
这样设置的超时时间大约为10ms左右。如果卡片无响应,状态寄存器会置位Timeout标志。实际应用中,建议在发送命令后等待完整超时周期,不要提前读取FIFO,否则可能拿到错误数据。参考代码中的重试机制是连续发送三次请求,每次间隔10ms,都无响应才判定无卡。
6. 实际操作中的经验心得
这套参考代码已经在我经手的多个项目中跑过,从最开始的FM1702SL到后来的FM1722SL,基本都是同一套架构,改动量很小。我个人体会最深的一点是:读卡器的稳定性,七分靠硬件,三分靠固件。代码写得再好,天线匹配不行、电源不干净,读卡距离和成功率都上不去。相反,只要天线调好了,固件哪怕粗糙一点,也能正常工作。
给新手的建议是:先把初始化流程走通,用读寄存器的方式确认SPI通信无误,再用单步调试观察REQA发送后FIFO里有没有数据。不要一上来就整体跑通所有协议,那样出了问题难以定位。调试的时候准备一张已知UID的M1卡,用逻辑分析仪配合上位机打印,比对实际读到的UID是否一致。
最后分享一个排查小技巧:当读卡不稳定时,把读卡器的功耗和波形同时抓下来,观察每次读卡瞬间电源有没有跌落。如果有跌落,多半是天线的发射功率拉低了电压,可以适当调低发射增益,同时增大电源电容容量。这个坑值得记下来,它能省去你盲调天线的大量时间。
本文还有配套的精品资源,点击获取