
简介基于STM32的智能门禁系统完整工程包集源码、设计文档与演示视频于一体面向嵌入式学习者、电子竞赛团队及智能安防开发者既可作课程设计参考也能帮助快速搭建门禁原型。包内共239个文件压缩包约149MB以C源码.c/.h、Keil工程文件、编译中间文件为主另含PDF设计文档、MP4演示视频与MD说明方便对照项目文档学习。目前已有394人学习下载。开发源码覆盖RFID读卡、指纹识别、OLED界面显示、舵机与电磁锁控制等门禁核心流程设计文档提供需求分析和系统架构说明演示视频完整展示系统运行效果工程内还包含编译链接生成的hex与map文件便于直接烧录验证。整体按硬件驱动、通信协议、主控逻辑分层组织可用Keil直接编译调试是理解STM32外设驱动、状态机设计和物联网安全认证的实用素材。1. 基于 STM32 的智能门禁系统拿到源码包先别编译先对硬件很多人拿到基于 STM32 的智能门禁系统源码包习惯先找 README再打开 Keil 编译结果要么编译报错要么烧录后屏幕不亮、刷卡没反应。换我拿到这类 stm32 项目顺序会反过来先确认芯片型号、晶振、引脚映射和外设初始化再谈编译。门禁系统反直觉的地方在于刷卡、密码、显示这些功能单独都不难难的是掉电后白名单还在不在、继电器上电瞬间会不会误动作、连续输错密码能否恢复、延时函数卡死时系统会不会挂住。这些边界状态正是“源码设计文档演示视频”这个组合里最该被复现的部分也是新手和五年经验工程师拉开差距的地方。本文不搬运包内文件只讲拿到这类基于 STM32 的毕业设计项目后我会怎么从硬件对到源码、从文档对到视频。2. STM32 门禁系统的硬件架构与外设选型先看接口再看锁一份智能门禁系统源码必然绑定一套具体硬件。读卡模块接在主控哪个接口、锁控引脚在哪个 GPIO、蜂鸣器是有源还是无源直接决定代码第一眼能不能读懂。我会先画一张“模块-接口-中断路径”清单再翻工程里的MX_*_Init函数效率比逐行读 main.c 高得多。2.1 读卡模块接 STM32 的四种方式与选型对照接口典型模块信号线软件重点适用场景SPIRC522MOSI / MISO / SCK / CS帧命令、防碰撞低成本室内刷卡I2CPN532I2C 模式SDA / SCL地址配置、应答超时与 OLED 共用总线UARTPN532、一体化读头TX / RX帧协议解析与 K210 等 AI 摄像头对接韦根HID / EM 一体读头DAT0 / DAT1外部中断计时工程门禁、抗干扰强源码里如果出现MFRC522风格函数或者HAL_SPI_Transmit说明读卡模块走了 SPI如果中断服务函数里在对两路引脚电平做持续计时那大概率是韦根 26 位格式。韦根数据由 1 位厂商码、8 位卡号、8 位用户码和 1 位偶校验组成外围电路只需两根数据线抗干扰能力强。我一般推荐 SPI 方案协议简单、串口日志直观适合在源码基础上去改协议工程门禁则选韦根。RC522 供电是 3.3VSPI 时钟频率设在 12 MHz 比较稳超过 4 MHz 后读卡距离会明显缩短。韦根输入建议在 DAT0/DAT1 上各接一个 10kΩ 上拉电阻到 3.3V同时把 STM32 对应 GPIO 也配置为内部上拉避免信号线浮空时触发误中断。2.2 锁控输出电路3.3V 单片机控制 12V 电插锁电插锁和电磁锁通常工作在 12V、电流约 1A没法直接接 3.3V GPIO。常见做法是“GPIO → 光耦隔离 → 三极管 → 继电器 → 锁”把逻辑地和锁电源隔开。位号参数作用R11kΩ光耦输入限流输入电流约 2mAU1PC817隔离 MCU 与锁电源R210kΩ基极下拉防止上电误触发Q1SS8050 / S8050NPN 三极管驱动继电器线圈K15V/12V 线圈继电器触点容量 5A / 30VDCD11N4007续流二极管跨接线圈两端对应的 GPIO 初始化要放在所有外设最前面并先输出低电平void lock_ctrl_init(void) { __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef it; it.Pin GPIO_PIN_0; // LOCK_CTRL锁控输出 it.Mode GPIO_MODE_OUTPUT_PP; it.Pull GPIO_NOPULL; // 硬件已有 R2 下拉 it.Speed GPIO_SPEED_FREQ_LOW; // 低速即可继电器不需要高频 HAL_GPIO_Init(GPIOB, it); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); }这段代码的关键是先拉低再完成配置。GPIO 默认是浮空输入如果硬件上没有 R2上电瞬间引脚电平不确定继电器可能误吸合一次。若板子上没接 R2it.Pull就配置成GPIO_PULLDOWNF1 系列支持内部下拉部分新系列没有需要以对应参考手册为准。锁控引脚输出高电平时继电器吸合开锁后软件要控制持续时间。对脉冲型电控锁我习惯把高电平时间做成宏LOCK_HOLD_TIME_MS源码头文件里一般能看到类似定义对电插锁这种持续供电型则要配合门磁判断关门后立刻断电。提示如果锁控引脚和板载 LED 复用在同一个 GPIO先确认 LED 是低电平点亮还是高电平点亮很多现成开发板恰好反相直接驱动继电器会开合逻辑颠倒。2.3 电源、蜂鸣器、门磁与 4x4 键盘的接线要求电源建议用 DC 12V / 1A 输入一路直供电插锁另一路降压到 5V 或 3.3V 给 STM32、读卡器和显示屏。AMS1117-3.3 线性稳压适合整板电流小于 500mA 的场景接了 RC522、OLED、蜂鸣器之后余量不大带 WiFi 模块的话我会换成 DC-DC比如 MP1584 或 TPS5430。蜂鸣器分两种先看源码再定硬件类型驱动方式源码特征有源蜂鸣器GPIO 输出高/低电平HAL_GPIO_WritePin无源蜂鸣器需要 PWM 方波__HAL_TIM_SET_COMPARE有源蜂鸣器内部有振荡电路给高电平就响无源蜂鸣器需要定时器输出一定频率的方波。看到htim句柄和 PWM 配置就知道是后者。门磁一般用常闭型干簧管接 GPIO 输入并启用内部上拉。门关上时引脚读到高电平门打开变成低电平。代码里只在开锁状态读它避免关门瞬间的抖动被当成有效电平。4x4 矩阵键盘占 8 个 GPIO行输出列输入扫描代码放在状态机里按节拍跑。还有一个容易忽略的点锁电源的大电流不要经过键盘和门磁的地线。整板共地要在电源输入处单点汇合否则锁继电器吸合瞬间的地电位跳动会让按键扫描误判。2.4 拿源码反推硬件映射的三条线索第一条线索是MX_GPIO_Init()。里面每个GPIO_InitStruct.Pin就是一张接线表把所有引脚和注释列出来基本能还原出显示屏、按键、门磁分别在哪。第二条线索是外设句柄。hspi1、huart1、htim2这些句柄和MX_xxx_Init成对出现标出哪个外设管读卡、哪个管蜂鸣器中断服务函数里出现的外设名会更明确。第三条线索是printf重定向。工程如果实现了fputc或自定义的HAL_UART_Transmit封装串口日志就走这个串口没实现的话调试信息只能靠 OLED 或 LED。检查顺序通常是芯片型号 → 时钟树 → 引脚映射 → 外设句柄 → 编译。老源码经常按某一块开发板写死换板子后第一类报错出现在system_stm32f1xx.c的时钟配置第二类是 GPIO 复用冲突。先解决这两类问题再往下谈业务逻辑。3. 智能门禁核心逻辑状态机、白名单与按键的源码实现门禁系统的难点不在单个外设驱动而在读卡、按键、超时三个事件会以任意顺序到来。我不会用一整串if去处理而是建一个状态机让每个事件只做固定迁移。3.1 四个核心状态与状态转移表当前状态事件动作下一状态待机收到合法卡置开锁标志开锁中待机收到非法卡蜂鸣 200ms失败次数加 1待机待机密码连续错误 3 次进入 30 秒报警期间忽略一切输入报警开锁中门磁已关断开锁控复位开锁标志待机开锁中超时 5 秒断开锁控待机报警30 秒倒计时结束失败计数清零待机typedef enum { DOOR_IDLE 0, DOOR_LOCK_OPEN, DOOR_ALARM } door_state_t; door_state_t g_state DOOR_IDLE; uint8_t g_fail_count 0; uint32_t g_state_start_tick 0;开锁中状态必须同时监听“门磁已关”和“超时”两个条件谁先触发谁生效。这样更贴近真实场景人推门进去门磁闭合锁立刻断电不需要空等 5 秒门一直开着超时到了就强制断锁。做状态机时要注意中断里不要调用printf和HAL_Delay实测中常见的 stm32 延时函数卡死问题大多是中断里执行了阻塞型调用导致主循环喂狗被拖住。3.2 白名单的掉电保存用内部 Flash 存 UID 列表白名单如果放在全局数组里掉电即丢。通用做法是存进 STM32 内部 Flash省掉外部 EEPROM。以 F103 为例Flash 最小擦除单位是 1KB 页程序占用之后通常在末尾留一页给数据。示例地址0x0800F000实际以工程的链接脚本为准编译后确认这一页没有被代码占用。#define FLASH_SAVE_ADDR 0x0800F000u #define UID_LEN_MAX 32u uint8_t g_uid_list[UID_LEN_MAX][4]; uint16_t g_uid_count; void flash_load_uid_list(void) { g_uid_count *(volatile uint16_t *)FLASH_SAVE_ADDR; if (g_uid_count UID_LEN_MAX) { g_uid_count 0; // 首次上电或数据损坏 return; } memcpy(g_uid_list, (void *)(FLASH_SAVE_ADDR 2), g_uid_count * 4); } int flash_add_uid(uint8_t *uid) { FLASH_EraseInitTypeDef erase; uint32_t page_err 0; HAL_FLASH_Unlock(); erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress FLASH_SAVE_ADDR; erase.NbPages 1; if (HAL_FLASHEx_Erase(erase, page_err) ! HAL_OK) { HAL_FLASH_Lock(); return -1; } memcpy(g_uid_list[g_uid_count], uid, 4); g_uid_count; HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, FLASH_SAVE_ADDR, g_uid_count); for (int i 0; i g_uid_count; i) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_SAVE_ADDR 2 i * 4, *(uint32_t *)g_uid_list[i][0]); } HAL_FLASH_Lock(); return 0; }这段代码的要点有三个。第一先写数量再写卡号读出时优先校验数量数量异常就按空表处理避免拿到脏数据。第二擦除是整页操作写入过程中掉电会丢整页量产的方案要加双备份区先写备份再搬运。第三FLASH_TYPEPROGRAM_HALFWORD和FLASH_TYPEPROGRAM_WORD要按芯片选择F1 系列支持 16 位和 32 位编程F4 系列则常用 32 位和 64 位。如果源码里用的是AT24C02那就是 I2C 外挂 EEPROM 方案。它的读写代码更简单但多占一组 I2C 总线适合板子上已经有 EEPROM 的设计。3.3 4x4 键盘扫描与按键消抖矩阵键盘的扫描原理是逐行拉低、读列电平。下面这段代码把行输出、列输入和消抖放在一起#define K_ROWS 4 #define K_COLS 4 static const uint8_t keymap[K_ROWS][K_COLS] { {1,2,3,A}, {4,5,6,B}, {7,8,9,C}, {*,0,#,D}, }; uint8_t key_scan(void) { for (uint8_t r 0; r K_ROWS; r) { CLEAR_ROW(r); // 拉低当前行 delay_us(20); // 等电平稳定 for (uint8_t c 0; c K_COLS; c) { if (IS_COL_LOW(c)) { delay_ms(10); // 消抖 if (IS_COL_LOW(c)) { SET_ROW(r); // 恢复当前行 return keymap[r][c]; } } } SET_ROW(r); } return 0; }消抖延时 10ms 足够挡住接触抖动。行扫描之间的delay_us(20)不能改成HAL_Delay(1)否则扫完 16 个键要 16ms手速快时丢键。按键判定之后不要马上处理逻辑而是把键值放到环形缓冲区由状态机在下一个节拍消费这样长按和短按都能统一处理。例如密码输入场景#表示确认*表示删除前一位长按*1 秒进入管理员模式。长按检测要用HAL_GetTick()记录按下时刻不能把消抖循环里卡住 1 秒否则整个键盘扫描都会被阻塞。3.4 超时关门、独立看门狗与延时卡死的排查关门检测和超时控制需要独立的时间基准。我一般用 TIM2 产生 1 秒节拍定时器中断里只置标志位主循环看到标志后递增计数再检查超时条件。这比在状态机里调用HAL_Delay可靠得多。看门狗建议用独立看门狗 IWDG它走 LSI 时钟主程序跑飞也能复位void mx_iwdg_init(void) { // 使能 LSI再操作 IWDG 寄存器 RCC-CSR | RCC_CSR_LSION; IWDG-KR 0x5555; // 解除写保护 IWDG-PR IWDG_PRESCALER_256; // 40kHz / 256 156.25Hz IWDG-RLR 468; // 468 * 6.4ms 约 3 秒 IWDG-KR 0xCCCC; // 启动看门狗 } void main_loop(void) { while (1) { if (g_1s_flag) { g_1s_flag 0; key_tick(); } state_machine(); __HAL_IWDG_RELOAD_COUNTER(); // 喂狗放在循环末尾 } }喂狗语句放在主循环末尾一旦中间某段代码卡死3 秒后系统自动复位。复位的瞬间门禁回到待机状态不会保持锁死。调试时要注意 IWDG 会在断点停下时触发复位所以先用宏把看门狗关掉功能验证完再打开。“延时函数卡死”是 stm32 门禁项目里最高频的问题之一。典型现场串口中断里执行printf阻塞等待发送完成此时 SysTick 中断也被拖延HAL_Delay拿不到时基主循环到不了喂狗语句看门狗复位后又进入同一路径形成复位循环。解决办法是中断里只置标志位所有日志和延时都放主循环。4. 设计文档里最值得看的四类图架构、时序、布局、版本对照设计文档在源码包里常被当成“凑字数”但它其实是理解工程的捷径。拿到后不要逐页读先找四类图系统架构图、交互时序图、PCB 布局图以及原理图与代码的版本对照。4.1 系统架构图先确认三个边界架构图标出了模块之间的连接关系我看它时只关心三个边界。第一个是电源域边界12V 锁电源、5V 读卡器、3.3V MCU 之间怎么隔离强弱电隔离的位置就是设计上最容易被答辩追问的点。第二个是信号边界读卡器输出、继电器控制、门磁输入各自走哪条路径对应到代码里就是中断线和 GPIO 分组。第三个是人机接口边界按键、显示屏、蜂鸣器都挂在哪个接口上决定了源码里有多少个外设句柄需要初始化。如果架构图里出现了 AI 摄像头常见方案是 K210 与 STM32 通过串口通信。K210 做人脸识别后只输出“匹配/不匹配”的结果帧STM32 不关心识别过程只解析帧内容。帧格式一般只有 4 到 8 字节比如AA 55 01 0A 0F帧头、命令字、用户 ID、帧校验各占一段。校验通常是累加和具体算法以源码为准但架构图上看到这两颗芯片串接代码里就应该有一个独立的UART_RxHandle解析函数。4.2 交互时序图与关键时延参数核对时序图标出了从刷卡到开锁的完整链路需要关心的时延参数集中在四个阶段阶段典型时延可调参数验证方式读卡器识别30100ms读卡器轮询周期、SPI 频率串口时间戳命令解析与查表小于 1ms波特率、白名单长度断点计时锁控输出520ms继电器机械时间不可压缩门磁回馈门磁回收立即无状态机日志如果文档写了“开锁脉冲 300ms”源码里应该有对应的LOCK_HOLD_TIME_MS宏。用串口日志看事件间隔比对着文档猜靠谱。日志时间戳直接用HAL_GetTick()它从芯片上电开始按毫秒递增不需要额外接 RTC也够用来评估“刷卡到开锁”的整体时延。4.3 PCB 布局三处必看位置PCB 图不一定要有有的话重点看三个位置。第一是电源输入附近有没有防反接二极管、100uF 电解和 104 陶瓷电容如果只有小电容继电器吸合瞬间的电流跌落可能让 MCU 复位。第二是继电器区域有没有光耦和续流二极管的位号原理图里有但 PCB 上没画产品阶段只能飞线。第三是 RC522 天线区域天线周围 5mm 内不要走电源线背面地平面尽量完整天线面积直接决定读卡距离。4.4 文档与源码不一致时的处理顺序文档描述和源码不一致时优先级是MCU 数据手册 实际工程代码寄存器配置 原理图 设计文档文字。芯片行为是固定的代码是已经编译烧录过的原理图可能改版没更新文档最容易被忽略。换一块核心板后改一个引脚通常要同步三个位置GPIO 初始化结构体、外部中断线的EXTIx_IRQHandler、以及中断服务函数里对引脚号的判断。只改初始化不改中断按键和门磁都会失灵只改中断不改初始化编译直接报引脚号越界。5. 演示视频录制与验证60 秒清完最小流程留下三张对照表演示视频的价值不是展示“能开机”而是证明“边界状态都处理过”。我拍到最小可用流程按下面这张表来走总时长控制在 60 秒左右。步骤动作预期表现1冷启动上电OLED 或串口显示系统就绪2刷合法卡5 秒内继电器动作并开锁3刷非法卡蜂鸣器报警失败计数加 14输入密码并确认密码正确时开锁5开门后等待 5 秒超时后自动断锁6断电重启白名单仍然存在录制前先自己完整预演三遍确认每次状态迁移都一致再开机录。一镜到底不剪辑比剪辑过的视频更有说服力。5.2 串口日志与验证表门禁系统的状态迁移要用串口日志留痕。日志打点位置放在状态迁移处而不是主循环里每个周期都打印避免日志输出阻塞主流程#define LOG_EVT(tag, state) \ printf([%lu][%s] state%d\r\n, HAL_GetTick(), tag, state)对应验证表如下验证项期望结果串口标签合法卡开锁 5 秒[CARD][OK]非法卡蜂鸣报警计数加 1[CARD][DENY]密码超限30 秒锁定[PIN][LOCK]掉电重启白名单不丢[FLASH][LOAD]录制视频时镜头先拍串口日志再拍动作回看时每一秒都有时间戳可对照。5.3 Keil5 环境与更换芯片型号的验证如果源码编译环境还没准备好注意 Keil5 的 C51 和 STM32 支持包可以共存但 Device 列表里只显示已安装的 DFP。报cannot open source file stm32f1xx_hal.h时先在 Pack Installer 里装对应的 STM32F1xx_DFP。换芯片时用 STM32CubeMX 更改单片机型号时钟树保持 72MHz 后外设会自动重新映射但 GPIO 初始化里的标签不会保留所以改完必须回到MX_GPIO_Init核对一遍引脚定义。提示先确认 BOOT0 拉低、SWD 三根线没被复用到其他外设再接烧录器。SWD 被复用是 ST-LINK 连接失败最常见的原因和驱动版本没有关系。APM32 直接使用 STM32 程序这件事常见但有限制。APM32F103 系列引脚兼容性高寄存器级代码基本能编译启动文件和 HAL 库文件需要换成对应厂商的版本用了 ST 官方 HAL 库则要把库换成 APM32 的 SDK不能只改 Device 型号就默认全部跑通。做完一次“换芯编译”并记录改动点比单纯浏览开源代码更贴近工程落地。本文还有配套的精品资源点击获取