资讯动态

FM17550读卡程序实战:从RC522兼容到Mifare防碰撞与认证

发布时间:2026/10/17 13:44:22 来源:尧图企业网站定制
简介这是一份基于STM32F103微控制器的FM17550 NFC读卡器完整C工程面向需要实现ISO14443A和ISO14443B两种类型简称AB卡读写以及低功耗管理的嵌入式开发者适用于门禁、公交刷卡、移动支付等常见物联网应用场景。整个压缩包非常精简大小仅为26KB共包含13个源文件其中7个C语言源文件和6个头文件内容覆盖SPI通信底层驱动、type_a与type_b卡片协议解析、LPCD低功耗检测、fm175xx寄存器配置以及上层读写调用接口模块划分清晰能够直接导入Keil MDK或STM32CubeIDE进行编译和调试。目前已有3543人学习或下载代码提供了完整的初始化、防冲突、读卡、休眠唤醒流程并针对SPI时钟参数配置和低功耗模式切换等关键点给出了易于理解的实现方式有助于快速掌握FM17550的工作机制。通过研习这份工程代码开发者可以系统学习NFC AB卡处理流程与低功耗设计思路也能够将其中成熟代码复用到门禁、公交扣费或支付终端等产品中从而大幅缩短开发周期。1. FM17550读卡程序一张卡片的身份从一串读卡命令开始做过门禁、食堂刷卡机或者桌面读卡器的人大概率绕不开FM17550这颗芯片。它是一颗13.56MHz的非接触式读写卡芯片直接支持ISO14443A协议也就是我们手里最常见的Mifare Classic卡S50/S70、部分校园卡和公交卡。所谓“FM17550读卡程序”本质上就是通过SPI或UART接口驱动这颗芯片完成寻卡、防碰撞、选卡、认证、读写块这一整套动作。它能解决什么问题最直接的就是在RC522缺货涨价、或者需要国产替代的背景下用几乎相同的硬件和代码跑通读卡功能。适合谁适合正在做读卡器原型验证、门禁模块调试、或者把老项目从RC522迁到FM17550的嵌入式开发。这篇文章从寄存器映射讲起一路写到最小读卡程序、防碰撞和认证细节最后给出我调这块芯片时踩过的坑和一些收尾技巧。2. 从RC522到FM17550引脚兼容、寄存器映射和“直接替换”的真实边界2.1 FM17550和RC522的关系这不是“国产平替”而是“寄存器级兼容”FM17550这颗芯片在市场上主要以“兼容MFRC522”的身份出现。引脚定义、SPI指令格式、寄存器地址基本对得上最典型的55脚QFN封装可以直接贴到原先给RC522画好的板子上。但“基本对得上”不等于“完全一样”真正做项目时要注意三点差异。第一天线驱动管脚输出阻抗有细微差别天线匹配电路里的电容电阻值可能需要微调。第二FM17550的寄存器复位值和RC522并非全部一致特别是某些状态位和定时器默认值。第三芯片内部非接触接口的自动载波关闭Auto RF Off行为不完全一样。这三个差异在实际项目中会导致两种现象一是原来RC522能稳定读的板子换芯后偶尔读不到卡二是低功耗模式下芯片的功耗表现不同。因此把FM17550当RC522用代码层面基本无障碍但硬件射频匹配和低功耗策略要单独验证。我一般建议拿到芯片后第一件事是写一个寄存器全读程序把复位后的寄存器值打印出来和RC522的数据手册逐一对照再做后续开发。2.2 接口选型SPI、UART、I2C 应该选哪一个FM17550支持SPI、UART、I2C三种主机接口通过引脚电平配置。实际项目中SPI用得最多因为速率高、时序简单、调试方便STM32和各类MCU都带硬件SPI。UART适合主机没有SPI引脚的场景尤其是低引脚数的单片机。I2C在这个芯片上用得少主要因为速率受限读卡器领域几乎不会选它。代码实现上SPI接口最关键的是理解它的读写时序片选拉低后第一个字节是“地址方向位”方向位为1表示读为0表示写。后续字节就是数据。读寄存器时地址字节后面要跟一个无效字节产生时钟才能把数据读出来。这个细节很容易被忽略用逻辑分析仪一看就明白。SPI速率方面FM17550手册给出的上限通常到10MHz左右。我实际调试时习惯先从1MHz起步跑通流程后再往上升。原因是很多读卡不稳定问题来自SPI时序过冲和天线噪声的耦合低速先把逻辑跑对再逐级提频。如果板子布线比较拥挤SPI时钟线离天线线圈太近高速时会出现读卡偶发失败这时候降速是成本最低的解法。2.3 寄存器读写封装一个可复用的底层驱动骨架写FM17550读卡程序第一步是把寄存器读写封装成两个函数。下面是基于STM32 HAL库的示例适用于所有SPI接口的FM17550和兼容芯片。#define FM17550_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define FM17550_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) uint8_t FM17550_ReadReg(uint8_t reg) { uint8_t tx[2], rx[2]; tx[0] ((reg 1) 0x7E) | 0x80; // 地址左移1位读方向位置1 tx[1] 0x00; // 产生时钟的哑字节 FM17550_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 10); FM17550_CS_HIGH(); return rx[1]; // 第二字节才是寄存器内容 } void FM17550_WriteReg(uint8_t reg, uint8_t val) { uint8_t tx[2]; tx[0] (reg 1) 0x7E; // 写方向位为0 tx[1] val; FM17550_CS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, 10); FM17550_CS_HIGH(); }这段代码里最关键的是地址编码FM17550的寄存器地址是7位SPI传输时需要左移一位bit0作为读写标志。读操作固定发一个0x00字节当作时钟否则MISO上不会有数据。写操作则直接发数据。参数方面超时时间设10ms足够因为单次SPI传输只有两个字节实际耗时在微秒级。CS拉高拉低的时机必须精确包裹整个传输过程中间不能插入其他SPI设备操作。有了这两个函数后面所有操作都建立在这层封装上。特别注意有些兼容芯片的读寄存器行为在寄存器地址最高位有差异如果读回来全是0xFF先检查地址编码是否把最高位误设了。3. 本地跑通FM17550的最小读卡程序初始化序列与REQA的三层拆解3.1 芯片初始化天线上电、定时器配置和中断清除FM17550上电后默认的天线驱动是关闭的需要写寄存器打开。初始化序列各家的库大同小异核心动作是软复位、关闭天线、配置定时器、打开天线、清中断标志。void FM17550_Init(void) { FM17550_WriteReg(CommandReg, 0x0F); // 软复位CommandReg[3:0]1111 HAL_Delay(10); FM17550_WriteReg(TModeReg, 0x80); // 打开定时器定时器自动开始 FM17550_WriteReg(TPrescalerReg, 0xA9); // 预分频值决定定时器时钟 FM17550_WriteReg(TReloadRegH, 0x03); // 定时器重装载值高字节 FM17550_WriteReg(TReloadRegL, 0xE8); // 低字节合计约25ms超时 FM17550_WriteReg(TxAskReg, 0x40); // 发送时强制100%ASK调制 FM17550_WriteReg(TxControlReg, 0x83); // 打开天线驱动引脚TX1/TX2 FM17550_WriteReg(RxThresholdReg, 0x55); // 接收门限降低误触发 FM17550_WriteReg(ComIrqReg, 0x7F); // 清所有通信中断标志 FM17550_WriteReg(DivIrqReg, 0x7F); FM17550_SetBitMask(ComIrqReg, 0x01); // 允许RxDone中断 }软复位命令是CommandReg写0x0F这会让芯片回到默认状态。延时10ms是等芯片内部状态机稳定。定时器参数决定一次命令的超时时间预分频0xA9配合重装载值0x03E8实际超时约25ms。这个值要大于卡片返回ATS或者ACK的最长等待时间一般20到30ms都合理。TxControlReg写0x83是打开天线驱动的关键bit1和bit0分别控制TX1和TX2管脚输出。如果这一步漏了后续所有命令都不会有载波输出卡根本没有能量响应。ComIrqReg写0x7F是清中断标志这在发送命令前必须做否则上一次命令遗留的中断会干扰本轮判断。3.2 发送REQA一张卡进入场区后的第一声问候初始化完成后读卡程序的第一步是发送REQA命令。REQA是ISO14443A协议里的Type A请求命令卡片进入射频场后收到REQA会回复ATQA对请求的应答。发送REQA在FM17550上通过Transceive命令实现。uint8_t FM17550_Request(uint8_t req_code, uint8_t *atqa) { uint8_t status; uint8_t back_len; FM17550_WriteReg(ComIrqReg, 0x7F); // 清中断 FM17550_WriteReg(FIFOLevelReg, 0x80); // 清FIFO指针 FM17550_WriteReg(FIFODataReg, req_code); // REQA0x26 或 WUPA0x52 FM17550_WriteReg(CommandReg, 0x0E); // 0x0ETransceive命令 FM17550_WriteReg(BitFramingReg, 0x07); // 发送7位REQA是7位命令 // 等待中断或超时 status FM17550_WaitIrq(0x01, 25); // 等RxDone标志超时25ms if (status) return 1; // 超时场里没有卡 back_len FM17550_ReadReg(FIFOLevelReg); atqa[0] FM17550_ReadReg(FIFODataReg); // ATQA低字节 atqa[1] FM17550_ReadReg(FIFODataReg); // ATQA高字节 return 0; }REQA命令值0x26是固定的。FIFO里只有这一个字节发送时BitFramingReg设置为0x07表示发送7位因为REQA定义是短帧7位。这里容易犯的错误是直接按字节发送把BitFramingReg写成0x00导致卡片不响应。卡片如果在场会回复2字节的ATQAMifare Classic S50的ATQA是0x0004这个值可以作为后续判断卡类型的依据。3.3 等待中断的轮询逻辑轮询比中断更可靠发送命令后需要等待芯片产生中断。FM17550的通信中断包括RxDone、TxDone、Idle等实际开发中最可靠的做法是轮询ComIrqReg寄存器而不是依赖外部GPIO中断。uint8_t FM17550_WaitIrq(uint8_t irq_mask, uint16_t timeout_ms) { uint32_t tick HAL_GetTick(); while ((HAL_GetTick() - tick) timeout_ms) { uint8_t irq FM17550_ReadReg(ComIrqReg); if (irq 0x80) return 0; // ErrFlag置位通信错误 if (irq irq_mask) return 0; HAL_Delay(1); } return 1; // 超时 }轮询间隔设为1ms超时25ms。为什么不用阻塞式等待因为FM17550在无卡时会一直不产生RxDone中断阻塞等待会卡死整个系统。轮询方式在处理过程中可以插入喂狗、切换LED状态等操作。读卡器产品中寻卡通常是一个周期性的动作每100ms寻一次卡轮询正好配合这个节奏。这个函数返回值0表示收到中断或错误1表示超时。调用处需要区分如果ErrFlag置位说明通信过程出错应该清FIFO然后重新初始化天线状态而不是简单地继续发下一条命令。4. 深入防碰撞与Mifare认证选卡、密钥认证和读写块的操作细节4.1 防碰撞命令序列为什么必选一张卡不能只靠REQA射频场里可能同时存在多张卡REQA会让所有卡同时回复ATQAFIFO里收到的数据是碰撞后的乱码。因此ISO14443A协议定义了防碰撞循环先发出防碰撞命令0x93或0x95配合级联序号逐一筛选出唯一一张卡。uint8_t FM17550_Anticoll(uint8_t *serial) { uint8_t status, len; uint8_t buf[5]; FM17550_WriteReg(ComIrqReg, 0x7F); FM17550_WriteReg(FIFOLevelReg, 0x80); FM17550_WriteReg(BitFramingReg, 0x00); // 全字节发送 FM17550_WriteReg(CommandReg, 0x0E); // Transceive // 发送防碰撞命令0x93和完整的UID长度信息 FM17550_WriteReg(FIFODataReg, 0x93); FM17550_WriteReg(FIFODataReg, 0x20); status FM17550_WaitIrq(0x01, 25); if (status) return 1; len FM17550_ReadReg(FIFOLevelReg); for (uint8_t i 0; i len; i) { buf[i] FM17550_ReadReg(FIFODataReg); } // 数据格式UID[0..3] BCC校验字节 memcpy(serial, buf, 4); // 校验BCC uint8_t bcc buf[0] ^ buf[1] ^ buf[2] ^ buf[3]; if (bcc ! buf[4]) return 2; // 校验失败 return 0; }防碰撞命令写入FIFO时先写0x93选择级联1再写0x20表示完整UID的位计数。这里的位计数0x20代表32位即4字节。卡片收到后会回复它的UID和BCC校验字节。BCC是前4字节UID的异或值代码里手动验证不通过说明通信受了干扰需要重试。实际多卡场景如果场里有三张卡第一次防碰撞收到的可能是多张卡的UID混合数据此时FM17550的FIFO里会出现碰撞错误。专业处理方式是解析碰撞位置的bit确定发生碰撞的位然后发送带部分UID的防碰撞命令逐步排除。小项目里如果只需要“读最近的卡”用轮询加单次防碰撞就够了每次重新发起REQA时返回的其实是同一张卡。4.2 Mifare Classic认证三遍握手、密钥区和块地址的计算逻辑拿到UID后读Mifare S50的数据块还需要认证。S50的存储结构是16个扇区每扇区4个块其中每个扇区的块3是扇区尾部存放KeyA、访问位、KeyB。认证时必须指明要访问的扇区号和密钥类型。uint8_t FM17550_Auth(uint8_t block_addr, uint8_t key_type, uint8_t *key) { uint8_t buf[6]; // 写密钥到KeyA或KeyB寄存器区 for (uint8_t i 0; i 6; i) { if (key_type 0x60) // 0x60用KeyA认证 FM17550_WriteReg(KeyA, i, key[i]); else // 0x61用KeyB认证 FM17550_WriteReg(KeyB, i, key[i]); } FM17550_WriteReg(ComIrqReg, 0x7F); FM17550_WriteReg(FIFOLevelReg, 0x80); // Auth命令需要写入认证类型、块地址、UID FM17550_WriteReg(FIFODataReg, key_type); FM17550_WriteReg(FIFODataReg, block_addr); for (uint8_t i 0; i 4; i) { FM17550_WriteReg(FIFODataReg, uid[i]); } FM17550_WriteReg(CommandReg, 0x0E); // Transceive uint8_t status FM17550_WaitIrq(0x01, 25); if (status) return 1; // 超时认证失败 uint8_t err FM17550_ReadReg(Status2Reg); if (err 0x01) return 2; // 错误标志密钥不匹配 return 0; }如果前面防碰撞拿到了卡号认证时还需要完整的UID。注意如果卡的UID是4字节直接用前面拿到的serial如果是7字节的双级联卡认证时需要用完整的7字节UID。Mifare Classic认证的逻辑是芯片内部完成三遍握手主机只需要发送认证命令参数FM17550会自动处理卡片返回的随机数和密钥流。这里有个血泪经验认证必须指定正确的块地址类型。块地址是对整个卡片的线性编号扇区0的块0地址是0扇区0的块1是1依此类推。如果你拿到了扇区号而不是块号记得乘4。很多新手把扇区号直接当成块地址传进去认证就会失败。4.3 数据块读写进入一个块之后读和写的操作流程差别认证成功后读块和写块的操作流程类似但写块多一个内部编程过程时序上要格外小心。uint8_t FM17550_ReadBlock(uint8_t block_addr, uint8_t *data) { FM17550_WriteReg(ComIrqReg, 0x7F); FM17550_WriteReg(FIFOLevelReg, 0x80); FM17550_WriteReg(FIFODataReg, 0x30); // READ命令 FM17550_WriteReg(FIFODataReg, block_addr); // 块地址 FM17550_WriteReg(CommandReg, 0x0E); if (FM17550_WaitIrq(0x01, 25)) return 1; uint8_t len FM17550_ReadReg(FIFOLevelReg); for (uint8_t i 0; i len i 16; i) { data[i] FM17550_ReadReg(FIFODataReg); } return 0; } uint8_t FM17550_WriteBlock(uint8_t block_addr, uint8_t *data) { FM17550_WriteReg(ComIrqReg, 0x7F); FM17550_WriteReg(FIFOLevelReg, 0x80); FM17550_WriteReg(FIFODataReg, 0xA0); // WRITE命令 FM17550_WriteReg(FIFODataReg, block_addr); // 16字节数据 for (uint8_t i 0; i 16; i) { FM17550_WriteReg(FIFODataReg, data[i]); } FM17550_WriteReg(CommandReg, 0x0E); if (FM17550_WaitIrq(0x01, 25)) return 1; // 等待内部写操作完成时间约5ms HAL_Delay(6); return 0; }读块命令0x30后跟块地址卡片返回16字节数据加2字节CRC。FIFO里收到的字节数应该是18代码里读取时只取了前16字节。写块则要注意Mifare Classic的块0厂商块出厂时被永久锁定只能读不能写。如果尝试写块0卡片会应答错误这是正常的。另外S50的块结构里扇区尾部块3写的是密钥和访问控制位写坏一张卡就废了。调试时务必避开扇区0的块3或者先用块1块2做读写测试。4.4 Mifare Ultralight和NTAG系列没有认证的轻量读法不是所有ISO14443A卡都需要认证。Mifare Ultralight和NTAG系列卡没有密钥体系直接读块即可。这类卡的读卡流程更简单REQA拿到ATQA后直接发读命令0x30读取页面数据即可。uint8_t FM17550_ReadUltralight(uint8_t page, uint8_t *data) { FM17550_WriteReg(ComIrqReg, 0x7F); FM17550_WriteReg(FIFOLevelReg, 0x80); FM17550_WriteReg(FIFODataReg, 0x30); FM17550_WriteReg(FIFODataReg, page); FM17550_WriteReg(CommandReg, 0x0E); if (FM17550_WaitIrq(0x01, 25)) return 1; uint8_t len FM17550_ReadReg(FIFOLevelReg); for (uint8_t i 0; i len; i) { data[i] FM17550_ReadReg(FIFODataReg); } return 0; }页面大小是4字节。这个流程同样适用于NFC标签里的NTAG213/215/216。接入逻辑可以做成先发REQA根据ATQA的值判断卡类型走认证流程还是轻量读取流程。这样一套代码同时支持Mifare Classic和NTAG系列能覆盖市面上绝大多数M1卡和NFC标签。5. FM17550读卡程序避坑典型翻车现象、原因溯源与修复路径5.1 天线测不到卡初始化顺序错了芯片没有载波输出现象Init函数跑完后用示波器探天线两端没有任何13.56MHz信号卡片放在天线上一动不动。原因最常见的是TxControlReg没有正确写入0x83天线驱动管脚根本没输出。其次是软复位后延时不够芯片内部状态机还在复位过程中后续寄存器写入全部无效。还有一种情况SPI总线接到FM17550后没有等电源稳定上电瞬间写寄存器时序错乱导致初始化里某些寄存器写失败。解决第一步先读寄存器TxControlReg实际值确认是0x83而不是默认值。第二步检查软复位后延时是否足够把HAL_Delay(10)改成HAL_Delay(50)排除这个变量。第三步用示波器在CS引脚上确认SPI波形正常排除MCU引脚配置问题。我调试时习惯在每一条写寄存器操作后面加一句读回验证跑一遍Init后打印关键寄存器这种问题十秒钟就能定位。5.2 卡片偶然能读但十次里有两三次失败现象程序整体没问题但读卡不稳定有时候要贴上去两三秒才有反应。测试卡片固定不动同一张卡反复测试成功率不稳定。原因射频匹配网络没有调到最佳谐振点。FM17550天线匹配电路包含一个LC并联谐振回路谐振频率要落在13.56MHz附近。天线线圈尺寸、走线寄生电容、串联电阻都会影响谐振。换芯后芯片输出阻抗和RC522有细微差异原天线参数不再精确匹配。解决调整天线匹配电路的电容值。常见做法是在天线线圈两端并联一个可调电容5到30pF调到一个读卡最灵敏的容值然后换成固定电容。用网络分析仪观察回波损耗是理想方案没有仪器的话就是调电容加实测找到一个刷卡成功率最高的容值。另外天线驱动的调制深度也会影响灵敏度TxAskReg的值可以尝试从0x40改成0x00或0x50结合实测效果选择。5.3 进了认证却写不进数据块地址和认证密钥不匹配现象认证返回成功但WriteBlock之后读回来的数据没有变化或者写入立即报错。原因Mifare Classic的访问控制位Access Bits限制了块的读写权限。厂家出厂时默认的访问位允许KeyA和KeyB都读数据但写数据可能需要特定的密钥。如果你认证用的是KeyB但访问位配置成KeyB不能写数据写操作就会失败。解决先读取目标扇区的块3解析访问控制位。常见做法是准备一套已知的密钥先往扇区尾部写一份全FF的访问位把扇区设成开放状态再用这套密钥自由读写数据块。调试阶段可以绕过访问控制等业务需要锁定卡时再研究访问位的bit映射。切记不要拿有用数据的卡来测试写访问位写坏了只能换卡。5.4 多张卡同时在场时程序卡死现象一张卡工作正常两张卡同时放在天线区域程序进入死循环LED不再闪串口也不打印数据。原因防碰撞处理不完整。代码里只做一次防碰撞就取UID当多张卡同时REQA时FIFO里收到的是碰撞数据BCC校验失败。程序如果在校验失败后没有继续发防碰撞命令而是等待下一次REQA会陷入不断碰撞的循环。更严重的是FM17550内部状态机可能卡在某个中间状态后续命令全部无响应。解决实现完整的防碰撞循环。当BCC校验失败时解析FIFOLevelReg和FIFO里的碰撞标志确定碰撞位置发送带部分UID的防碰撞命令。如果不需要完整的防碰撞逻辑可以降低轮询频率每次REQA前延时100ms让卡片先退出场区。这个操作在门禁场景是合理的因为人的刷卡动作天然是单张卡靠近。5.5 SPI读寄存器全是0xFF或0x00现象初始化时读任何寄存器都返回0xFF感觉芯片没反应。或者读回来全是0x00但波形看起来正常。原因0xFF通常是SPI方向位错误或CS时序不对。FM17550的SPI从机在CS拉高后MISO会释放如果MISO线上有上拉电阻读到的就是0xFF。0x00则是时钟极性和相位配置错位数据在错误的时钟边沿被采样。还有一种情况是SPI波特率太高超过芯片实际承受能力。解决检查SPI配置的CPOL和CPHAFM17550一般要求模式0CPOL0CPHA0。CS片选时序上确保一次传输过程中没有其他SPI设备占用总线。如果代码是用HAL库注意HAL_SPI_TransmitReceive和HAL_SPI_Transmit之间不要混用统一用TransmitReceive来维持一致的时序。最后把SPI分频系数调到最低速率试一遍排除速率过高的可能。6. 调试FM17550的进阶技巧从“能通”到“稳”波形、调参与读卡距离验证读卡程序跑通之后真正难的是把“能读卡”变成“每次都稳定读卡”。我把调试经验浓缩在三个方面波形验证、参数微调和读卡距离的定量测试。首先是波形验证。用逻辑分析仪抓SPI通信重点关注CS低电平期间的总线活跃度。FM17550写寄存器时CS低电平的时间应该在微秒量级。如果CS低电平期间夹杂了其他操作说明有代码在ISR里打断SPI传输需要把读写函数放进临界区或关中断保护。天线端波形用示波器看正常读到卡时解调后的信号应该是清晰包络没有毛刺。如果包络边缘有抖动先查供电纹波FM17550对电源噪声很敏感建议加一个10uF和0.1uF的去耦电容组合。其次是参数微调。FM17550有个接收增益寄存器RxGainReg默认值对应接收器增益约25dB。适当提高增益能增加读卡距离但增益过高会饱和。我的调试方法是从默认值开始每次加一档实测读卡距离如果距离没有提升反而出现误码就回退一档。调制深度方面如果读卡距离偏短检查TxAskReg的ASK调制深度设置100%调制0x40是默认选择但某些天线组合下降低调制深度反而能提升读卡稳定性这需要实测。最后是读卡距离验证。简单的测试方式拿一张标准ISO卡从距离天线5cm处缓慢靠近记录程序第一次成功读到卡的距离。然后把卡固定在这个距离反复执行100次寻卡加防碰撞统计成功率。如果成功率低于95%就需要调匹配电容。更严格的做法是测不同角度平放、垂直、倾斜45度的读卡距离模拟真实使用场景。我后来养成一个习惯任何读卡器项目在交付前都要跑一遍这个距离加成功率测试测试数据记录在项目笔记里。读卡器这种设备的“玄学”问题基本都是天线匹配和参数没调试到位不能指望代码逻辑去兜底。希望这篇笔记能帮你少走弯路——FM17550不难但该抠的细节一个都不能省。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑