ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

FM17550读写S50卡完整指南:从硬件天线到代码实现

FM17550读写S50卡完整指南:从硬件天线到代码实现 1. 从零开始FM17550 与 S50 卡到底能做什么做门禁、校园卡、会员刷卡器或者宿舍水电控项目的朋友应该都对 RFID 读写不陌生。市面上最常见的方案是 RC522而 FM17550 作为国产替代芯片在很多量产项目里已经把 RC522 替换掉原因是稳定性和供货都更靠谱。S50 卡就是大家常说的 M1 卡属于 NXP 的 Mifare Classic 系列频率 13.56MHz是目前国内门禁、食堂、图书馆系统里应用最广的非接触 IC 卡。FM17550 是一颗高度集成的非接触读写芯片兼容 ISO14443A 协议所以它天生就能直接读写 S50/S70 这类 Mifare 卡片。很多工程师第一次接触这个组合时最容易卡住的点主要在三处硬件上 FM17550 的封装和天线电路怎么处理初始化代码里寄存器配置的参数怎么定以及认证和读写块操作的时序怎么对。这三个问题一旦搞明白了整个项目基本就通了一半。这篇文章把我自己做项目的完整过程拆开来讲从引脚定义、硬件连接、天线布局到 C 语言实现的初始化、寻卡、防碰撞、选卡、认证、读块、写块全流程最后再聊聊我实际踩过的一些坑。适合刚开始接触 FM17550 或者想从 RC522 迁移过来的工程师参考手把手照着做基本都能跑通。2. 硬件连接与电路设计别栽在第一步2.1 FM17550 和 RC522 的引脚关系FM17550 的封装是 QFN32引脚定义和 RC522 高度相似如果你之前画过 RC522 的板子可以直接把芯片替换过来外围电容电阻参数几乎不用改。核心引脚主要包括电源、复位、通信接口和天线接口四组。电源引脚VDD3.3V、PVDD、TVDD全部接 3.3V每个电源引脚旁边都要放一个 0.1uF 去耦电容靠近引脚放置。复位引脚RSTPD 低电平有效接 MCU 的一个 GPIO用于软件复位。通信接口FM17550 支持 SPI、I2C 和 UART 三种模式通过引脚电平配置选择。默认情况下大家用得最多的是 SPI 模式接线简单速度快。SPI 模式用到 SCK、MOSI、MISO、NSS 四个引脚。天线接口TX1、TX2 接天线驱动电路RX 接接收电路这两个部分是硬件设计的核心重点。需要注意的是FM17550 的工作电压是 3.3V如果你的 MCU 是 5V 的比如老款 STM32 某些型号或者 51 单片机SPI 引脚之间需要做电平转换不然长期运行容易损坏芯片。我在实际项目中直接用 3.3V 的 STM32F103 来驱动电压匹配不需要额外电路。2.2 天线电路设计要点天线电路是 FM17550 硬件里最容易出问题的地方。FM17550 内部已经有功放电路但 TX1/TX2 输出的差分信号不能直接接天线线圈需要经过 LC 匹配网络。典型电路如下TX1 通过一个电容典型值 33pF接到天线一端TX2 通过一个电容典型值 33pF接到天线另一端天线两端之间并接一个电容典型值 27pF用于谐振调谐RX 引脚通过电阻分压网络接到天线用于接收卡片的负载调制信号天线的形状和尺寸直接决定了读写距离。我实测下来用 PCB 天线画一个 50mm x 50mm 的矩形线圈线宽 0.3mm匝数 3 圈读写距离大约能到 4 到 6 厘米。如果用市场上买的那种成品铜线圈天线读写距离会稍微远一点。这里有一个非常关键的调试点天线谐振频率必须准确落在 13.56MHz 附近。没有网络分析仪的情况下可以用示波器探头接在天线两端观察发送时的震荡波形如果波形幅值最大且稳定说明谐振点基本正确。如果天线设计不好最典型的故障是读卡距离特别近甚至贴上去都读不到。注意天线附近的金属物体和地平面会吸收磁场能量严重缩短读卡距离。PCB 天线正下方不要大面积铺地元器件也不要放在天线线圈正中间的上方区域。2.3 模块化接线参考自己做板子的时候我习惯把 FM17550 做成一个独立的小模块通过排针引出 SPI 接口然后插到主板上。这样调试方便坏了直接换模块。完整接线参考如下FM17550 引脚功能连接目标VDD / PVDD / TVDD3.3V 电源统一接 3.3V各接 0.1uF 去耦电容GND / PVSS / TVSS地统一接 GNDSDASPI 模式下为 MOSI数据输入MCU 的 SPI_MOSISCLSPI 模式下为 SCK时钟MCU 的 SPI_SCKTX1 / TX2天线驱动输出接 LC 匹配网络和天线RX接收输入接天线接收电路NSS片选MCU 的 GPIO低电平有效RSTPD复位MCU 的 GPIO低电平有效MFIN / MFOUT副载波输入输出一般悬空不用接我开始做的时候犯过一个低级错误把 NSS 直接接 GND导致 SPI 通信始终在选中状态虽然单设备没大问题但后来要接多个 SPI 设备时就会冲突。建议还是用一个 GPIO 控制片选方便后续扩展。3. 通信协议与初始化流程代码前先搞清楚这个3.1 S50 卡的存储结构写代码之前必须把 S50 卡的内存结构搞明白否则后面的读块写块操作全是盲目的。S50 卡一共有 1024 字节的 EEPROM分成 16 个扇区每个扇区 4 个块每个块 16 字节。也就是说每个扇区 64 字节16 个扇区总共 1024 字节第 0 扇区的第 0 块存放厂商代码UID出厂前写死只读不可修改每个扇区的第 3 块是扇区尾部存放密钥 A、访问控制位、密钥 B这个块叫 Trailer Block一般不能直接当数据块用数据读写的基本单位是“块”一次读 16 字节一次写 16 字节。所以你要修改一张卡的数据最少也要以一个块为单位来操作。扇区地址的计算方法也很直接S50 卡总共有 64 个块块地址从 0 到 63。扇区 0 对应块 0 到 3扇区 1 对应块 4 到 7扇区 n 对应块 n*4 到 n*43。实际应用里最常见的做法是把一个扇区的块 0、块 1、块 2 当数据区块 3 当密钥配置区这样每张卡最多可以存 48 字节有效数据如果只用 16 个扇区中的数据区就是 16*48768 字节。3.2 M1 卡认证机制简述S50 卡不是随随便便就能读写的它在读写数据块之前必须进行三重认证。简单理解就是卡和读写器互相验证密钥验证通过之后才能执行后续的读或写操作。默认情况下S50 卡出厂密钥是 12 个字节的 FFFFFFFFFFFF密钥 A 和密钥 B 都是不过通常拿到卡片后会自行修改。认证时读写器向卡发送密钥卡片内部进行校验返回认证结果。认证失败后续所有读写操作都会被拒绝。FM17550 内部把这些流程封装成了寄存器操作命令我们在 MCU 端只需要通过 SPI 发送相应的命令帧芯片会自动完成 ISO14443 物理层的交互。这也是 FM17550 比较好用的地方复杂的模拟前端、编码解码都由芯片完成MCU 只负责上层协议。FM17550 的命令帧格式一般遵循以下结构字节含义0x00头部固定值0x00头部固定值0xFF帧开始标识命令长度后续数据的长度命令字例如寻卡 0x26、防碰撞 0x93 等数据域与命令相关的参数校验和长度加数据所有字节的加和写代码的时候我们把这一帧数据通过 SPI 发送给 FM17550然后芯片返回处理结果和应答数据。3.3 初始化过程应该做什么FM17550 上电后不会自动进入正常工作状态需要 MCU 完成以下步骤硬件复位拉低 RSTPD再拉高发送软件复位命令0x01 命令字等待芯片响应配置定时器、中断、天线驱动等相关寄存器开启天线发送这里要注意一个细节很多人直接用 RC522 的初始化代码套 FM17550虽然大部分寄存器地址兼容但有些默认值和功能不完全一样。最稳妥的方式是参考 FM17550 的数据手册把寄存器初始化表逐一对照而不是盲目照搬。下面是我在实际项目中梳理出来的初始化精简流程C 语言风格实际代码根据平台调整// FM17550 寄存器操作函数声明 void FM17550_WriteRegister(uint8_t reg, uint8_t value); uint8_t FM17550_ReadRegister(uint8_t reg); void FM17550_Rst(void) { // 1. 硬件复位RSTPD 拉低至少 1ms 以上再拉高操作片选 HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET); // 2. 软件复位命令0x01 是软复位指令发送地址寄存器的方式 // 需要先发送 0x00 到 CommandReg(0x01)再发送其他配置 FM17550_WriteRegister(0x01, 0x0F); // 软复位命令 HAL_Delay(5); // 3. 写寄存器初始化配置 // 关闭定时器设置接收模式等 FM17550_WriteRegister(0x2A, 0x80); // TModeReg 关闭定时器程序模式 FM17550_WriteRegister(0x2B, 0x80); // TPrescalerReg 定时器分频 FM17550_WriteRegister(0x2C, 0x66); // TReloadRegH 定时器装载高位 // 4. 开启天线 FM17550_WriteRegister(0x14, 0x03); // TxControlReg 打开天线1和天线2 // 5. 设置接收增益等根据实际调试微调 FM17550_WriteRegister(0x26, 0x1F); // RxGainReg }提示上面用到的寄存器地址在不同版本芯片上可能存在细微差异比如 RC522 的 ManualRC522 文档和 FM17550 官方手册对某些保留寄存器定义不同。务必以你手上那颗芯片对应的数据手册为准不可全盘照搬记忆。4. 完整代码实现寻卡、防碰撞、选卡、认证、读写全流程4.1 发送命令帧的封装FM17550 的 SPI 通信核心是写命令帧和读响应帧。SPI 时序相对简单片选拉低通过 SPI 写入命令字节然后继续写读写寄存器地址等参数。这里我习惯封装三个基础函数// 写寄存器 void FM17550_WriteRegister(uint8_t reg, uint8_t value) { HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET); uint8_t tx[2]; tx[0] (reg 1) 0x7E; // 地址左移一位写标志为 0 tx[1] value; HAL_SPI_Transmit(hspi1, tx, 2, 100); HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET); } // 读寄存器 uint8_t FM17550_ReadRegister(uint8_t reg) { HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET); uint8_t tx[2]; uint8_t rx[2]; tx[0] ((reg 1) 0x7E) | 0x80; // 读标志为 1 tx[1] 0x00; // 读取时发送任意数据产生时钟即可 HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 100); HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET); return rx[1]; } // 发送完整命令帧并读取响应 uint8_t FM17550_SendCommand(const uint8_t *sendData, uint16_t sendLen, uint8_t *recvData, uint16_t *recvLen) { uint8_t status 0; // 组装命令帧 uint8_t frame[64]; uint8_t idx 0; frame[idx] 0x00; frame[idx] 0x00; frame[idx] 0xFF; frame[idx] (sendLen 2) 0xFF; // 命令长度 数据长度 命令字 校验 uint8_t checksum 0; checksum frame[3]; for (uint16_t i 0; i sendLen; i) { frame[idx] sendData[i]; checksum sendData[i]; } frame[idx] checksum; // 通过 SPI 发送 HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, frame, idx, 100); HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET); // 等待 BM 中断或者延时后读取响应 HAL_Delay(2); return status; }4.2 寻卡发送 REQA 命令寻卡的作用是探测射频场里有没有卡片以及卡片是否 ISO14443A 类型。FM17550 支持以命令 0x26 发送 Request 标准请求。常见的请求有两种0x26 是 ISO14443A 的 REQA短帧请求0x52 是 WUPA唤醒请求。区别在于对于已经处于 IDLE 状态的卡REQA 会把它唤醒对于处于 HALT 状态的卡需要用 WUPA 来唤醒。寻卡流程发送请求后需要启动芯片的接收功能等待卡片的 ATQA 应答。ATQA 是两个字节的数据可以用于判断卡片类型。S50 卡的典型 ATQA 是 0x0004。uint8_t FM17550_Request(uint8_t reqMode, uint8_t *atqa) { uint8_t cmd[] {0x00, 0x00, 0xFF, 0x03, 0x03, 0x26, 0x07}; // 这里0x26为REQA uint8_t recvData[16]; uint16_t recvLen 0; // 发送前清空 FIFO FM17550_WriteRegister(0x02, 0x0F); // FIFOLevelReg 清FIFO // 开启接收模式然后发送命令 FM17550_WriteRegister(0x01, 0x0C); // CommandReg 写 Transceive 命令 // 向 FIFO 写入发送数据reqMode FM17550_WriteRegister(0x03, reqMode); // FIFODataReg // 启动发送通过 BitFramingReg 设置最后一个字节的比特数 FM17550_WriteRegister(0x0D, 0x07); // BitFramingReg 7 bits for REQA // 等待接收到数据 HAL_Delay(2); // 读取状态寄存器和 FIFO 中的数据 if (FM17550_ReadRegister(0x05) 0x20) { // 检查 FIFO 有数据 uint8_t len FM17550_ReadRegister(0x0A); // FIFOLevelReg if (len 2) { atqa[0] FM17550_ReadRegister(0x03); atqa[1] FM17550_ReadRegister(0x03); return 0; } } return 1; }实际项目中我会把发送过程的命令、寄存器状态检查、超时判断等细节拆细这里先用最直观的逻辑表达关键步骤。每个操作后建议加一个状态判断如果芯片的 ErrorReg寄存器 0x06出现错误位要及时清理。4.3 防碰撞拿到完整的 UID当射频场中同时有多张卡时防碰撞功能就派上用场了。所有卡片同时响应请求读写器通过逐位比较的方式选出一张卡片进行通信。对于 S50 卡防碰撞命令是 0x93后面跟着 4 字节 UID 的级联校验过程。单张卡环境下防碰撞比较简单发送 0x93 命令和 0x20 参数表示完整的防碰撞序列卡片会返回 5 字节数据其中前 4 字节是 UID最后 1 字节是 UID 的校验值。uint8_t FM17550_AntiCollision(uint8_t *uid) { uint8_t len 0; // 发送防碰撞命令 uint8_t cmd[] {0x93, 0x20}; // 通过 Transceive 命令发送 cmd 数组并接收 5 字节应答 // 应答的前 4 字节是 UID第 5 字节是校验 // 此处省略底层发送接收细节 // 校验 UID 的 XOR 值是否匹配 uint8_t checksum uid[0] ^ uid[1] ^ uid[2] ^ uid[3]; if (checksum ! uid[4]) { return 1; // 校验失败 } return 0; }注意防碰撞返回的 UID 顺序是高位在前在存储和显示的时候要注意字节序问题。很多人在把 UID 打印到串口时发现和读卡器读出来的顺序不一样就是这个原因。4.4 选卡让卡片进入 ACTIVE 状态拿到 UID 后下一步是选卡。选卡命令同样以 0x93 开头参数是 0x70 加上完整的 4 字节 UID 和校验值。卡片收到选卡命令后会返回一个字节的选择确认码SAKS50 卡的 SAK 通常是 0x08表示它是 Mifare Classic 1K 卡片。选卡成功后卡片进入 ACTIVE 状态可以继续执行认证和读写操作。如果选卡失败可能是 UID 校验不对或者是卡片类型不支持。uint8_t FM17550_SelectCard(const uint8_t *uid, uint8_t uidLen, uint8_t *sak) { if (uidLen ! 4) return 1; uint8_t cmd[7]; cmd[0] 0x93; cmd[1] 0x70; cmd[2] uid[0]; cmd[3] uid[1]; cmd[4] uid[2]; cmd[5] uid[3]; cmd[6] uid[0] ^ uid[1] ^ uid[2] ^ uid[3]; // 发送命令接收 SAK 应答 // 成功返回 0并把 *sak 指向接收到的 SAK 字节 if (*sak ! 0x08) { // 有的兼容卡 SAK 可能不同需要根据实际卡片类型判断 } return 0; }4.5 三重认证读写前的关键门槛S50 卡认证命令是 0x60验证密钥 A或 0x61验证密钥 B。命令格式如下第一个字节0x60 或 0x61第二个字节块地址后面跟 6 字节密钥FM17550 内部会把密钥写入密钥寄存器然后向卡片发起认证。卡片返回认证通过后之后的读写操作就不需要再认证了但每次换扇区换到不同的块地址所在扇区后需要重新认证。也就是说认证是以“扇区”为单位的。uint8_t FM17550_Auth(uint8_t authMode, uint8_t blockAddr, const uint8_t *key) { uint8_t cmd[8]; cmd[0] authMode; // 0x60 用密钥A0x61 用密钥B cmd[1] blockAddr; // 目标块地址 for (int i 0; i 6; i) { cmd[2 i] key[i]; } // 先通过 SPI 把密钥写入 FM17550 寄存器KeyAB 或 KeyA、KeyB 系列寄存器 // 然后发送认证命令到 FIFO执行 Transceive // 最后检查卡片返回的状态如果错误寄存器置位则认证失败 // 认证成功后后续读写就能正常进行了 if (FM17550_ReadRegister(0x06) 0x08) { // 如果 ErrorReg 的 Protocol 错误位被置位 return 1; } return 0; }这一块很容易踩坑的是密钥的字节序。S50 卡的密钥从第一个字节到第六个字节按照标准顺序填写比如默认密钥就是 6 个 0xFF。有的封装库会要求先反转密钥但 FM17550 的数据手册里没有这种要求直接按顺序发送就行。遇到认证失败时先检查密钥字节顺序再检查卡片当前的访问控制位配置。4.6 读块操作读块命令是 0x30后面跟一个字节的块地址。执行 Transceive 后芯片会返回 16 字节的块数据再跟两个字节的 CRC 校验。uint8_t FM17550_ReadBlock(uint8_t blockAddr, uint8_t *data) { uint8_t cmd[2]; cmd[0] 0x30; cmd[1] blockAddr; // 发送读块命令 // 等待并接收 18 字节数据16字节数据 2字节CRC // 把前 16 字节拷贝到 data 数组中 return 0; }读块看似简单但有一个前提容易被忽略读取块的访问权限受该扇区块 3 中访问控制位的控制。如果访问控制位配置为某个块需要密钥 B 认证而你用密钥 A 认证读操作同样会被拒绝。遇到读写不成功时先确认你认证用的密钥和该块的访问权限是否匹配。4.7 写块操作写块命令是 0xA0后面跟块地址和 16 字节数据。卡片收到写指令后会先返回一个 4 字节的 ACK 帧表示准备好接收数据然后读写器需要再发送 16 字节的数据和 2 字节 CRC卡片完成写入后会再返回一个 ACK。uint8_t FM17550_WriteBlock(uint8_t blockAddr, const uint8_t *data) { uint8_t cmd[2]; cmd[0] 0xA0; cmd[1] blockAddr; // 发送写块命令等待 ACK // 确认 ACK 后发送 16 字节数据 2 字节 CRC // 等待最终 ACK 确认 return 0; }写块有一个特别要提醒的点S50 卡每个块在写入之前必须确保该块已经擦除或者允许覆写这是由访问控制位决定的。有些卡片出厂设置为“不可写”直接写会返回错误。另外写操作要紧跟认证不能隔太久否则卡片可能因为超时退出 ACTIVE 状态。5. 实操记录与常见问题排查实录5.1 硬件排查读不到卡怎么办我在调试 FM17550 时遇到过两次“读不到卡”的情况一次是天线匹配不对一次是 SPI 通信配置错误。先排查 SPI用示波器或者逻辑分析仪抓 SCK、MOSI、MISO 三根线的波形确认 FM17550 是否正确返回数据。如果 MISO 始终为低或高电平先检查片选时序——片选拉低后要有足够的建立时间发送完命令后要拉高片选。FM17550 的 SPI 速率我一般限制在 2MHz 以内过快可能导致通信不稳定。再排查天线用手指按压天线线圈附近如果读卡距离有明显变化说明天线输出功率和匹配不对需要调整匹配电容的值。还有一种情况是卡片放上去时示波器能看到发送波形但完全没有接收信号这时候重点检查 RX 引脚的分压网络是不是接错了。最后排查电源FM17550 发射时需要较大的瞬态电流如果电源走线太细或去耦电容不足会导致电压跌落芯片工作不稳定。我给天线部分单独加了一颗 10uF 钽电容问题立刻就解决了。5.2 软件排查认证失败、读写失败认证失败的原因我整理成了一张排查表现象可能原因解决办法认证命令发出后无响应卡片未激活选卡步骤失败检查 UID 是否从防碰撞正确获取重新执行寻卡到选卡全流程密钥 A 认证失败密钥错误或字节序错误确认默认密钥是否为 FFFFFFFFFFFF确认 6 字节密钥发送顺序密钥 B 认证失败访问控制位禁止用密钥 B 认证检查块 3 访问控制位改用密钥 A 或重新配置访问位读块失败该块访问权限为“不可读”查看数据手册的访问位真值表确认当前权限写块失败该块访问权限为“不可写”或卡片处于 HALT重新执行寻卡到认证全流程检查访问控制位读写失败还有一个容易被忽略的点就是块地址必须与当前认证的扇区匹配。比如你认证的是扇区 0块 0 到 3却去读块 7扇区 1FM17550 不会报错但卡片会拒绝。正确做法是先认证块 4再读块 7。每次切换扇区都要重新认证。5.3 兼容性差异FM17550 和 RC522 代码迁移如果你之前写过 RC522 的代码迁移到 FM17550 时寄存器地址映射大部分一致但这几个点需要注意软件复位命令的值可能不同RC522 用 0x0FFM17550 建议也在软复位后等待更长时间。天线控制寄存器TxControlReg的默认值不同FM17550 的默认值未必开启了天线必须显式写入。接收增益寄存器RxGainReg的默认值不同影响读卡灵敏度需要实测调整。命令帧格式帧头、长度、校验方式是完全一样的因为两者都遵循相同的 SPI 命令规范所以封装好的上层代码寻卡、防碰撞、认证、读写几乎可以无缝复用。我做过一次从 RC522 到 FM17550 的移植改动量大约是 30% 的底层寄存器配置代码上层协议栈代码基本没动。这也是 FM17550 兼容性做得好的一面对工程师来说迁移成本很低。5.4 关于 S50 卡容量和备份的小技巧实际项目里我习惯用扇区 0 的块 1 和块 2 存卡的唯一标识或个人信息因为 UID 是只读的没办法修改所以哪怕卡片被复制通过 UID 可以做一层初级校验。但要注意UID 本身是可以被特殊设备修改的市面上有 UID 可改卡所以严格的安全场景不能只依赖 UID还要配合私有加密协议。S50 卡的工厂默认密钥 FFFFFFFFFFFF 很容易被猜解量产项目上线前建议要修改每张卡的密钥。推荐的做法是每张卡写入一个随机密钥然后在后台数据库里建立 UID 和密钥的映射关系。这样一来即使某张卡的密钥泄露也不会波及其他卡。另外写卡时最好先读一次块内容确认不是重要数据区再执行写操作。S50 卡虽然没有磨损寿命问题EEPROM 写入寿命通常十万次以上但误写导致数据丢失的过失还是要避免。6. 写在最后的几个实操心得做 FM17550 的读卡器硬件上最大的心得是天线匹配值得多花时间——我用矢量网络分析仪调过一次天线读卡距离从 3 厘米直接提升到 7 厘米这个提升比任何软件优化都明显。如果条件有限至少也要用示波器看看波形确认谐振点没偏太多。软件上的心得是把寻卡到认证的流程拆成独立函数每一步都返回状态码方便定位问题。我最开始把选卡和认证写在一个函数里出问题时分不清是哪一步失败后来拆开调试几分钟就定位到了问题。还有一个小技巧调试阶段可以把 UID 和操作结果通过串口打印出来方便对照卡片实际数据。等到功能稳定后再把这些调试输出关掉既能提高效率也能减少串口对时序的干扰。如果后续想扩展可以考虑在 FM17550 的基础上加一个 PNG 或 OLED 显示当前卡号再加上继电器控制电锁就是一个完整的门禁系统了。这篇内容讲到的每一层代码都是可以复用的基础模块往上叠加功能很容易。
返回列表