拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM8S驱动FM17550读卡器:从SPI时序到ISO14443-A防碰撞完整指南

简介此资源是一套基于STM8S微控制器与FM17550智能卡接口芯片的嵌入式开发工程包面向需要实现接触式智能卡读写功能的开发者与嵌入式学习者。工程围绕FM17550芯片驱动及WorldGI8智能卡协议展开包含底层HAL抽象层、中断处理、卡操作读卡/写卡/验证及错误处理等实现。压缩包共122个文件约5.12MB以C源文件46个和头文件51个为主同时提供IAR工程配置ewp/ewd、链接脚本xcl、编译脚本bat/ps1及说明文档chm/html/md目录结构清晰便于直接导入开发环境。目前已有337人学习下载适合具备C语言基础、熟悉STM8架构并希望快速集成FM17550驱动的工程师参考。通过该工程可掌握智能卡通信协议的底层实现思路获得可复用的驱动代码与调试配置缩短同类项目开发周期。1. 先搞清楚 STM8S 和 FM17550 的组合要解决什么问题在低成本读卡设备里STM8S 加 FM17550 是一套很常见的方案。STM8S 的 FLASH 和 RAM 都不大但做 13.56MHz 非接触读卡已经足够FM17550 的寄存器体系与 MFRC522 兼容代码迁移成本低供应链选择也更稳妥。实际开发中真正卡人的不是芯片本身而是 SPI 时序、短帧发送和 FIFO 数据处理这三件事。很多人照着 RC522 的 Arduino 例程改放到 STM8S 上后卡号读出来了但随后防碰撞或选卡总失败问题往往出在寄存器初始化序列或 7-bit 短帧没有真正发出去。下面按最小系统搭建、初始化时序、ISO14443-A 寻卡防碰撞、FIFO 和错误寄存器排错的顺序把整套流程拆开讲。适合正在用 STM8S 做门禁、智能柜或读卡模块的嵌入式工程师也适合为老产品做国产替代选型时评估工作量的开发者。2. STM8S 侧 SPI 引脚分配与最小驱动框架2.1 为什么选硬件 SPI 而不是 GPIO 模拟FM17550 支持 SPI、I2C、UART 三种方式具体用哪种由芯片的接口选择引脚电平决定。用 STM8S 驱动时我一般优先选 SPI原因有三个FM17550 的 SPI 从模式时序简单寄存器读写都是一字节地址加一字节数据STM8S 的 SPI 模块带双缓冲可以为后续 FIFO 连续读写省下不少 CPU更重要的是很多 RC522 参考代码都是 SPI 写的FM17550 兼容这层接口移植时不容易出错。GPIO 模拟 SPI 虽然也能工作但在后续防碰撞流程里需要频繁收发多字节数据中断服务里手动翻转时钟很容易把 106kbps 的帧节奏打乱。如果产品只是为了省成本而不用硬件 SPI那至少也要把 CS 和 RST 留给 GPIOSCK、MOSI、MISO 全部走硬件复用。STM8S 的 SPI 在默认选项字节下的引脚是固定的PC5 是 SCKPC6 是 MISOPC7 是 MOSI。不同型号可能经过重映射但 STM8S003F3 这类小封装没有重映射直接用 PC5、PC6、PC7 最省事。CS 不一定要走硬件 NSS把 PA3 配成普通 GPIO 更好。FM17550 每次寄存器访问都需要把一个完整地址字节和一字节数据放在同一次片选低电平里用软件控制 CS 更直观中途还能插入延时来避开地弹噪声。2.2 引脚连接和片选控制下面以 STM8S003F3 为例。FM17550 的 SPI 从机模式下最关键的连接是 SDA、SDO、SCK、CS、RST。如果板子上没有 RST 走线FM17550 内部仍能用软件复位但硬件复位脚能在上电时提供更确定的状态所以软件里最好都接出来。FM17550 引脚信号方向STM8S003F3 引脚说明SDA输入PC7SPI MOSISDO输出PC6SPI MISOSCK输入PC5SPI 时钟CS输入PA3GPIO 片选低有效RST输入PA2GPIO 复位默认拉高RST 的极性要按模块原理图确认很多现成模块把 RST 拉低复位也有模块接的是高电平复位。如果模块原理图不明确可以先把 RST 当低有效处理拉低 5us 再拉高然后做一次软复位这种方式对两种极性都不会造成破坏。CS 必须在访问前后有一个完整的拉低拉高过程不能只靠 SPI 的 NSS 软管理否则 FM17550 会把一个寄存器访问里的连续多个字节误读成不同的寄存器地址。2.3 SPI 初始化和单字节收发函数初始化时先把 STM8S 的 SPI 配置成主模式、MSB 在前、8 位数据、NSS 软件管理时钟极性低、相位第一边沿采样对应常见的 SPI Mode 0。FM17550 手册里的时序虽然看起来和 MFRC522 模块有差异但兼容芯片几乎都默认 Mode 0。如果读回来的 FIFO 全是 0xFF先不要怀疑芯片把模式换成 Mode 3 再对比一次。void RF_SPI_Init(void) { // 使能 SPI 外设时钟 CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE); // PC5: SCK, PC7: MOSI 输出PC6: MISO 输入 GPIO_Init(GPIOC, GPIO_Pin_5 | GPIO_Pin_7, GPIO_MODE_OUT_PP_HIGH_FAST); GPIO_Init(GPIOC, GPIO_Pin_6, GPIO_MODE_IN_FL_NO_IT); // 片选、复位引脚 GPIO_Init(GPIOA, GPIO_Pin_3, GPIO_MODE_OUT_PP_LOW); // CS GPIO_Init(GPIOA, GPIO_Pin_2, GPIO_MODE_OUT_PP_HIGH); // RST SPI_Init(SPI1, SPI_FIRSTBIT_MSB, SPI_BAUDRATEPRESCALER_16, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_NSS_SOFT); SPI_Cmd(SPI1, ENABLE); }分频选 16是因为 STM8S 默认系统时钟是 16MHz 或 8MHzSPI 分频后大约 0.5 到 1MHz。FM17550 的 SPI 可以跑更高但调试阶段把速率压低能减少示波器上看不到的信号毛刺和跨时钟域问题。GPIO_MODE_OUT_PP_HIGH_FAST里的 FAST 指输出斜率不是引脚速度等级SPI 时钟在 1MHz 左右时用 SLOW 也可以。单字节收发用 SPI_SendData 加 SPI_ReceiveData注意要先等 TXE 标志。FM17550 从机的 MISO 数据是在最后一个时钟沿之后才送到主机移位寄存器里的如果不等 RXNE 直接读第一字节可能拿到上一次的残留值。uint8_t RF_Byte(uint8_t byte) { SPI_SendData(SPI1, byte); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_TXE) RESET); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_RXNE) RESET); return SPI_ReceiveData(SPI1); }寄存器地址的低 8 位在 SPI 线路上并不是原样发送的。FM17550 沿用 MFRC522 的做法地址左移一位bit7 作为读标志bit0 固定为 0。写寄存器时发(addr 1) 0x7E读寄存器时发((addr 1) 0x7E) | 0x80。void RF_WriteReg(uint8_t reg, uint8_t val) { GPIO_WriteLow(GPIOA, GPIO_Pin_3); RF_Byte((reg 1) 0x7E); RF_Byte(val); GPIO_WriteHigh(GPIOA, GPIO_Pin_3); } uint8_t RF_ReadReg(uint8_t reg) { uint8_t val; GPIO_WriteLow(GPIOA, GPIO_Pin_3); RF_Byte(((reg 1) 0x7E) | 0x80); val RF_Byte(0x00); GPIO_WriteHigh(GPIOA, GPIO_Pin_3); return val; }这个底层函数是所有后续逻辑的地基。调电压、读版本号、清 FIFO、发命令最终都落到这两个函数上。后面所有“能写不能读”的问题也基本都在这一层的地址位或 CS 时序上。3. FM17550 寄存器地图与初始化序列从软复位到开启天线3.1 先背下这 7 个寄存器地址FM17550 内部寄存器很多但一次读卡流程真正要直接操作的只有几个。下表把寻卡前必须认识的寄存器列出来地址按兼容 MFRC522 体系排列用前面定义的 RF_ReadReg、RF_WriteReg 访问。ComIrqReg 是中断和状态标志位ErrorReg 会记录 CRC、防碰撞、FIFO 溢出等错误。FIFO 是 64 字节深度的缓冲区ISO14443-A 的防碰撞数据包长度通常不超过 10 字节所以基本不会溢出但每次发命令前还是要手动清一次 FIFO避免上一次残留数据影响下一次判断。符号地址关键位/值用途CommandReg0x010x0F 软复位0x0E Transceive控制命令状态机ComIrqReg0x04bit0 RxIbit1 TxIbit4 ErrorI等待收发完成和错误中断ErrorReg0x060x01 CRCErr0x02 CollErr0x04 BufferOvfErr读取错误原因FIFODataReg0x09读写 64 字节数据收发数据缓冲FIFOLevelReg0x0A低 6 位表示 FIFO 内字节数判断卡片回复长度BitFramingReg0x0Dbit7 StartSend低 3 位 TxLastBits短帧和发送启动TxControlReg0x140x83 打开天线和调制输出决定射频场是否输出ErrorReg 和 ComIrqReg 是调试读卡失败时最先看的两个寄存器。很多时候卡片已经响应了但因为帧格式里少了最后一位RxI 没置位整个状态机就卡死在等待中断里。这类问题如果不看 ErrorReg会误以为硬件没工作。3.2 初始化序列先软复位再配置定时器和射频FM17550 的初始化顺序并不是从里到外把所有寄存器都写成推荐值而是要先让芯片脱离未知状态。通常做法是硬件复位后执行软复位等待至少 5ms再把 CommandReg 写回 0x00 回到 Idle。接下来配置定时器、发送接收模式、CRC 初值、射频发射参数最后打开天线。这样做的目的是保证后面 Transceive 命令发出时芯片内部的 CRC 协处理器和定时器已经进入确定状态。#define FM17550_CMD 0x01 #define FM17550_TX_CONTROL 0x14 #define FM17550_TX_AUTO 0x15 #define FM17550_MODE 0x11 #define FM17550_TX_MODE 0x12 #define FM17550_RX_MODE 0x13 #define FM17550_CRC_RES_L 0x22 #define FM17550_MOD_WIDTH 0x24 #define FM17550_RF_CFG 0x2A #define FM17550_T_MODE 0x2E #define FM17550_T_PRESCALER 0x2F #define FM17550_T_RELOAD_H 0x30 #define FM17550_T_RELOAD_L 0x31 void FM17550_Init(void) { // 硬件复位等电源稳定 GPIO_WriteLow(GPIOA, GPIO_Pin_2); delay_us(5); GPIO_WriteHigh(GPIOA, GPIO_Pin_2); delay_us(50); // 软复位 RF_WriteReg(FM17550_CMD, 0x0F); delay_ms(5); RF_WriteReg(FM17550_CMD, 0x00); // 定时器超时供 Transceive 等待使用 RF_WriteReg(FM17550_T_MODE, 0x8D); RF_WriteReg(FM17550_T_PRESCALER, 0x3E); RF_WriteReg(FM17550_T_RELOAD_H, 0x00); RF_WriteReg(FM17550_T_RELOAD_L, 0xCD); // 自动发送 CRC接收时按 ISO14443-A 解码 RF_WriteReg(FM17550_TX_AUTO, 0x40); RF_WriteReg(FM17550_MODE, 0x3D); RF_WriteReg(FM17550_TX_MODE, 0x00); RF_WriteReg(FM17550_RX_MODE, 0x00); // CRC 初始值 0x63调制宽度 0x26 RF_WriteReg(FM17550_CRC_RES_L, 0x63); RF_WriteReg(FM17550_MOD_WIDTH, 0x26); // 射频输出增益0x7F 是常用中间值 RF_WriteReg(FM17550_RF_CFG, 0x7F); // 打开天线和调制器 RF_WriteReg(FM17550_TX_CONTROL, 0x83); }这里每个值都对应一个明确的硬件行为。T_MODE 和 T_PRESCALER 决定内部定时器周期如果系统时钟不是 13.56MHzRELOAD 值要按手册重算不能照抄。MODE 写成 0x3D 时芯片使用发送和接收独立的模式自动开启 CRC 校验接收端按 Miller 解码。TX_AUTO 寄存器的 bit6 置 1让 FIFO 中的数据经过 CRC 协处理器后自动追加 CRC_A 校验字节这对后面的 Select 命令很重要。RF_CFG 控制发射驱动电流0x7F 是常规值在铁质外壳或天线面积偏小的产品里要把这个值调大但功耗也会上升。3.3 上电时序和天线开关的边界上电后不要立刻初始化FM17550 的天线输出级需要几十微秒才能稳定。若 RST 引脚悬空芯片可能在随机状态直接进入发送模式和 STM8S 的 SPI 抢占引脚电平。天线并非一直打开就最好。在低功耗门禁中读卡器是常供电的但长时间开启天线会让射频场里的 IC 卡频繁耗电。如果想做间断寻卡可以在初始化完成后把 TxControlReg 写回 0x80 关闭天线然后在每次寻卡前重新写 0x83。不过要注意天线关闭后再打开需要至少 1ms 左右的稳定时间否则紧接着发 REQA 会看不到卡响应。这个稳定时间不是靠软件延时就能无限压缩的它取决于匹配网络的 Q 值。调低 Q 值可以缩短稳定时间但也会降低读卡距离只能在实际天线阻抗下做取舍。4. 在 STM8S 上跑通 ISO14443-A 寻卡与防碰撞流程4.1 短帧 REQA 和 WUPA 的发送方式ISO14443-A 的寻卡阶段用的是短帧也就是 7 bit不是普通字节。FM17550 里要发短帧必须把 BitFramingReg 的 TxLastBits 写成 7而 CommandReg 选择 Transceive 命令后还需要把 BitFramingReg 的 bit7 置 1 才能真正启动发送。这一步是最容易漏的。若 BitFramingReg 一直是 0命令不会发送FIFOLevel 自然是 0现象就是“发完 REQA 后永远没有中断”。用 STM8S 调试时先手动读一遍 BitFramingReg确认它的值是 0x87 而不是 0x07。场景命令数据帧类型REQA0x26短帧 7 bitWUPA0x52短帧 7 bit防碰撞 CL10x93 0x20标准帧 CRC_A选卡 CL10x93 0x70 UID BCC标准帧 CRC_AREQA 和 WUPA 的区别在于REQA 只响应已经进入 Idle 状态的卡WUPA 可以把所有卡强制唤醒。多数读卡代码第一轮用 REQA超时后改用 WUPA两种命令的卡响应格式相同。4.2 用 Transceive 命令发送 REQA 并接收卡片类型以 20ms 超时为上限轮询 ComIrqReg 的 RxI 位。如果等待期间出现 ErrorI立即读取 ErrorReg把所有错误位清零后再重新开始。注意每次发送前要先通过 FIFOLevelReg 的 bit7 写 1 清空 FIFO这是 FM17550 手册里明确要求的写 0 会被忽略。uint8_t FM17550_Request(uint8_t mode, uint8_t *tagType) { uint8_t irq; uint8_t fifoLen; RF_WriteReg(FM17550_FIFO_LEVEL, 0x80); // 清空 FIFO RF_WriteReg(FM17550_BIT_FRAMING, 0x07); // 短帧 7 bit不启动发送 RF_WriteReg(FM17550_FIFO_DATA, mode); // 0x26 或 0x52 RF_WriteReg(FM17550_CMD, 0x0E); // Transceive RF_WriteReg(FM17550_BIT_FRAMING, 0x87); // bit71开始发送 RF_WriteReg(FM17550_COM_IRQ, 0x7F); // 清所有中断标志 irq FM17550_WaitIRQ(20); // 最多等 20ms if ((irq 0x10) ! 0) // ErrorI return 0xFF; if ((irq 0x01) 0) // 没有 RxI return 0xFE; fifoLen RF_ReadReg(FM17550_FIFO_LEVEL) 0x3F; if (fifoLen 1) return 0xFD; *tagType RF_ReadReg(FM17550_FIFO_DATA); // ATQA 低字节 RF_WriteReg(FM17550_CMD, 0x00); // 结束 Transceive RF_WriteReg(FM17550_BIT_FRAMING, 0x00); // 清 TxLastBits return 0x00; }这里 WaitIRQ 的返回值是 ComIrqReg 的直接读回值。RxI 位是 bit0ErrorI 是 bit4。为什么清中断后用等待因为命令寄存器从 Idle 切到 Transceive 的瞬间ComIrqReg 里的 IdleI 会被自动清除但 RxI、TxI 和 ErrorI 在命令开始前是上次操作的残留状态必须由软件写 0x7F 清除。若把这个清除动作放到发送之后可能会出现欺骗性的早期中断导致函数返回成功但 FIFO 里没有任何数据。4.3 防碰撞和 Select处理级联和 BCC 校验拿到 ATQA 后下一轮需要得到完整的 UID。FM17550 的防碰撞发送 0x93 0x20卡片返回 4 字节的 UID 序列加 1 字节 BCC。BCC 是前 4 字节的按位异或FM17550 不会自动校验必须软件算一次。如果 BCC 不符最大概率是 FIFO 里混入了噪声重新清空 FIFO 再发一次即可不一定代表卡坏了。防碰撞过程里还可能出现 CollErr这种情况通常发生在两张卡同时响应时需要做带位碰撞检测的流程这里只处理单卡场景。uint8_t FM17550_AntiCollision(uint8_t *uid) { uint8_t irq; uint8_t len; uint8_t i; uint8_t bcc; RF_WriteReg(FM17550_FIFO_LEVEL, 0x80); RF_WriteReg(FM17550_BIT_FRAMING, 0x00); RF_WriteReg(FM17550_FIFO_DATA, 0x93); // 防碰撞级联 1 RF_WriteReg(FM17550_FIFO_DATA, 0x20); RF_WriteReg(FM17550_CMD, 0x0E); RF_WriteReg(FM17550_BIT_FRAMING, 0x80); // 标准帧启动发送 RF_WriteReg(FM17550_COM_IRQ, 0x7F); irq FM17550_WaitIRQ(20); if ((irq 0x10) ! 0) { return 0x01; } if ((irq 0x01) 0) { return 0x02; } len RF_ReadReg(FM17550_FIFO_LEVEL) 0x3F; if (len 5) { return 0x03; } for (i 0; i 4; i) { uid[i] RF_ReadReg(FM17550_FIFO_DATA); } bcc RF_ReadReg(FM17550_FIFO_DATA); if ((uid[0] ^ uid[1] ^ uid[2] ^ uid[3]) ! bcc) { return 0x04; } return 0x00; }防碰撞成功后选卡命令要把整个 UID 和 BCC 再发给卡片FM17550 会启动硬件 CRC 协处理所以写入选卡命令时不需要手动填 CRC_A。选卡返回的 SAK 字节在 FIFO 里只有一个如果 SAK 的 bit5 被置位说明 UID 长度超过 4 字节还要继续做级联 2 的循环。很多只做过 4 字节 UID 产品的工程师接到一张 7 字节 UID 的卡就发现程序死循环正是少了这一步判断。uint8_t FM17550_Select(uint8_t *uid) { uint8_t irq; uint8_t len; uint8_t i; uint8_t sak; RF_WriteReg(FM17550_FIFO_LEVEL, 0x80); RF_WriteReg(FM17550_BIT_FRAMING, 0x00); RF_WriteReg(FM17550_FIFO_DATA, 0x93); RF_WriteReg(FM17550_FIFO_DATA, 0x70); for (i 0; i 5; i) { // 4字节UID BCC RF_WriteReg(FM17550_FIFO_DATA, uid[i]); } RF_WriteReg(FM17550_CMD, 0x0E); RF_WriteReg(FM17550_BIT_FRAMING, 0x80); RF_WriteReg(FM17550_COM_IRQ, 0x7F); irq FM17550_WaitIRQ(20); if ((irq 0x10) ! 0) return 0x01; if ((irq 0x01) 0) return 0x02; len RF_ReadReg(FM17550_FIFO_LEVEL) 0x3F; if (len 1) return 0x03; sak RF_ReadReg(FM17550_FIFO_DATA); RF_WriteReg(FM17550_CMD, 0x00); RF_WriteReg(FM17550_BIT_FRAMING, 0x00); return (sak 0x20) ? 0x80 : 0x00; // bit5 表示需要级联 }选卡的参数说明0x93 表示 SEL 命令的级联级 CL10x70 是 BCC 后要跟随 CRC_A 的选择命令码。如果产品需要兼容 7 字节 UID可以把 0x93 换成 0x950x70 的含义不变。这里的代码没有在 Select 后执行 MIFARE 认证因此只适合纯粹获取 UID 的场景。要读写扇区还需要在 Select 后补上厂商认证命令和密钥加载流程。4.4 主循环里的读卡状态机把上面几个函数串进主循环时不能 REQA 失败后立即重发要留 5ms 左右的射频去抖时间。FM17550 的射频场和卡片上电复位有一定交互连续 REQA 太快会让卡片一直处于上电状态忽略 REQA。用状态机实现会比单纯 delay 更稳每次进入寻卡状态前先看距上次结束的时间超过 20ms 才发 REQA。这样不仅能降低误码率还能把读不到卡时的 CPU 空转时间留给其他任务。从状态机返回值看0x00 表示成功0xFE 表示无卡其他值直接进入 Debug 分支打印寄存器值。5. 调试技巧用 FIFO 水位和错误寄存器定位读不到卡的根因5.1 让 ErrorReg 先开口读卡失败时先别改天线匹配把 ErrorReg 原始值打出来。0x01 是 CRC 错误0x02 是防碰撞冲突0x04 是 FIFO 溢出0x80 是奇偶校验错误。如果看到 0x04说明 FIFO 数据没有及时取走要检查等待接收时是否在读到 FIFOLevel 前就关了 CS。如果看到 0x80问题多数出在发送端落在 STM8S 的 SPI 时钟相位上把 CPOL 和 CPHA 调成 Mode 3 再读 ErrorReg 对比。uint8_t err RF_ReadReg(0x06); uint8_t fifoLen RF_ReadReg(0x0A) 0x3F; uint8_t irq RF_ReadReg(0x04);三个值同时看比只看中断标志更容易缩小范围FIFO 里有数据但 err 不为 0说明是数据校验层的问题FIFO 为空且 err 为 0说明是发送端的问题FIFO 为空且 err 为 0x80说明是射频调制或短路的问题。5.2 FIFO 水位比中断标志更可信FM17550 的 ComIrqReg 在 Transceive 结束瞬间会同时置位好几件事有时 ErrorI 和 RxI 一起出现。调试时先读 FIFOLevel再读 ErrorReg最后读 ComIrqReg。FIFO 里有没有数据能直接区分“卡没回应”和“卡回应了但没通过校验”这两种场景。FIFOLevel 为 0 且 ErrorReg 为 0则问题一定在 REQA 发送端优先检查 BitFramingReg 的 0x87 发送模式。5.3 把寻卡时间压到 10ms 量级常规读卡循环里 20ms 超时对门禁足够但低功耗设备希望每次寻卡尽量短。可以把 T_RELOAD_L 从 0xCD 调到 0x20 左右同时把 WaitIRQ 的时间阈值跟着缩短。更重要的是在 REQA 成功后立刻关天线等读卡逻辑做完再开这样单次寻卡的射频开启时间能从 50ms 降到 10ms 左右。调整 T_RELOAD 后用示波器抓 IRQ 引脚确保下一张卡的 REQA 发送发生在稳定窗口内否则会出现“第一张卡正常第二张卡丢卡”的周期性故障。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门