ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32与MFRC522的RFID读卡模块设计:原理图到调试全流程

STM32与MFRC522的RFID读卡模块设计:原理图到调试全流程 去年接了一个智能工具柜的项目客户要求员工刷卡领用工具、归还时再刷一次后台留一条记录。当时我的第一反应就是 STM32 加 RFID 读卡模块方案很快就定下来了。现在复盘这个项目发现从原理图到最终程序中间有几个环节非常值得整理成笔记尤其是 MFRC522 这颗读卡芯片虽然不是新东西但每次接线、调天线、写命令流程都会踩到相似的坑。这篇就完整分享一下我在“基于 STM32 设计 RFID 读卡模块”里做过的选型、原理图拆解、代码实现和调试经验给准备做门禁、考勤、智能储物柜、实验室资产管理的朋友一个可以直接抄作业的参考。1. 项目整体设计与思路拆解1.1 这个读卡模块到底要做什么先明确需求。RFID 读卡模块的核心任务就一个把贴在卡片上的 ID 信息通过射频方式读回来然后交给 MCU 处理。这里我用的是 13.56MHz 频率的 MFRC522 芯片支持 ISO/IEC 14443A 协议最典型的是 Mifare Classic 1KS50和 4KS70卡。很多人会问为什么用 13.56MHz 而不是 125kHz原因很简单13.56MHz 读卡距离通常在 3~8cm适合门禁、打卡、储物柜这类需要主动把卡贴上去的场景。卡片容量比 125kHz 的 EM4100 大得多M1 卡有 1KB 存储可以分扇区存用户 ID、余额、次数等业务数据。MFRC522 芯片价格低、资料多、方案成熟STM32 社区里驱动一大把遇到问题也容易搜到答案。所以整个项目可以拆成两块硬件上要画好电源、SPI 接口、天线匹配和声光提示电路软件上要完成 SPI 初始化、MFRC522 寄存器读写、请求卡片、防碰撞、选卡、认证、读写扇区这些步骤。1.2 为什么选 STM32F103C8T6 作为主控这个方案严格来说可以用任何带 SPI 的单片机甚至用 Arduino 也能跑但我选 STM32F103C8T6 有几点实际考虑主频 72MHz处理 Mifare 认证算法和通信协议绰绰有余后续如果要在代码里加加密、加网络协议栈性能也不会成为瓶颈。外设资源丰富SPI、UART、GPIO、定时器一应俱全一个芯片能管读卡器、管显示屏、管蜂鸣器不需要再挂额外 MCU。20KB RAM、128KB FlashMFRC522 的驱动库加 RTOS 加应用逻辑完全放得下不用天天抠 Flash 容量。这块板子 3.3V 供电和 MFRC522 电平完全兼容不需要额外的电平转换电路。我后来还做过一个基于 STM32F030 的简化版本成本更低但调试工具和参考代码明显不如 F103 生态好所以新手起步我还是建议用 F103C8T6 核心板等成熟了再考虑换料。1.3 系统整体架构整个读卡模块的工作链路大概长这样STM32F103C8T6 通过 SPI1 接口与 MFRC522 通信MFRC522 芯片内部完成 13.56MHz 载波调制、解调、编码解码然后通过天线线圈向无源卡片供电并交换数据卡片进入天线场区后MCU 发送请求命令卡片返回 UIDMCU 再做防碰撞和选卡最后决定是否继续认证和读写数据。板上还设计了两个用户提示设备一个 LED 用于显示读卡状态一个蜂鸣器用于刷卡成功提示。调试阶段我会把 UART 接到 USB 转串口模块把卡号打印到电脑上看这样方便验证整个链路是否通畅。从系统角度看MCU 只负责时序与控制真正硬核的射频部分都在 MFRC522 内部完成这大大降低了硬件设计难度。 我们不需要自己去调制 13.56MHz 正弦波只要把 MFRC522 喂对寄存器和命令它就能自动完成射频通信。这也是为什么这个方案非常适合学生做毕设、工程师做产品原型。2. 原理图关键电路拆解2.1 电源电路设计MFRC522 模组和数据手册都明确要求供电范围是 2.5V 到 3.3V注意它不像某些 TTL 模块那样可以直接吃 5V。我们自制的读卡板供电方式有两种如果系统输入是 5V 适配器就用 AMS1117-3.3 线性稳压产生 3.3V电路简单成本低读卡这种负载很小的场景完全够用。如果系统输入本身就是 3.3V那就直接给 STM32 和 MFRC522 供电但要确保电源纹波不要太大。电源电路里我建议在 MFRC522 电源引脚附近放一个 10uF 电解电容和一个 100nF 陶瓷电容分别用于滤低频和高频噪声。 天线发射瞬间电流会突然增大如果电源内阻大或者滤波电容不够很容易出现读卡距离变短甚至卡片无响应的情况。注意MFRC522 的数字电源脚 AVDD、TVDD、PVDD 和 DVDD 虽然名目很多但通常都统一接 3.3V每颗引脚就近加 100nF 去耦电容。不要图省事只接一个总电容否则射频工作时噪声会通过电源串扰到模拟电路。2.2 STM32 与 MFRC522 的 SPI 接口连接MFRC522 支持 SPI、I2C、UART 三种通信接口默认上电后进入 SPI 模式前提是 SPI 引脚要拉到正确电平。我在原理图上保留了 MFRC522 的 EAI2C 使能引脚接 GND确保芯片工作在 SPI 模式。硬件 SPI1 的接线分配如下STM32F103C8T6 引脚MFRC522 引脚说明PA5SCKSPI 时钟PA6MISOMFRC522 输出数据PA7MOSISTM32 输出数据PA4SDACSN片选低电平有效PA3RST复位/使能高电平进入工作状态3.3VVCC电源GNDGND地这里最容易被误导的是模块上丝印写的是 SDA但 SPI 模式下它实际承担片选功能并不是 I2C 数据线。很多新手第一次接模块把 SDA 当 MOSI结果通信完全失败。建议看原理图时先确认 MFRC522 的 DEMOD、SDATA、SCK、NCS 这些信号名而不是只看模块丝印。IRQ 引脚在这个方案里我一开始悬空因为驱动代码用的是轮询方式不需要中断。 等后面做低功耗待机需要“卡靠近唤醒 MCU”时再用 IRQ它的好处是不用每隔几百毫秒就发一次应答命令MCU 可以一直睡觉卡片进入场区后由 IRQ 打断唤醒。2.3 天线匹配电路设计天线部分是整个原理图里最玄学的地方。MFRC522 内部有发射驱动电路通过 TX1、TX2 脚输出 13.56MHz 信号经过匹配网络后驱动天线线圈。天线线圈产生交变磁场卡片进入磁场后感应取电并反向调制信号MFRC522 再通过 RX 引脚接收卡片回传的数据。典型匹配网络包含两个串联电容和两个并联电容分别调整发射效率和接收灵敏度。 如果完全自己画天线建议先参考原厂天线设计文档里提到的线圈大小、圈数和阻抗要求。我之前第一次自己做板子为省空间把天线绕得又小又密结果读卡距离只有 1cm后来加大线圈尺寸、重新计算匹配电容才恢复到 5cm 左右。考虑到大部分人不会真的拿网络分析仪去测天线阻抗最可靠的方案是直接买一块成熟 MFRC522 模组的参考原理图把它的天线部分原样抄到自己的板上包括线圈走线宽度、间距、匹配电容容值都尽量保持一致。 这样能省掉大量调试时间。抄板不是丢人的事尤其是射频部分拿着诺基亚时代就验证过的电路抄一遍比自己凭感觉画靠谱得多。就我用的这块参考设计而言匹配电容分别是两个 22pF 和一个 33pF电感部分用一个 1uH 的贴片电感做馈电天线线圈为 PCB 走线总尺寸约 40mm x 25mm。 实测在 3.3V 供电下读卡距离 5cm 左右标签质量好的还能到 6cm。2.4 声光提示电路读卡成功要给人明确的反馈我加了 LED 和蜂鸣器。LED 电路很简单GPIO 配置为推挽输出引脚串联一个 330Ω 限流电阻接到 LED 阳极阴极接地。 GPIO 输出高电平 LED 亮输出低电平熄灭。蜂鸣器不能直接接 GPIO 驱动因为推挽输出最大只能提供大约 20mA 电流而有源蜂鸣器工作电流通常要 30mA 以上。 我用一颗 S8050 NPN 三极管做开关原理图连接方式GPIO 通过 1kΩ 电阻接三极管基极发射极接 GND集电极接蜂鸣器负极蜂鸣器正极接 3.3V。 当 GPIO 输出高电平时三极管导通蜂鸣器通电发声。蜂鸣器两端反向并联一个 1N4148 二极管用来吸收断电瞬间的反向电动势防止击穿三极管。注意如果蜂鸣器接的是 5V 供电那么 3.3V GPIO 输出的高电平也能通过三极管控制 5V 负载只要三极管基极串联电阻选合适就行。这也是我建议用三极管驱动而不用直接 GPIO 驱动的原因兼容性更强。3. 程序实现与核心代码流程3.1 开发环境和初始化配置程序开发我用 Keil MDK STM32CubeMX套路是先用 CubeMX 生成 SPI1、UART2、GPIO 的初始化代码再移植 MFRC522 驱动。 如果你习惯标准外设库逻辑也是一样的只是换 API 名称。CubeMX 里 SPI1 关键配置如下ModeFull-Duplex MasterData Size8 bitClock PolarityLowClock Phase1 EdgeNSSSoftwareBaud Rate Prescaler16此时 SPI 时钟为 72MHz / 16 4.5MHzMFRC522 的最高 SPI 时钟是 10MHz但为了稳定起见我用了 4.5MHz。 这个速度完全够用读一次卡号的数据量也就几十个字节4.5MHz 的通信时间可以忽略不计。如果把分频系数调太低总线速度太快杜邦线连接时反倒会出现丢位问题尤其飞线很长的时候。3.2 SPI 底层读写封装RFID 读卡模块的代码核心是 SPI 读写 MFRC522 的寄存器。MFRC522 的 SPI 地址格式比较特殊寄存器地址是 6 位通过 SPI 传输时地址要左移一位最低位表示读还是写为 0 表示写为 1 表示读。我封装的底层函数如下void RC522_WriteRegister(uint8_t reg, uint8_t value) { HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET); uint8_t addr (reg 1) 0x7E; HAL_SPI_Transmit(hspi1, addr, 1, 100); HAL_SPI_Transmit(hspi1, value, 1, 100); HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET); } uint8_t RC522_ReadRegister(uint8_t reg) { uint8_t send ((reg 1) 0x7E) | 0x80; uint8_t recv 0; HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, send, 1, 100); HAL_SPI_Receive(hspi1, recv, 1, 100); HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET); return recv; }这里有一个容易出错的地方读寄存器时发送完地址字节后MCU 需要再向 MFRC522 发一个空字节同时接收数据。 很多人的写法是直接调用 HAL_SPI_TransmitReceive 一次收发但要注意时序是否满足芯片要求我更喜欢拆成 Transmit 加 Receive逻辑更清晰调试时也容易定位问题。3.3 MFRC522 核心命令流程RFID 读卡操作并不是简单发一条命令就完事它有一套固定流程复位芯片并测试通信。写 AntennaOn 相关寄存器打开天线。发送 Request 请求命令0x52 或 0x26等待卡片进入场区。发送 Anticoll 防碰撞命令获取卡片 UID。发送 Select 选卡命令选中这张卡。根据业务需求做认证、读写扇区。其中请求命令有两条0x26 是 Request All0x52 是 Request Idle。 区别在于请求方式0x26 每次都能请求到进入场区的卡0x52 一般用在多卡防碰撞处理时。 实际判断“有没有新卡”时通常会交替使用这两条命令防止同一张卡被重复触发。防碰撞命令是读卡号的重头戏。M1 卡在场上有多张卡片同时存在时天线收到的信号会混叠芯片通过防碰撞机制逐一轮询最终得到一张卡完整的 4 字节 UID 以及 1 字节校验。 如果校验错误说明通信受到干扰需要丢弃本次结果重新请求。下面是一段精简的请求加防碰撞代码uint8_t RC522_Request(uint8_t req_mode, uint8_t *atqa) { // 把BitFraming设为7表示发送7位数据 RC522_WriteRegister(RC522_REG_BIT_FRAMING, 0x07); RC522_WriteRegister(RC522_REG_TX_MODE, 0x00); RC522_WriteRegister(RC522_REG_RX_MODE, 0x00); RC522_Command(RC522_CMD_TRANSCEIVE); RC522_WriteRegister(RC522_REG_FIFO_DATA, req_mode); // 启动收发并等待完成 RC522_SetBitMask(RC522_REG_COMMAND, 0x08); // 等待IRQ标志位 while ((RC522_ReadRegister(RC522_REG_IRQ0) 0x08) 0); // 读取ATQA响应 atqa[0] RC522_ReadRegister(RC522_REG_FIFO_DATA); atqa[1] RC522_ReadRegister(RC522_REG_FIFO_DATA); return MI_OK; }这里的 RC522_Command 函数用来往 CommandReg 写命令字比如 TRANSCEIVE收发命令0x0E。 启动收发后不需要死等太久可以用定时器做超时退出否则卡片离开场区时主循环会卡在 while 里影响其他外设运行。3.4 主循环应用逻辑读卡主循环我习惯放在 while(1) 里跑轮询每隔 150ms 主动向 MFRC522 发一次“有卡吗”的请求。卡片靠近时返回 ATQA然后继续防碰撞、选卡、认证。实际代码如下while (1) { uint8_t atqa[2] {0}; uint8_t uid[4] {0}; uint8_t uidCheck 0; uint8_t cardBuf[16] {0}; if (RC522_Request(0x52, atqa) MI_OK) { if (RC522_Anticoll(uid, uidCheck) MI_OK) { // 防碰撞成功打印UID char tmp[32] {0}; sprintf(tmp, UID: %02X %02X %02X %02X\r\n, uid[0], uid[1], uid[2], uid[3]); HAL_UART_Transmit(huart2, (uint8_t*)tmp, strlen(tmp), 100); // 蜂鸣器提示 HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET); HAL_Delay(100); HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET); // 这里可以继续做选卡、认证、读块操作 RC522_Select(uid); RC522_Auth(PICC_AUTHENT1A, 1, defaultKey, uid); RC522_Read(1, cardBuf); } } HAL_Delay(150); }这段代码中最关键的点是拿到 UID 后如果要读某个扇区的数据必须先执行选卡命令再执行密码认证。 如果跳过认证直接读块MFRC522 会返回错误状态这也是新手最容易踩的坑。M1 卡默认密码是 12 个 FF新卡出厂时一般全 FF但如果是用过的卡默认密码可能已经被改成别的值此时需要用已知密码才能认证通过。3.5 读卡数据如何上传到上位机调试阶段我用 UART2 将读到的 UID 发到电脑串口助手。 CubeMX 里把 UART2 配成 115200 波特率8 位数据位无校验1 位停止位主循环里用 HAL_UART_Transmit 发送字符串。产品阶段如果要做后台记录可以通过 UART 接 ESP8266 或 ESP32把 UID 以 JSON 格式发给服务器比如{device_id:tool_cabinet_01,card_uid:A1B2C3D4,action:borrow,timestamp:1698765432}这种方案改动小MCU 只是采集卡片数据网络协议全部交给 WiFi 模组处理逻辑清晰也方便后续迭代。4. 调试过程与常见问题实录4.1 SPI 通信失败寄存器读出来全是 0xFF这是最常遇到的问题。 拿到板子后我写了测试代码连续读取 MFRC522 的版本寄存器VersionReg地址 0x37正常应该返回 0x92 或 0x91但实际读回来全是 0xFF。排查步骤检查 MFRC522 供电是否正常用万用表量 VCC 和 GND 之间电压必须稳定在 3.3V。检查 SPI 接线有没有接反特别是 MISO 和 MOSI 方向。检查片选信号。SPI 模式下NCS 拉低后才能访问寄存器必须确认 GPIO 初始化正确。确认 MFRC522 通信模式。如果 EA 引脚被拉到高电平芯片会进入 I2C 模式此时 SPI 肯定读不出数据。最后用示波器抓 SCK 和 MOSI 波形看 MCU 到底有没有输出时钟。我遇到过一个很隐蔽的问题CubeMX 初始化 GPIO 时把 PA4 复用成了 SPI1_NSS但 MFRC522 的片选又接到 PA4导致 HAL 库自动控制片选和我代码里手动拉低拉高打架。 处理方法是把 PA4 配置为普通推挽输出CuMX 里 NSS 选 Software不要启用硬件 NSS。4.2 读卡距离特别短只有 1cm 左右天线匹配不好是主要原因。 我第一版板子读完卡距离极短后来排查发现是天线匹配电容选得不对。MFRC522 的发射电路需要一个合适的谐振回路才能把功率最大化传递到天线电容容值偏差偏高或者偏低都会让谐振频率偏移。如果自己画天线建议先用原厂推荐参数并且在打样后测量以下两个指标用示波器夹在 TX1 和 TX2 之间观察 13.56MHz 正弦波幅度是否饱满是否存在明显失真用电流探头或万用表测 3.3V 电源电流卡片靠近天线时电流应明显增大如果电流没有变化说明天线没产生谐振场。天线线圈的布线也有讲究尽量走顶层或底层完整平面不要跨分割区线圈面积要足够。 我后来改版时把天线加宽到 2mm 线宽、3 圈匹配电容换成 22pF读卡距离立刻从 1cm 拉回到 4~5cm。4.3 卡片可以读到 UID但认证失败这个现象非常典型请求和防碰撞都成功说明射频通信链路正常认证失败说明密码不对或者块地址不对。排查方向确认认证的块地址是否越界。M1 卡有 64 个块每张卡分 16 个扇区每个扇区 4 个块认证时要用扇区的 trailer 块地址。确认密钥类型选的是 KeyA 还是 KeyB有些卡只允许某一种密钥访问。确认 UID 数组是否拼错。认证命令需要把 4 字节 UID 传给 MFRC522如果 UID 有误认证自然失败。还有一个容易忽略的地方M1 卡的块 0 是厂商数据区包含 UID但默认状态下即使认证成功也不允许修改。 想测试写入功能不要拿块 0 做实验要用扇区 1 或扇区 2 的数据块。4.4 快速连续刷卡时偶发丢卡轮询模式下如果主循环里有阻塞操作比如 HAL_Delay 时间太长、蜂鸣器响 500ms就会导致 MCU 在卡片完全进入场区但没有及时发送防碰撞命令卡片已经离开识别区于是漏读。解决办法缩短阻塞时间蜂鸣器响 50~100ms 即可。把读卡逻辑放进中断驱动模式用 IRQ 信号触发。如果不想用中断就把轮询周期缩短最小可做到 50ms。同时注意M1 卡在同一个场区停留时间极短尤其有人快速挥卡时实际有效识别窗口可能只有几十毫秒轮询间隔必须足够短。4.5 系统上电后偶尔死机看门狗复位这个问题通常和电源跌落有关。 卡片靠近天线瞬间MFRC522 发射功率会突然增加电流脉冲从 30mA 跳到 70mA 甚至更高如果 MCU 的 3.3V 电源被拉低就会触发掉电复位。解决方法电源输入端并联大容量电解电容我加了 100uF 和 10uF 各一个。MFRC522 的 TVDD 和 PVDD 引脚各自用磁珠隔离减少射频干扰反灌到 MCU。降低天线发射功率也就是调整 TxControl 寄存器的 CW 位虽然读卡距离会变小但系统稳定性优先。5. 后续扩展与个人建议这个读卡模块还有一种更省心的做法直接买市面上的 TTL 串口 RFID 读头模块内部已经做好射频天线和协议解析MCU 只需要通过串口读数据。 但这种方案有几个局限串口读头的 UID 输出格式往往是固定的没法灵活改价格比 MFRC522 裸芯片方案高对一些需要直接操作卡内扇区数据的项目串口读头的响应速度和控制颗粒度也不够。从学习角度我还是强烈建议自己把 MFRC522 的方案完整做一遍。 因为在这一套流程里你不仅会接触 SPI 通信协议、寄存器配置、射频匹配、M1 卡存储结构还会接触到 UART 调试、电源完整性设计、中断优化这些嵌入式通用技能。 这些东西不是只在 RFID 项目里有用换到其他传感器、通信模组时照样派得上用场。我实际测试中发现用这块板子的 UART 输出接到上位机软件不仅能看到 UID还能把读写扇区的返回值一起打出来对排查问题非常有帮助。 后期我还做了 OLED 显示卡片余额、ESP8266 上传刷卡记录、摄像头抓拍联动门锁等功能都是在同一个主控上扩展外设完成并不需要重新画板。最后分享一个小细节打样的时候不要把天线线圈紧挨着 STM32 的晶振13.56MHz 的射频能量可能会干扰晶振起振。 我第一次改版把天线挪到了板子边缘和 8MHz 晶振拉开距离之后系统稳定性好了很多复位不再频繁。设计阶段多留一点布局裕量后面调试能省一半力气。
返回列表