
简介面向STM32与FM17XX系列读卡芯片开发者的参考代码包全面覆盖ISO14443A/B双协议寻卡流程内含TypeA与TypeB卡片请求、防碰撞、选择等关键函数并针对Mifare S50/S70、UltraLight、DESFire等常见卡片类型给出返回代码说明。工程中还提供SPI收发底层驱动、ADC采集、TIM定时器等外设配置以及读卡成功后联动门锁电机的控制逻辑适合门禁、智能锁、消费终端等场景的二次开发与调试。压缩包共259个文件以C源文件与H头文件为主同时包含Keil工程配置、汇编源文件、Hex固件、链接映射、HTM与说明文档等总大小4.33MB附带的编译中间文件便于还原完整工程环境。目前已有1173人学习下载适合具备一定单片机基础、希望快速移植读卡方案或对照完整工程排查SPI通信、寻卡时序等问题的开发者。 这两年做嵌入式读卡器项目我前前后后摸过NXP的RC522/RC531、复旦微的FM17XX系列也踩过无数防冲突和天线匹配的坑。最近整理手上这套基于FM17XX的ISO14443A/B读卡参考代码时才发现很多经验散落在各个项目的调试记录里干脆集中写一篇把芯片选型、协议差异、代码框架、调试避坑一次性讲清楚。如果你是做门禁、消费机、读写器或者需要快速适配非接触式IC卡的产品开发这篇内容可以直接拿来当动手之前的底稿。1. 这套参考代码解决的是什么问题1.1 FM17XX国产读卡芯片里的百搭选择FM17XX是复旦微电子的非接触式读卡芯片家族工作频率13.56MHz支持ISO14443A/B协议部分型号还兼容ISO15693。做嵌入式的人可能更熟悉RC522这类芯片FM17XX在寄存器设计和命令集上跟NXP的RC系列有很大传承关系很多型号可以直接用RC522的思路去驱动但细节上又不一样。简单说它就是一套“国产供应链、成本可控、双协议覆盖”的卡片读写方案。在项目里选型时要特别注意不同型号的能力边界型号支持协议接口典型应用场景FM1702SLISO14443ASPI门禁、考勤、M1卡读写FM1715SLISO14443A/BSPI/UART/IIC通用读卡器、消费机FM1722SLISO14443A/B ISO15693SPI/UART/IIC多协议读写器、图书管理FM17520ISO14443ASPI/IIC低功耗消费、手环、水表从实际项目看只做M1卡读写FM1702SL性价比最高要兼容身份证、社保卡这类Type B卡片最低得选FM1715SLFM1702SL做不了Type B这个坑我在早期选型时踩过差点把硬件推倒重来。1.2 参考代码的定位与适用场景这套FM17XX读卡参考代码并不是某个官方SDK的搬运而是把ISO14443A/B两种协议栈的完整操作流程做了模块化整理。代码从SPI底层驱动开始到收发命令封装再到寻卡、防冲突、选卡、认证、读写块流程完整注释清晰。适合直接参考的场景包括门禁读头开发、食堂消费机、充电桩读卡模块、校园卡读写终端以及任何需要快速验证FM17XX芯片功能样机的项目。代码核心思路是“协议流程拆分 状态机驱动”读卡操作不再是一堆寄存器赋值堆在一起而是按协议阶段清晰推进方便移植和排查问题。2. 写驱动前先理解ISO14443A/B的底层差异2.1 Type A与Type B的基本区别很多初接触非接触式IC卡的人上来就写代码结果发现同一套驱动有时灵有时不灵本质是没搞清楚Type A和Type B的物理层和链路层差异。Type A使用Modified Mill编码、100% ASK调制从读卡器到卡片再从卡片返回数据用的是Miller编码和Manchester编码。这个编码体系的优点是抗干扰能力好但接收端需要更精确的位同步。市面上最常见的M1卡S50/S70、NFC Tag都是Type A。Type B使用NRZ编码、10% ASK调制返回数据采用BPSK。Type B的特点是调制深度低对天线和功放要求更高但数据传输效率相对高。第二代居民身份证、护照、社保卡、银行IC卡PBOC芯片卡基本都是Type B。这两者最大的工程意义在于你的天线匹配和读卡芯片硬件必须同时满足两种调制方式才能在同一个读头上跑A/B双协议。软件上如果只调好了Type AType B不一定好使。2.2 防冲突机制是最容易翻车的协议细节防冲突Anti-collision是ISO14443协议里最核心、也最容易写错的环节。Type A和Type B的防冲突思路完全不同。Type A采用比特级防冲突。多个卡片同时进入场区时读卡器发送SEL和NVB参数卡片反馈自己的UID部分位。如果卡片数量多读卡器收到的是叠加信号寄存器里会记录发生冲突的位位置CollPos这时读卡器通过修改NVB逐步筛选直到只剩一张卡。Type B采用时隙机制。读卡器发送REQB后卡片按照随机或计算出的时隙编号在对应时隙返回ATQB。读卡器根据ATQB中的PUPI唯一标识锁定目标卡片再用ATTRIB命令完成激活。很多代码在Type A防冲突时出错是因为没有正确处理Register中的冲突位置或者没有在冲突后重新按位发送部分UID。Type B代码出错则往往是REQB参数设置不合理导致时隙错乱。2.3 命令帧的位类型别忽视ISO14443A中区分短帧7bit、标准帧8bit和长帧。寻卡命令REQA和WUPA是短帧防冲突和后续读写命令是标准帧。FM17XX这类芯片会在寄存器里配置帧格式发送前需要确认位帧寄存器和CRC使能是否与命令类型匹配。比如REQA命令是0x26只要7位就能表达但如果你代码里按8位标准帧发有些卡也能响应有些卡就会判定为非法命令。这种“看起来能用、但兼容性差”的问题根子就在帧格式配置上。3. 核心代码架构与流程实现3.1 初始化与底层收发封装FM17XX作为从设备初始化第一步是SPI总线的建立。SPI的速率建议控制在5MHz以内具体以数据手册时序要求为准。除了收发函数还要留出复位引脚控制上电后对芯片做一次软复位把状态机拉回已知位置。void fm17xx_init(void) { // 软复位 fm17xx_write_reg(CMD_REG, CMD_SOFTRESET); fm17xx_delay_ms(10); // 配置发送和接收模式打开CRC、启用天线 fm17xx_write_reg(MODE_REG, 0x3F); // 使能CRC_TX/CRC_RX标准帧 fm17xx_write_reg(TX_MODE_REG, 0x00); fm17xx_write_reg(RX_MODE_REG, 0x00); fm17xx_write_reg(TX_ASK_REG, 0x40); // 调制深度 fm17xx_write_reg(TX_CTRL_REG, 0x50); // 开天线 }这段代码的用意不是照抄寄存器数值而是让你理解初始化的几个关键动作复位芯片、确认通信模式、配置编码调制参数、打开射频发射。FM17XX不同型号的寄存器初始值会有差异调片子时一定要对照数据手册。3.2 transceive封装是读卡驱动的灵魂读卡操作本质上就是“发一帧命令收一帧响应”。这个收发动作芯片厂家叫作Transceive。FM17XX内部有64字节FIFO数据收发都先经过它。int fm17xx_transceive(uint8_t *tx_buf, uint8_t tx_len, uint8_t *rx_buf, uint8_t *rx_len) { uint8_t irq, timeout 0; // 清FIFO和中断标志防止上一帧残留数据干扰 fm17xx_write_reg(FIFO_LEVEL_REG, 0x80); fm17xx_write_reg(IRQ_REG, 0x7F); // 发送数据写入FIFO fm17xx_write_reg(FIFO_DATA_REG, tx_buf, tx_len); fm17xx_write_reg(BIT_FRAMING_REG, 0x00); // 启动Transceive命令 fm17xx_write_reg(CMD_REG, CMD_TRANSCEIVE); // 轮询等待接收中断注意要加超时保护 while (1) { irq fm17xx_read_reg(IRQ_REG); if (irq RX_IRQ_BIT) { break; } if (timeout 5000) { return ERR_TIMEOUT; } } *rx_len fm17xx_read_reg(FIFO_LEVEL_REG); fm17xx_read_reg(FIFO_DATA_REG, rx_buf, *rx_len); return ERR_OK; }这里有个细节发送命令前必须清FIFO和中断。我见过太多项目没做这一步导致上一帧的残留数据混进来CRC校验时好时坏。另外读FIFO的时候如果接收到的数据有错部分芯片会自动触发错误中断代码里最好把ErrorReg的状态读出来区分是超时、CRC错误还是帧错误方便上层逻辑做不同处理。3.3 Type A完整流程寻卡、防冲突、选卡、认证、读写Type A的流程是分阶段执行的第一步寻卡。发送REQA0x26短帧卡返回ATQA2字节ATQA的最低有效位后续用于级联判断。第二步防冲突。如果场区里只有一张卡可以直接跳过防冲突把UID当作全0处理。多张卡同时进场就要执行比特防冲突。FM17XX有一个CollReg寄存器冲突位置会记录在这里。核心逻辑是发送0x93SEL NVB UID部分数据根据CollReg反馈的冲突位调整NVB迭代筛选UID。第三步选卡。UID完整解析后发送SEL NVB 0x70 4字节完整UID CRC卡返回SAK1字节。SAK可以判断卡类型比如M1 S50的SAK通常是0x08。第四步认证和读写。M1卡的密钥认证由FM17XX硬件完成软件只需调用MFAuthent命令把密钥和块地址写进FIFO。认证通过后发送读取命令0x30 块地址读取16字节块数据发送写入命令0xA0 块地址 16字节数据完成写块。这套流程看起来不复杂但代码组织上一定要按“状态机”来写。很多项目把寻卡、选卡、认证塞在一个大函数里一旦某一步失败后续状态完全不可控。参考代码里把每一步都封装成独立接口上层Listen循环只管调用、判断返回值异常恢复也清晰。3.4 Type B流程REQB、ATQB、ATTRIBType B相对Type A的流程更简单但格式更严格。第一步发送REQB。标准REQB是5字节0x05 AFI PARAM CRC。PARAM里的N值决定时隙数量常用N1表示卡片在1个时隙内随机应答。第二步接收ATQB。ATQB总长11字节不含CRC包含PUPI4字节、应用数据4字节、协议信息3字节。PUPI相当于卡片在本次会话中的临时身份后续ATRIB需要用到。第三步发送ATTRIB。ATTRIB命令用于激活卡片命令格式为0x1D PUPI4字节 参数 可选字段 CRC。激活成功后才能继续访问卡片内部应用。Type B调试时特别容易遇到REQB参数不对导致无响应。AFI应用族标识用于过滤应用类型普通读取设0x00即可。PARAM里的N值和ADV附加帧保护位会影响卡片应答初学者尽量保持N1别去调整稳定后再优化。4. 真机调试中的踩坑记录与排查技巧4.1 读不到卡片先检查硬件而不是软件读卡器“不上卡”是最高频的故障九成是硬件问题。晶振起振了吗用示波器看13.56MHz天线波形幅度是否足够天线匹配电容是否按规格书选型FM17XX的TX1/TX2管脚要接天线匹配网络这个网络设计直接影响读卡距离。我调试时习惯先确认SPI链路读芯片版本寄存器或ID寄存器能读回来芯片就是活的。然后测场强拿卡片贴近天线用示波器看卡片加载调制后的波形。如果波形幅度极低大概率是天线线圈没匹配好这时候软件调得再勤都白搭。注意FM17XX天线匹配不是随便接个电容就行的。常见的匹配结构是TX1/EMC滤波/天线线圈/C1/C2并联谐振谐振点要落在13.56MHz附近。硬件调好后读卡距离才能稳。4.2 防冲突定位不准导致选卡失败这是一个非常典型的软件坑。Type A防冲突时卡片返回的UID被检测到冲突CollReg会记录冲突位。芯片文档里CollPos字段的值是基于从LSB开始的位偏移很多参考代码直接把这个值当字节偏移用导致后续NVB计算错误。处理方式是冲突位对应的“剩下要发送的位数”要重新计算再组装NVB字节。NVB高4位表示已经完整发送的字节数低4位表示最后一个字节里已发送的位数。很多代码在这里用了位运算或移位方式巧算但逻辑一绕就容易错。我的建议是先写成最直观的版本// 假设CollPos表示冲突发生的位置以位为单位 uint8_t complete_bytes coll_pos / 8; uint8_t partial_bits coll_pos % 8; uint8_t nvb (complete_bytes 4) | partial_bits;这样在调试串口里打印出来每一步都看得见比起一堆位运算宏好排查得多。4.3 Type B卡片时灵时不灵Type B使用10% ASK调制幅度调制比Type A低得多。天线线圈如果Q值太高调制信号可能被压掉导致读卡器收不到Type B卡片的ATQB。FM17XX本身没问题但天线匹配网络对Type B更敏感。软件上REQB命令发出后等待ATQB的超时时间不能太短。Type B卡片有时候会在时隙末尾才应答如果超时设成10ms可能正好错过。建议至少给30ms以上。另外有些项目在使用多协议轮询时Type A和Type B交替执行中间要留一点切换间隔让场稳定下来否则卡片状态机还没复位收不到新的REQB。4.4 连续读卡死机、看门狗复位设备长时间运行后偶发死机多半是某一个读卡错误中断没处理干净导致后续中断一直被阻塞。我的做法是每一次transceive之后无论成功失败都把中断标志寄存器读一遍并全部清掉进入待机前再清一次FIFO。另外FM17XX的命令寄存器写入要遵循“先写参数、再写命令”的顺序。比如MFAuthent命令必须先把密钥和块地址写FIFO再写命令字。顺序反了芯片行为不可预测。这种问题在代码review时不仔细看很难发现只能靠调试经验沉淀。这里给个经验值在实际项目中我会给每一条读卡指令加上总超时兜底一般设置500ms。就算协议某个环节卡死也能及时复位重来不会把整个系统拖垮。5. 关于这套代码的个人心得代码整理过程中最大的收获不是寄存器用熟了而是对ISO14443A/B协议流程有了整体认知。读卡这件事表面上是几条命令的拼装实际上每个环节的状态转移、异常恢复、硬件交互都环环相扣。我在实际项目里用这套架构做过门禁读头和消费机稳定性和可维护性都明显好于早期那版把所有逻辑写在一个大循环里的代码。最后再分享一个小技巧调试读卡项目时一定留一路串口日志把每次transceive的命令、返回值、错误码、耗时都打出来。很多奇怪问题看协议分析仪反而看不出打日志却能发现是某一帧命令的CRC使能没配对、或者FIFO残留数据导致下一帧错位。这套参考代码里也把日志接口留好了格式你自己随便改。希望这篇能帮你少走几步弯路。本文还有配套的精品资源点击获取