
简介STM32F103ZET6单片机RFID-RC522门禁控制系统资源特别适合嵌入式初学者、电子设计爱好者以及准备物联网课程设计或毕业设计的开发者可据此从零搭建一套基于13.56MHz射频识别的非接触式门禁控制方案也能用于实验室门禁、智能柜管理等真实场景。资源压缩包约34.4MB以函数库源码与开发板原理图文件为主要内容体积小巧、便于下载研读目前已有410人学习关注。源码部分详尽覆盖GPIO、时钟与SPI接口的初始化MFRC522驱动读写、防冲突处理、卡号数据解析以及根据识别结果执行门禁开闭的逻辑同时包含读卡失败、通信异常时的错误处理能帮助开发者理解数据从射频卡到单片机再到执行设备的完整流转过程有效训练底层驱动与上层业务整合能力。原理图则清楚画出STM32F103ZET6与RC522模块、LED指示灯、按键、蜂鸣器等外围设备的连接关系为硬件搭建、信号排查与后期功能扩展提供直接依据软件与硬件结合的学习路径既能提升嵌入式C语言与调试技能也能积累物联网安防产品的实际工程经验为后续更复杂的射频识别项目打下扎实基础。1. 为什么这门禁要先读卡再门锁RC522 在 STM32F103ZET6 上的核心问题门禁系统是嵌入式开发里最容易做成“能跑就行”的项目刷卡显示卡号继电器咔哒一声完事。但等你真去调电路、看波形、核对时序时才会发现RC522 读不到卡不是模块坏了而是 SPI 速率、天线匹配或防碰撞流程没对上等你把卡读出来了又会发现直接比卡号根本不够用MIFARE 卡的密钥机制、白名单存储、掉电重试这些问题才是把项目从演示变为“能用”的关键。这个标题里的关键词拆开看其实是三件事STM32F103ZET6 这颗 64 引脚、512KB Flash 的大容量 MCU 提供了什么资源RFID-RC522 这套 13.56MHz 读卡方案走的是哪种协议流程“函数库版”又意味着代码基于 ST 标准外设库而不是 HAL适合老工程迁移、考级毕设和裸机教学。适合谁想做门禁、考勤、储物柜、实验室设备管理的人尤其适合手上有 F103ZET6 开发板、想把 RC522 真正驱动起来而不是停留在点灯层面的人。2. RC522 读取门禁卡SPI 时序与 MIFARE 寻卡流程拆解2.1 RC522 与 STM32F103ZET6 之间的总线选型RC522 支持 SPI、I2C、UART 三种接口但在 STM32F103ZET6 上做门禁我通常直接选 SPI原因有三个RC522 的 SPI 从模式支持 10Mbit/s 时钟读卡时整体占用很短F103ZET6 的 SPI1 挂在 APB2 总线上时钟可达 36MHz分频后轻松覆盖 RC522 需求SPI 线少SCK、MOSI、MISO、NSS在开发板原理图上排线方便。I2C 模式地址冲突多UART 模式要额外配波特率都没有 SPI 直白。接线时注意 RC522 模块和开发板原理图上的引脚标号并不总是同名常见的映射是RC522 引脚STM32F103ZET6说明SDANSSPA4SPI1_NSS软件控制片选SCKPA5SPI1_SCKMOSIPA7SPI1_MOSI连 RC522 的 SIMISOPA6SPI1_MISO连 RC522 的 SOIRQ可不接轮询时悬空中断时接 PA3RSTPA8或 PB0复位引脚用 GPIO 控制3.3V / GND3.3V / GND必须 3.3V不能接 5V 引脚提示RC522 模块上自带的电平转换电路输出电压是 3.3V但部分模块的 MISO 引脚是 5V 容忍的。稳妥做法是全部按 3.3V 处理避免长期运行损伤 STM32F103ZET6 的 PA6。2.2 MIFARE 卡交互的五个标准步骤RC522 读卡不是直接“读一下 UID”就结束它内部要跑一组命令序列。这张卡是 NXP 的 MIFARE Classic 1K 或 S50 兼容卡命令流程稳定如下寻卡 Request发送PCD_REQA0x26或PCD_WUPA0x52得到 2 字节卡类型如 0x4400 表示 Mifare 1K。防碰撞 Anticollision发送PCD_ANTICOLL0x93返回 4 字节 UID 和 1 字节校验。选卡 Select发送PCD_SELECT0x93 后接 UID拿到卡容量和确认字节。三次认证发送PCD_AUTHENT需要扇区号和密钥默认是 6 字节 0xFF。读块或写块PCD_READ0x30或PCD_WRITE0xA0此时才能访问数据块。RC522 的命令寄存器是 0x01CommandReg写命令后再查询 0x02ComIEnReg 的中断标志或 0x05Status2Reg 的 MFCrypto1On来判断执行结果。初学者最容易跳过第 3 步直接读块结果返回 MI_NOTAGERR。这是 MIFARE 的固有设计——选卡之后通信链路才锁定到这张卡上。2.3 RC522 内部寄存器的读写时序函数库版的实现核心是两个底层函数写寄存器RC522_WriteReg和读寄存器RC522_ReadReg。SPI 通信时每条数据都由一个地址字节和一个数据字节组成地址字节的最高位表示方向0 为写、1 为读。写寄存器时把片选拉低发送地址字节和数据字节释放片选读寄存器时先发送地址字节再发送一个虚拟字节作为时钟驱动同时从 MISO 采样返回数据。// RC522 底层寄存器读写SPI 模式适用于标准外设库 uint8_t RC522_ReadReg(uint8_t addr) { uint8_t value; GPIO_ResetBits(RC522_NSS_PORT, RC522_NSS_PIN); // 片选拉低进入通信 SPI_SendByte(RC522_SPI, ((addr 1) 0x7E) | 0x80); // 读标志位最高位置 1 value SPI_SendByte(RC522_SPI, 0x00); // 发送空字节同时接收数据 GPIO_SetBits(RC522_NSS_PORT, RC522_NSS_PIN); // 片选拉高通信结束 return value; } void RC522_WriteReg(uint8_t addr, uint8_t data) { GPIO_ResetBits(RC522_NSS_PORT, RC522_NSS_PIN); SPI_SendByte(RC522_SPI, (addr 1) 0x7E); // 写方向最高位为 0 SPI_SendByte(RC522_SPI, data); GPIO_SetBits(RC522_NSS_PORT, RC522_NSS_PIN); }地址为什么左移一位因为 RC522 的 SPI 帧格式里地址占 6 位方向位在第 7 位后面还保留了一位所以传入的寄存器地址如 CommandReg 是 0x01要先左移一位再拼方向位。这里的SPI_SendByte是一次完整的 SPI 收发函数需要保证 F103 的 SPI1 已配置为主模式否则 MISO 上采不到数。验证这两段代码是否正确可以读 RC522 的 VersionReg0x37正常返回 0x92 或 0x12返回其它值说明接线或 SPI 配置有错。3. 函数库版工程骨架GPIO 初始化与 SPI 参数怎么定3.1 STM32F103ZET6 的 RCC、GPIO、SPI 初始化函数库版指的是 ST 官方标准外设库SPL结构上是RCC_Configuration、GPIO_Configuration、SPI_Configuration三段式没有 HAL 的MX_前缀也没有 CubeMX 自动生成。对系统时钟初始化外部晶振用 8MHzPLL 倍频到 72MHzAPB1 总线 36MHzAPB2 总线 72MHz。SPI1 挂 APB2所以 SPI 的时钟源就是 72MHz分频系数最低是 2对应 36MHz——这个值远高于 RC522 的 10MHz 上限需要继续分频。void SPI1_Configuration(void) { SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; // 双线全双工 SPI_InitStructure.SPI_Mode SPI_Mode_Master; // RC522 是从机 SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; // 空闲时钟为低 SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; // 第一个边沿采样 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件管理片选 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 72MHz / 8 9MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }3.2 三个容易让 RC522 沉默的参数细节波特率预分频为什么选 8 而不是 2 或 4RC522 规格书标称 SPI 时钟最高 10MHz但天线电路和布线质量会压缩实际裕量9MHz 比 5MHz 更接近临界值时序余量小PCB 布局差一点就可能出现“第一张卡能读、换张卡就卡死”的怪问题。我在正式项目里会用 8 分频9MHz在面包板飞线上用 16 分频4.5MHz稳定性优先。CPOL 和 CPHA 的组合也很讲究。RC522 要求 SPI 空闲时钟为低、第一个时钟沿采样数据对应 CPOLLow、CPHA1Edge也就是 SPI_MODE0。如果你是从别的工程复制代码先检查这两行模式不配对时 RC522 的 VersionReg 一样能读出来但收发数据会错位命令响应永远是 0xFF 或超时。GPIO 初始化里 SCK、MOSI、NSS 要配置为复用推挽输出GPIO_Mode_AF_PPMISO 配为上拉输入GPIO_Mode_IPU。NSS 特别提一句虽然SPI_NSS_Soft使能后 F103 不再自动控制硬件 NSS但 PA4 如果被其它外设复用片选信号会串扰所以要用 GPIO 单独控制 PA4 的输出电平初始化时先置高。3.3 最小工程验证读版本号搭好工程后第一件事不是去读卡而是读 RC522 的版本寄存器。把前面两个函数组合起来在主函数里连续读三次 0x37 地址串口打印输出。这一步能同时验证 SPI 时序、GPIO 映射、电源三样东西比直接跑到寻卡那一步省事得多。// 从 RC522 读取版本号用于验证 SPI 通信是否打通 uint8_t version 0; RC522_WriteReg(CommandReg, PCD_RESETPHASE); // 先复位 RC522 内部状态机 version RC522_ReadReg(VersionReg); // 0x37 地址 printf(RC522 version: 0x%02x\r\n, version); // 正常为 0x92 或 0x12RESETPHASE 命令的作用是把 RC522 内部寄存器恢复到上电默认值这不是可省的步骤——模块上电后内部状态不确定直接发寻卡命令可能卡在某个错误状态。版本号能读出来后再初始化天线在 TxControlReg 里使能模拟前端然后进入寻卡循环。如果版本号读出来是 0x00优先检查 MOSI/MISO 是否接反这是所有 SPI 设备调试里最频繁的接线事故。4. 从读到放行白名单校验与继电器动作的完整链路4.1 设计一个能判断“放行还是拒绝”的状态机门禁逻辑的层次要分清RC522 驱动层负责拿到 UID应用层负责判断这个 UID 是否有权限。如果在驱动层直接做判断后面想扩展密码模式、刷卡加密码双重验证就会很别扭。我一般把整个刷卡到开门拆成五个状态空闲等待刷卡、寻卡成功、防碰撞取 UID、认证扇区读取数据、比对后输出动作。// 门禁主状态机刷卡 → 取 UID → 查白名单 → 动作输出 uint8_t card_uid[4]; uint8_t status; while (1) { status RC522_CheckCard(card_uid); // 寻卡 防碰撞 选卡一步完成 if (status MI_OK) { // 在 M1 卡的 0 扇区 0 块前 4 字节就是 UID // 第 5 字节是卡号校验位第 6~16 字节是厂商数据 // 比对时只取前 4 字节不同品牌的卡 UID 长度有 4 字节和 7 字节两种 if (WhiteList_Check(card_uid) 1) { Relay_Open(); // 打开电磁锁或继电器持续 2 秒 OLED_ShowUID(card_uid); // 屏显放行状态 } else { Buzzer_Warn(); // 蜂鸣器响 3 声提示无权限 } delay_ms(500); // 防止同一张卡重复触发 } }RC522_CheckCard内部把协议那一节讲的五步都走完了。注意一个细节delay_ms(500)这个延时防的是“卡贴在读卡器上不拿走状态机反复读到同一张卡导致继电器频繁抖动”不是防碰撞去重的替代品。如果你还想做得更严谨可以记录上一次放行的 UID在它持续紧贴期间不重复执行继电器动作等卡离开后再复位比较位。没有电磁锁的工程通常会省略这个逻辑继电器会非常吵。4.2 白名单存哪直接比对 UID 还是读 M1 卡扇区数据门禁系统的常见做法有两种。第一种直接把卡号写死在代码数组里适合几张卡、结构简单的场景第二种把持卡人信息写入 M1 卡扇区系统只验证扇区密钥再用读到的数据做二次确认。我会优先推第二种——M1 卡的 UID 虽然出厂唯一但用 PM3 等设备可以复制扇区数据破解门槛不高。增加一层扇区认证哪怕密钥仍然使用默认的 6 字节 0xFF至少把“只认卡号”这种安全性极低的方案挡在门外。M1 卡的扇区结构一共 16 个扇区每个扇区 4 个块每块 16 字节。每个扇区的第 3 块是密钥块存储 6 字节密钥 A 和 6 字节密钥 B以及访问位。门禁里常用的位置是第 1 扇区、第 0 块前 4 字节存用户编号中间存权限等级剩下作为备用。块内偏移用途示例0~3门禁号或用户 ID{0x01, 0x00, 0x00, 0x00}4~5权限等级1 表示可通行6~9有效期时间戳0表示永不过期10~15预留0xFF认证扇区时用PCD_AUTHENT需要知道密钥类型KeyA 或 KeyB和扇区号对应的块地址。扇区 1 的块地址是 4这个换算常有人写错扇区序号乘 4 就是该扇区第一个块的地址。读块要用PCD_READ命令前面提到的第 4 步认证没有通过时读出来的数据全部是 0。4.3 继电器与电磁锁的驱动边界RC522 是 3.3V 逻辑继电器线圈是 12V 或 5V中间必须有三极管或 ULN2003 做电平转换。这一步不接线问题不大但如果直接在 PA8 上挂继电器STM32F103ZET6 的 GPIO 驱动能力只有 8mA 左右根本吸合不了继电器还可能把引脚烧掉。标准做法是 GPIO→三极管基极限流电阻→继电器线圈→续流二极管→电源地。函数库版里控制继电器的代码就是两个 GPIO 写电平的调用// 继电器开/关参数 t 为吸合时间单位毫秒 void Relay_Open(uint16_t t) { GPIO_SetBits(GPIOB, GPIO_Pin_5); // 三极管基极高电平继电器吸合 delay_ms(t); // 电磁锁吸合时间 2000ms 左右 GPIO_ResetBits(GPIOB, GPIO_Pin_5); // 释放 }继电器开多长时间要看门装的是什么。电插锁通常需要 1~2 秒的断电时间让人拉门电磁锁则要保持通电断电才开门逻辑恰好相反——这时候 GPIO 的控制电平要反向还要加一个互锁避免误判。开发板原理图上一般会画一个 LED 指示继电器状态调试时看这个 LED 比听声音直观。5. 设计门禁系统的 3 个进阶细节掉电保存、看门狗与仿真排错5.1 用 STM32F103ZET6 内部 Flash 保存卡号白名单把白名单数组直接写在程序里每次改卡号都要重新下载固件这在交付场景里不可接受。用 F103ZET6 的内部 Flash 存放白名单更实际——它有 512KB 主 Flash页大小 2KB写一个扇区足够几百个卡号。函数库版里 Flash 操作的入口是FLASH_ProgramWord和FLASH_ErasePage写入前要解锁并清标志位// 从内部 Flash 读一张卡的白名单addr 为 Flash 中的地址偏移 uint8_t WhiteList_Load(uint32_t addr, uint8_t *uid_buf) { uint32_t *p (uint32_t *)addr; uid_buf[0] (uint8_t)(p[0] 0xFF); uid_buf[1] (uint8_t)((p[0] 8) 0xFF); uid_buf[2] (uint8_t)((p[0] 16) 0xFF); uid_buf[3] (uint8_t)((p[0] 24) 0xFF); return 1; }注意几个点。Flash 写操作会阻塞 CPU写之前必须关中断否则程序会跑飞写入一个 32 位数据的时间大约是几十微秒完全可以接受。另外 Flash 擦写次数有限虽然 F103 的 Flash 寿命通常在 1 万次以上但频繁用按键“添加卡号”的场合最好先判断卡号是否已存在避免每次都触发擦写。5.2 死循环与看门狗RC522 通信卡死后的自救RC522 的 SPI 通信偶尔会进入死状态——射频干扰或天线距离变化导致命令无响应程序如果卡在等待中断响应的死等循环里整个门禁就瘫了。在函数库版工程里加一个独立看门狗IWDG是成本最低的解决方案每 500 毫秒喂狗一次如果 RC522 卡死导致主循环停止IWDG 自动复位 MCU模块恢复初始状态。注意喂狗要在主循环末尾而且 RC522 的重试时间要小于喂狗周期。// 初始化独立看门狗超时时间约 2 秒 IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_128); // 40kHz / 128 312.5Hz IWDG_SetReload(625); // 2 秒超时 IWDG_Enable(); // 主循环中 IWDG_ReloadCounter();5.3 Proteus 仿真排错函数库版工程能不能先不烧板子如果你手里的 F103ZET6 开发板还没到或者引脚接线图找不到了可以先在 Proteus 里搭一个仿真工程。Proteus 8 及更新版本可以直接搜到 STM32F103ZET6 芯片不需要额外装第三方库——选择“Pick Device”后输入“STM32F103ZET6”就能看到前提是当前用的 Proteus 版本组件库较新。RC522 模块在 Proteus 里有现成模型但需要注意很多仿真模型不支持真实的射频时序S50 卡的仿真数据和实体卡读出来的 UID 不完全一致验证代码跑通后还是要回到开发板原理图核对实物接线。Keypad 输入法、串口助手轮询读卡、OLED 屏显这三样是门禁调试时最常用的“眼睛”。仿真能帮你确立状态机逻辑实物才能验证 RC522 的射频时序和继电器负载能力。无论是毕设答辩还是实际交付板子上的天线走线、模块位置和铁质门框的距离这些原理图上看不出来的物理因素才是门禁可靠性真正的分水岭。本文还有配套的精品资源点击获取