简介:本资源是一套基于STM32平台的完整智能门禁系统工程源码,面向计算机、电子信息、自动化等专业的本科生课程设计、毕业设计及单片机进阶学习者,解决多模态身份认证(人脸识别+RFID卡+蓝牙APP远程控制+数字密码)与嵌入式门禁逻辑集成的实际开发问题。压缩包共254个文件,含49个头文件(.h)定义硬件接口与功能模块,46个C源文件(.c)实现核心算法与外设驱动,以及编译生成的.o、.d、.axf、.hex等构建产物和Keil工程配置文件(.uvprojx/.uvoptx),整体大小为8.66MB,结构规范,便于理解STM32固件开发全流程。已有355人学习下载,资源提供可直接编译运行的完整工程,涵盖TIM/RCC/ADC/I2C/CAN等标准外设驱动代码,以及人脸识别图像采集、RFID读卡、蓝牙通信协议解析与密码锁状态机等关键模块,适合在掌握C语言与STM32基础后开展功能扩展与调试实践。 最近在整理一个综合性的嵌入式项目,刚好把基于STM32的智能门禁系统从头到尾理了一遍。这个项目的核心是四合一认证:人脸识别、RFID刷卡、蓝牙App远程控制、密码键盘开锁,整合在一颗STM32主控上。网上能搜到一堆零散的“门禁系统源码”,但真正能跑通、能讲清楚设计原理的不多。这篇就把我实际做过的方案、踩过的坑和最终的代码框架都拆开聊透,希望能给做毕业设计、比赛作品或者真实产品原型的朋友一个可复用的参考。
1. 这个项目解决什么问题:四种开锁方式的方案取舍
1.1 门禁场景的真实需求
门禁这个东西,看似简单,但一到实际场景就会发现单一认证方式永远不够用。办公室大门,白天人多,刷卡最快;晚上加班的人可能没带卡,这时候就需要密码;如果你手上抱着快递搬东西,腾不出手去按密码,人脸识别就很实用;而临时给访客开门、或者人在工位上个厕所被别人反锁在外面,手机蓝牙远程开锁就是救命的通道。
所以这套系统的定位很清晰:不是“做出来能开锁”,而是“在多重场景下都能稳定可靠地把门打开”。四个认证通道互相备份,任何一个单独用都能开锁,也可以配置成组合认证模式(人脸+密码同时满足才开门)。这种设计思路在真实产品和课程设计里都是加分项。
1.2 为什么选中STM32而不是树莓派或纯模块方案
核心原因就三个:成本、实时性、外设资源。
树莓派跑人脸识别当然更强,Linux生态也舒服,但在门禁这种任务里,成本近百块、启动时间几秒、稳定性受SD卡影响,这些都是硬伤。纯模块方案(用一个人脸识别模组直接联动电磁锁)虽然简单,但四个通道之间没法做统一的权限管理和日志记录,相当于四把锁各自为政,不叫“智能门禁”。
STM32系列里,我选了STM32F103C8T6,也就是经典的蓝丸核心板。这颗芯片主频72MHz、64KB Flash、20KB RAM,资源上正好能扛住这套系统:
- 人脸识别模组走串口,主控只是收发指令和响应,不跑算法,所以CPU压力不大
- RC522 RFID走SPI接口,速度足够
- 蓝牙模块走另一个串口
- 矩阵键盘占用8个左右的GPIO
- 电磁锁控制用继电器,一个GPIO就搞定
算下来外设刚刚好,不浪费也不紧张。如果你要额外加OLED显示屏、蜂鸣器、红外人体感应这些,F103C8T6也还留有富余。预算充裕的话可以直接上F407或F103ZET6,代码逻辑不用变,主要改引脚映射就能跑。
1.3 总体软件架构:裸机状态机
很多人一上来就想着上FreeRTOS。但实际评估下来,门禁这种业务逻辑并不复杂,核心就是一个事件驱动的状态机:等待认证 → 收到某种认证请求 → 校验该通道的凭据 → 通过则开锁/失败则提示 → 回到待机状态。
裸机状态机的优势是调试直观、代码可读性强、没有任务调度的心智负担。四个外设的数据接收全部走中断,中断里只做收数据和置标志位,真正处理数据的逻辑放在主循环的状态机里。这样不会出现中断里跑复杂逻辑导致其他通道丢数据的风险。
提示:如果项目要求扩展多个传感器或联动更多设备,再考虑上FreeRTOS,否则裸机是性价比最高的方案。我在上一版项目里就曾硬上RTOS,结果为了处理优先级反转花了两天时间,而需求并未复杂到需要RTOS的程度。
2. 硬件连接细节:从最小系统板到各模块引脚的完整梳理
2.1 引脚分配的整体规划
硬件设计的第一原则是避免引脚冲突,尤其是STM32的下载引脚和I2C/SPI专用引脚。我的引脚分配如下:
| 外设 | 接口类型 | 引脚 | 说明 |
|---|---|---|---|
| 人脸识别模组 | USART1 | PA9(TX)、PA10(RX) | 波特率115200 |
| RFID RC522 | SPI1 | PA5(SCK)、PA6(MISO)、PA7(MOSI)、PA4(CS) | STM32作为SPI主机 |
| 蓝牙HC-05 | USART2 | PA2(TX)、PA3(RX) | 波特率9600或115200 |
| 矩阵键盘(4x4) | GPIO输入 | PB0-PB7 | 行扫描+列检测 |
| 继电器 | GPIO输出 | PA1 | 高电平触发开锁 |
| 蜂鸣器 | GPIO输出 | PA0 | 认证成功/失败提示 |
| 状态指示灯 | GPIO输出 | PB8、PB9 | 红绿LED |
这几个外设接口之间没有复用,唯一要注意的是PA9/PA10、PA2/PA3串口的TX/RX交叉连接,这个是最容易接反的。人脸模组和STM32连接时,STM32的PA9(TX)要接模组的RX,PA10(RX)接模组的TX。
2.2 人脸识别模组的选型与接线
人脸识别模块是整套系统里唯一外购的“黑匣子”。市面上常见的有三类:
- 串口型人脸识别模组(如中控智慧、汉王等):自带摄像头和算法,输出识别结果和用户ID,价格在50-150元之间
- OpenMV/K210开发板:自己写图像识别代码,自定义程度高,但需要额外学习MicroPython或K210 SDK
- ESP32-CAM+OpenCV后端:摄像头采集,WiFi传到服务端识别,延迟高,不适合门禁
我的推荐是串口型人脸识别模组,理由很简单:门禁系统主控的职责是逻辑控制和通道管理,不应该陷进图像算法里。串口型模组内部已经完成了人脸检测、特征提取、比对识别,对外只需发送“注册人脸”“删除人脸”“识别”等指令,返回结果也是现成的JSON或定长帧,这对STM32这种性能有限的主控非常友好。
接线就三根线:VCC(5V)、GND、TX/RX交叉连接。部分模组支持UART TTL电平,直接和STM32同电压域连接,不需要额外转换。如果是5V电平的模组,必须加电平转换芯片(如MAX3232)或用分压电阻,不能直接怼到STM32引脚上,不然大概率烧引脚。
2.3 RFID、蓝牙、键盘的连接
RC522 RFID模块是SPI接口,供电范围3.3V-5V,但IO逻辑电平必须注意:很多RC522模块板上自带稳压和电平转换,可以直接接3.3V供电并使用3.3V SPI信号。如果模块不带电平转换,5V供电时SPI信号千万不能直接接STM32引脚,否则电压超标。
HC-05蓝牙模块工作在3.3V,但它有个坑:STATE引脚和EN引脚电平是3.3V,而KEY引脚(进入AT模式)要求高电平。接STM32时,TX/RX用串口2,KEY引脚接一个GPIO或者直接接3.3V进入AT模式配置,配置完再断开。
4x4矩阵键盘的接线相对简单,8根引脚两边分别接STM32的PB0-PB3(行)和PB4-PB7(列),全部配置为输入上拉,通过行列扫描检测按键。注意PB3、PB4在F103上是JTAG引脚,默认是复用功能,需要先禁用JTAG才能当普通GPIO用,这个坑后面细说。
2.4 电源系统的估算
整套系统的功耗集中在大头:
- STM32核心板:约50mA
- 人脸模组:平均100-200mA,峰值300mA
- RC522:约30mA
- HC-05:约40mA
- 继电器+电磁锁(通电瞬间):电磁锁工作电流300-800mA(视型号),继电器线圈约70mA
所以总电流峰值接近1A。USB供电(5V/2A)够用,但如果是锂电池供电,必须选容量足够且带过流保护的电池,并且考虑电磁锁的感性负载反向电动势问题。继电器线圈两端必须并一个1N4007续流二极管,否则断电瞬间产生的反向尖峰电压会直接冲击STM32电源轨,甚至导致主控复位。
3. 固件工程搭建:HAL库、多串口、DMA空闲中断与状态机框架
3.1 基础工程结构
整个固件代码我按模块化方式组织,目录结构大致如下:
Project ├── Core │ ├── Inc │ └── Src │ ├── main.c │ ├── stm32f1xx_hal_msp.c │ ├── stm32f1xx_it.c │ └── system_stm32f1xx.c ├── Drivers │ └── STM32F1xx_HAL_Driver ├── Middlewares │ └── src │ ├── state_machine.c │ ├── protocol.c │ └── event_queue.c └── App ├── face_module.c ├── rfid_module.c ├── keypad_module.c ├── bluetooth_module.c └── door_control.c核心思路是:每个外设对应一个.c文件,提供Init()、Handle()、GetEvent()等接口,业务逻辑层只通过统一的接口访问各外设,避免在main.c里堆几千行的面条代码。
3.2 多串口的数据接收方案:DMA+空闲中断
门禁系统的人脸模组和蓝牙模块都会持续不断地发数据,而且数据长度不确定。最麻烦的问题是:STM32的USART接收如果不处理空闲状态,你永远不知道一帧数据什么时候结束。
我给两个串口都用了DMA+IDLE空闲中断方案。原理是这样:DMA把接收到的字节持续搬运到内存环形缓冲区,当串口总线上出现空闲(一个完整帧发完了,线路空闲超过一个字节时间)时,USART会产生IDLE中断,此时读取DMA当前剩余计数,就能知道这一帧收了多少字节。这样不管发来的是4字节指令还是128字节的人脸特征数据,都能一次收完。
关键代码片段:
void USART1_IRQHandler(void) { if (RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); /* 停止DMA,计算接收长度 */ HAL_DMA_Abort(&hdma_usart1_rx); receive_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); /* 设置事件标志 */ event_set(EVENT_FACE_DATA_READY, receive_len); /* 重新启动DMA接收 */ HAL_UART_Receive_DMA(&huart1, (uint8_t*)face_rx_buf, BUFFER_SIZE); } HAL_UART_IRQHandler(&huart1); }这个方案的优点是不占用CPU,主循环只管处理数据,收数据由DMA自动完成。实测在115200波特率下,即使连续接收几百字节的人脸模板数据,也不会丢字节。
3.3 事件状态机的设计
门禁的整个业务逻辑可以抽象成以下几种事件:
| 事件 | 触发源 | 对应动作 |
|---|---|---|
| EVENT_FACE_DATA | 人脸模组返回识别结果 | 判断是否注册用户,是则开锁 |
| EVENT_RFID_CARD | RC522检测到卡片 | 读取卡号,比对授权列表 |
| EVENT_KEYPAD_INPUT | 键盘按下 | 收集数字,满N位后校验密码 |
| EVENT_BT_CMD | 蓝牙收到App指令 | 解析指令类型,执行开锁/添加卡/查询日志 |
| EVENT_AUTH_SUCCESS | 任一通道校验通过 | 继电器吸合2秒,蜂鸣器响一声 |
| EVENT_AUTH_FAIL | 任一通道校验失败 | 蜂鸣器响三声,连续失败5次锁定60秒 |
| EVENT_TIMEOUT | 开锁后超时 | 继电器释放,回到待机 |
状态机就五态:IDLE、AUTHENTICATING、OPENING、LOCKED、ERROR。
- IDLE:等待事件,任何认证通道都能进入AUTHENTICATING
- AUTHENTICATING:正在校验凭据,此时其他通道的数据可以排队但先不处理
- OPENING:继电器已吸合,等待2秒后自动回到IDLE
- LOCKED:连续认证失败触发锁定,倒计时结束才回IDLE
- ERROR:异常状态(如看门狗复位后、存储校验失败),需要管理员密码才能恢复
状态机的实现在main循环里,用一个switch-case包裹,代码结构清晰、方便加新状态。
while (1) { uint32_t event = event_poll(); switch (current_state) { case STATE_IDLE: handle_idle_state(event); break; case STATE_AUTHENTICATING: handle_auth_state(event); break; case STATE_OPENING: handle_opening_state(event); break; case STATE_LOCKED: handle_locked_state(event); break; case STATE_ERROR: handle_error_state(event); break; default: break; } }4. 人脸识别开锁:与识别模组的串口对接和协议解析
4.1 模组的工作原理与对外接口
人脸模组内部的事情我不展开说,但要知道它的工作流:上电初始化 → 检测到人脸 → 提取特征 → 与内部注册的人脸特征库比对 → 输出结果。对外暴露的指令集大致分三类:
- 注册类指令:添加一个新用户,传入用户ID和人脸照片数据(通常需要人脸正对摄像头,模组自己采集)
- 删除类指令:按用户ID删除人脸数据
- 识别类指令:模组自动检测并比对,返回“通过/不通过+用户ID”或者直接返回识别到的用户ID
我的方案是让模组工作在人脸检测自动上报模式下,也就是每隔一段时间模组检测到人脸,自动向串口发送一帧识别结果。STM32这边只需要解析这一帧,不需要频繁地发识别指令。
4.2 通信帧格式的解析
我用的人脸模组帧格式是一个自定义协议,固定帧头是0xAA 0x55,长度字段表示后续数据长度,数据区包含指令类型、状态、用户ID和校验。
例如识别成功的帧可能是:
AA 55 0E 01 00 00 00 01 00 03 00 01 05 E2AA 55:帧头0E:数据长度14字节01:指令类型(识别结果上报)00 00 00 01:状态,1表示识别成功00 03 00 01:用户ID整形(这里是769)05 E2:CRC16校验
解析代码就要做到:从字节流里找帧头,然后按长度收完一帧,校验CRC,再提取状态和ID。这里我强烈建议写一个通用的帧解析器,不要在一个中断回调里把帧处理完。帧解析器可以用状态机来写:
typedef enum { PARSE_FRAME_HEAD1, PARSE_FRAME_HEAD2, PARSE_FRAME_LEN, PARSE_FRAME_DATA, PARSE_FRAME_CRC } ParseState; void face_parse_byte(uint8_t byte) { switch (parse_state) { case PARSE_FRAME_HEAD1: if (byte == 0xAA) parse_state = PARSE_FRAME_HEAD2; break; case PARSE_FRAME_HEAD2: if (byte == 0x55) parse_state = PARSE_FRAME_LEN; else parse_state = PARSE_FRAME_HEAD1; /* 重新同步 */ break; case PARSE_FRAME_LEN: frame_len = byte; rx_index = 0; parse_state = PARSE_FRAME_DATA; break; case PARSE_FRAME_DATA: data_buffer[rx_index++] = byte; if (rx_index >= frame_len) parse_state = PARSE_FRAME_CRC; break; case PARSE_FRAME_CRC: if (crc16_check(data_buffer, frame_len, byte) == 0) { face_frame_callback(data_buffer, frame_len); } parse_state = PARSE_FRAME_HEAD1; break; } }这种字节级状态机的好处是对数据流中的噪声不敏感,即使前一个帧头被干扰丢掉了,也能在下一个帧头处自动恢复同步。实际运行中,只要串口不丢字节,这个解析器是100%可靠的。
4.3 识别结果的异步处理
主循环里收到“识别成功”事件后,提取用户ID,查询在EEPROM/Flash中保存的用户权限表,决定是否放行。注意,人脸识别和密码/RFID的校验逻辑略有区别:人脸模组返回的ID是模组内部的用户索引,而门禁系统有自己的用户ID体系,所以中间需要一层映射。
我在设计里让模组用户ID就是从1开始的连续编号,而门禁系统的授权表采用“用户ID + 权限位 + 门禁时段”的结构。比如:
typedef struct { uint32_t user_id; uint8_t face_enable; uint8_t rfid_enable; uint8_t password_enable; uint8_t bt_enable; uint8_t time_slot; /* 索引到时间规则表 */ uint8_t is_admin; } AccessUser;这样在新增一个用户时,管理员可以灵活指定他有哪些通道的权限。人脸识别成功只是通过了“脸”这个凭证,门禁系统还要看他是否有使用人脸开门的权限。这个设计在后期扩展用户管理时非常有用。
5. RFID与密码锁:RC522与矩阵键盘的完整实现
5.1 RC522的SPI初始化和M1卡操作
RC522是NXP的经典RFID读卡芯片,支持ISO/IEC 14443A协议的M1卡,比如常见的S50卡。STM32通过SPI接口访问RC522的寄存器,使用瑞萨/恩智浦官方提供的RC522驱动代码,把关键函数封装成3个接口:
RC522_Init():初始化SPI、复位RC522、设置天线增益RC522_Request():请求寻卡,检测天线范围内是否有卡片RC522_Anticoll():防碰撞,拿到卡片的唯一序列号(4字节)
卡片检测的典型时序是:
uint8_t rc522_check_card(uint8_t *card_id) { uint8_t status; status = RC522_Request(PICC_REQIDL); /* 寻卡 */ if (status == MI_OK) { status = RC522_Anticoll(card_id); /* 防碰撞,获取卡号 */ if (status == MI_OK) { /* 这里可以读取卡数据块的UID序列号 */ /* 然后执行Halt指令让卡片休眠 */ RC522_Halt(); return 1; } } return 0; }处理卡号后要立即停止射频操作,即发送PICC_HALT命令,否则卡片会一直握手,影响下一次寻卡。很多初学者漏了这一步,结果发现第二张卡刷不进去。
5.2 卡号的存储与新增授权流程
卡号是4字节的UID,比如1A 23 45 67。授权卡号列表可以存在片上Flash的末尾扇区或外挂的AT24C02 EEPROM里。Flash的优点是省一个芯片,但有擦写寿命限制(F103的Flash擦写次数约1万次),如果需要频繁增删卡,推荐用EEPROM。
我处理的方式是:Flash专门开一个扇区来存“认证配置块”,每次改动先读出、修改、擦除扇区、写回。这个流程在写配置时要注意防止掉电丢数据——最简单的策略是“写前备份旧数据到下一个扇区”,掉电后上电检测到主扇区校验失败就自动从备份扇区恢复。
新增RFID卡的流程是:管理员密码验证通过 → 进入“发卡模式” → 新卡靠近 → RC522读到UID → 存入授权表 → 蜂鸣器提示成功。这类管理操作可以放在菜单逻辑里,配合密码键盘完成。
5.3 矩阵键盘扫描和密码验证逻辑
4x4矩阵键盘扫描的方法很常规:先将4根行线(PB0-PB3)设为输出低电平,4根列线(PB4-PB7)设为输入上拉,依次拉低每一行,检测列线是否被拉低。如果某一列被拉低,说明该行该列的键被按下。行扫描+列检测的代码如下:
uint8_t keypad_scan(void) { uint8_t row, col; for (row = 0; row < 4; row++) { GPIO_WriteLow(KEYPAD_ROW_PORT, row_mask[row]); for (col = 0; col < 4; col++) { if (GPIO_ReadInputDataBit(KEYPAD_COL_PORT, col_mask[col]) == RESET) { delay_ms(10); /* 消抖 */ while (GPIO_ReadInputDataBit(KEYPAD_COL_PORT, col_mask[col]) == RESET); return keymap[row][col]; } } GPIO_WriteHigh(KEYPAD_ROW_PORT, row_mask[row]); } return KEY_NONE; }密码逻辑分几部分:输入状态机(等待密码长度、按#确认、按*清空)、密码校验(与存储的密码哈希比对)、连续错误锁定(5次失败锁60秒)。密码存储不要明文保存,我用的办法是CRC16+盐,即使Flash被读出来也还原不出原始密码。
5.4 密码锁与RFID的组合模式
在默认配置里,RFID和密码是“或”的关系:刷合法卡或输入正确密码都能开锁。但如果部署在安全等级高的场所,可以切换为“与”模式:必须先刷卡,然后在键盘上输入卡对应的PIN码,两者都通过才开锁。这个功能在代码里其实就是给状态机加一个STATE_AUTHENTICATING_MULTI_FACTOR状态,先缓存RFID卡号,再等待键盘输入密码,两件事都完成后合并校验。这就是多因素认证(MFA)在嵌入式设备上的一个典型实现。
6. 蓝牙App远程控制:自定义通信协议与手机端应用实现
6.1 HC-05模块的AT模式配置
HC-05是一款经典的蓝牙2.0 SPP模块,手机上用“蓝牙串口助手”类App就能连接。它默认是AT模式还是透传模式,主要看KEY引脚的电平:KEY接高电平后上电,进入AT模式;KEY接低电平或悬空,进入透传模式。
配置环节的关键步骤:
- 按住HC-05上的按钮上电,或者把KEY接3.3V再上电,模块LED慢闪表示进入AT模式
- 用USB-TTL连接模块,打开串口助手,波特率38400,发送
AT,返回OK - 设置名称:
AT+NAME=SmartDoor - 设置配对密码:
AT+PSWD=1234 - 设置波特率:
AT+UART=9600,0,0 - 断开KEY引脚,重新上电,进入透传模式
注意:HC-05的AT指令波特率经常被人忽略,新版本固件的AT波特率可能是38400,但透传波特率按AT+UART设置。如果连不上AT模式,先试试38400。
6.2 自定义协议格式设计
蓝牙通道的特点是:数据可以被第三方截获,所以协议里必须有基本的校验和安全设计。我的自定义协议帧格式:
起始符(0x5A) 帧类型(1字节) 数据长度(1字节) 数据区(不定长) 校验和(1字节) 结束符(0xA5)帧类型定义:
| 帧类型值 | 含义 | 数据区内容 |
|---|---|---|
| 0x01 | 开锁请求 | 4字节时间戳 |
| 0x02 | 心跳包 | 1字节设备状态 |
| 0x03 | 查询日志 | 无 |
| 0x04 | 添加临时密码 | 6字节新密码 |
| 0x05 | 远程锁定 | 无 |
| 0x06 | 开锁响应 | 1字节结果+1字节剩余电量 |
校验和是“起始符+帧类型+长度+数据区”所有字节的累加取反。虽然不算高强度的加密,但足以防止普通串口调试工具伪造指令。真要安全,就要在上层加AES加密,STM32也能跑得动AES-128,只是代码量会翻倍,可以考虑为产品版本预留升级空间。
6.3 手机端App的实现思路
App端我用的是Android原生+一个蓝牙串口库(如经典的BluetoothSerial),核心流程很清晰:
- 扫描设备,找到名字为
SmartDoor的HC-05 - 配对并建立SPP连接(UUID是
00001101-0000-1000-8000-00805F9B34FB) - 发送开锁指令帧,等待STM32回响应帧
- 收到响应后根据结果弹提示
如果你不想写原生App,也可以直接用开源的“蓝牙串口助手”App,手动发送十六进制指令,把5A 01 04 A1 B2 C3 D4 校C 78 A5发出去就能开锁。对测试协议来说,这比开发App快得多。
App端的界面我做到了三个页面:
- 主页:门锁状态显示、开锁按钮、远程锁定开关
- 用户页:查看当前授权用户列表
- 日志页:显示最近的开锁记录(这个需要STM32把日志存到Flash,再通过蓝牙查询指令传上来)
蓝牙操作的稳定性有个经验:HC-05透传数据时,STM32这边的接收中断里千万不能屏蔽全局中断太久。有一次我在USART2中断里调用了printf(内部阻塞等待串口1发送),结果蓝牙数据直接丢失,换来了一个很难查的问题。中断里只做收数据和置标志,其他的全部丢到主循环。
7. 系统联调中踩过的坑与最后的安全打磨
7.1 串口数据错乱和波特率匹配
联调时最容易出的问题就是人脸模组和蓝牙模块波特率不匹配。人脸模组默认是115200,蓝牙模块我配成9600。两个串口波特率不同,必须在STM32初始化时分别设置,别图省事直接用HAL_UART_Init的默认值。
还有一个隐蔽的坑:使用ST-Link离线下载程序会影响串口数据。ST-Link连在SWD引脚上时,如果软件里开了SWD调试、又同时跑着串口收发的话,ST-Link的复位和调试请求会和串口传输抢内部总线,导致串口数据出现随机错乱。解决办法是调试时不要直接用ST-Link的串口功能,也不要开着High priority的调试中断跑串口。
7.2 继电器控制电磁锁的反向电动势问题
这是项目里最危险的坑。刚开始测试时,我把继电器直接接在电磁锁上,发现STM32经常复位。用示波器看电磁锁断电瞬间,电源轨上出现了20多伏的毛刺。原因就是继电器切断感性负载的电流时,电磁锁线圈会瞬间产生高压反向电动势。
解决方案:
- 继电器线圈两端并联1N4007续流二极管,阳极接GND,阴极接继电器控制端(对应电源正极)
- 电磁锁本身并联一个RC吸收电路(如100Ω+0.1uF串联),吸收开关尖峰
- 主控板供电和继电器驱动供电分开,共地但不共电源线,用光耦(如PC817)做隔离控制
经过这三层处理后,实测电磁锁反复开关上百次,STM32的供电非常干净。
7.3 安全策略:看门狗、失败锁定、事件日志
门禁系统是安防设备,安全性不能只看“能不能开锁”,还要看“开不了锁时怎么办”和“被攻击时怎么办”。
- 独立看门狗(IWDG):在主循环里喂狗,一旦主循环卡死(比如内存溢出或死循环),看门狗自动复位整个系统,保证门禁不会因为程序异常永远瘫痪
- 连续失败锁定:无论哪个通道,连续失败5次就锁定60秒,期间任何认证请求都不处理。防的就是暴力试密码和试卡
- 事件日志:把每次开锁成功/失败、管理员操作、系统复位记录到Flash循环日志区。虽然STM32的Flash容量不大,但每条日志压缩成16字节,能存上千条,足够回溯最近使用情况
7.4 最终效果的实测和扩展方向
整机实测下来,人脸识别从检测到开门约2-3秒(模组的识别速度决定),RFID刷卡几乎是瞬时响应,蓝牙开锁受手机连接时间影响在1秒左右,密码开锁输入6位密码加确认约3秒。这个性能作为办公室门禁是完全够用的。
如果想把项目再往前推进一步,可以考虑这些扩展:
- 加一块0.96寸OLED显示当前状态和用户信息,提升交互体验
- 接入ESP8266/ESP32模块,把开门记录同步到云端,支持远程查看
- 用FreeRTOS改造,增加电源管理,支持低功耗待机模式
- 把密码和卡号换成更安全的算法,比如AES-128加密存储
根据我个人经验,这个项目的核心价值不在于“能跑通”,而在于把四种异构的外设认证方式统一到一个清晰的状态机框架里,同时在硬件上真正解决了干扰、电源和可靠性问题。如果你打算拿这套系统做毕业设计,代码结构和文档化的设计思路会比单纯的功能演示加分得多。祝调试顺利。
本文还有配套的精品资源,点击获取