简介:本资源是一套基于STM32平台实现的多功能智能门禁系统完整源码工程,面向计算机、电子信息、自动化等专业的本科生课程设计、毕业设计及单片机进阶学习者,解决传统门禁功能单一、扩展性差的问题,集成人脸识别、RFID卡识别、蓝牙手机App控制与数字密码锁四大核心模块。压缩包共254个文件,含49个头文件(.h)定义硬件接口与功能模块,46个C源文件(.c)实现算法逻辑与外设驱动,以及编译中间文件(.o/.d/.crf)、Keil工程配置(.uvprojx/.uvoptx)、可执行镜像(.hex/.axf)等,结构完整,开箱即用,总大小8.66MB。已有355人下载学习,资源包含标准STM32F10x固件库驱动(如stm32f10x_rcc.c、stm32f10x_i2c.c等),并附带keilkill.bat等实用工具脚本,便于快速清理与重建工程,适合希望深入理解嵌入式多模态身份认证系统架构与协同控制机制的学习者参考与二次开发。 做智能门禁系统这个项目,前后断断续续花了大概一个半月。最开始的需求很简单:实验室入口的门禁想升级,原来只有一张IC卡,人来人往经常丢卡,管理员补卡补到崩溃。聊了几轮之后,需求就变成了"要四种方式开锁:密码、刷卡、蓝牙、人脸,最好还能看记录"。我评估了一下手头的模块,直接定了STM32为主控,然后把整个系统搭了起来。这篇文章就把整个项目的技术拆解、硬件选型、软件实现和踩坑记录整理出来,给正在做类似项目的朋友做个参考。
这套系统做下来,核心价值其实不在某一个模块,而在"多模态融合"这件事上。单独玩人脸识别、单独刷RC522、单独做蓝牙APP,网上教程一大把,但真正把它们集成到一块板子上、用一套逻辑管理起来,让四种方式稳定共处,这才是整个项目最花精力的地方。适合正在做嵌入式课程设计、毕业设计,或者想把手头的门禁方案升级成多模态系统的工程师参考。
1. 项目整体架构:四种门禁方式的设计取舍
1.1 业务逻辑与四种开门方式的优先级
先明确一个核心问题:四种开门方式之间的关系是什么?是"或"的关系,谁都能开;还是"分层管理",不同人有不同权限?我最终采用的是并行验证、统一执行的策略——四种方式各自独立识别,任何一种验证通过,系统就走同一套开锁逻辑(驱动舵机/继电器)。但在具体实现上,每种的业务逻辑有差异:
- 密码锁:最基础的兜底方案。即使人脸识别挂掉、蓝牙连不上、卡丢了,只要记住密码就能进。我把密码设计成"管理员密码+用户密码"两级,管理员密码可以修改所有用户密码和卡片白名单,用户密码只能开门。
- RFID刷卡:最稳定的方案。RC522读取卡片UID,和存储在Flash里的白名单比对。白名单支持管理员卡注册、注销。实测下来识别速度在几百毫秒级别,比人脸快得多,适合日常高频进出场景。
- 蓝牙APP:最灵活的方案。手机连上HC-05,通过自定义协议发指令。APP端走的是"按钮+状态反馈"模式,点击开门按钮,STM32校验帧结构后执行开锁并回传状态。这个方案很适合远程临时授权,比如人不在实验室,让同事帮忙进去拿东西。
- 人脸识别:科技感最强的方案。但也是四者里最"娇气"的,受光线、角度影响最大。所以我把它的定位设定为"辅助验证",而不是唯一验证——比如门禁场景要求高安全时,可以设置成"人脸+密码"双因子验证,否则单独人脸通过就开门。
四种方式的执行顺序上,我做了个简单的互斥锁设计:任意方式验证成功后,系统进入开锁状态,期间其他方式的验证请求会被挂起,直到门锁复位(我设定的是5秒后自动上锁)。这避免了多个验证同时通过导致舵机反复动作的问题。
1.2 为什么选STM32而不是树莓派、ESP32或K210单片方案
这是个很实际的选择题。我当时在几个候选方案里纠结过一轮:
- 树莓派方案:性能最强,Python生态写OpenCV人脸识别几乎是现成的,但短板很明显——体积大、成本高、需要Linux系统,开机要几十秒,做门禁这种"上电几秒内就要能用"的场景不太合适。而且树莓派的GPIO电平是3.3V,虽然和多数模块兼容,但整体方案的嵌入式属性偏弱,学生做毕设容易被质疑"没有嵌入式含量"。
- ESP32方案:集成了WiFi和蓝牙,价格比同性能的STM32还便宜,理论上完全能跑这个项目。但问题是ESP32的人脸识别生态不如K210成熟,大部分得跑ESP-DL框架,调试成本高。另外很多学校的嵌入式课程主线是STM32,用ESP32在"技术对接度"上会弱一些。
- STM32 + K210协处理器方案:这是我最终采用的方案。STM32F103C8T6负责整个系统的调度——扫描矩阵键盘、通过SPI读RC522、通过USART和HC-05通信、通过另一个USART接收K210的人脸识别结果、输出PWM控制舵机。K210只干一件事:摄像头采集图像,跑人脸检测和识别模型,把结果通过串口发给STM32。各司其职,非常清晰。
这里给新手一个建议:不要试图让STM32F103去跑人脸识别算法。F103的主频是72MHz,RAM只有20KB,跑卷积神经网络根本不可能。人脸识别必须挂在带硬件加速器或独立算力的模组上(K210、OpenMV、树莓派Zero都行),STM32只负责"接收结果"和"执行动作"。
1.3 方案选型的三个关键决策点
第一个决策点是主控选型。我选的是STM32F103C8T6,也就是大家常说的"蓝板",理由是:它足够便宜(模块价格在十元级别),外设接口丰富(多路USART、SPI、I2C、ADC),资料生态极好——不管是标准库还是HAL库,网上的例程一堆,踩坑时很救命。如果你的项目需要同时跑人脸识别+屏幕显示+更多传感器,可以升级到F407或F429,但F103对这个门禁场景是够用的。
第二个决策点是人脸识别模组。我对比过三种方案:OpenMV(贵,但上手简单,MicroPython开发)、树莓派Zero做上位机(性能强,但要配Linux,启动慢)、K210(性价比最高,KPU硬件加速,支持本地跑yolo和face detection模型)。最后选了K210搭配OV2640摄像头,用MaixPy开发。K210模组几十块钱,识别速度能跑到每秒几帧,对门禁场景足够了。
第三个决策点是RFID模块选型。市面上最常见的RC522是SPI接口,也有I2C和UART版本。我选了SPI版本,因为STM32的硬件SPI速度比模拟I2C快,读卡延迟更小。要注意RC522是3.3V电平,和STM32的IO电平匹配,不需要额外转换电路。
2. 硬件选型与电路连接要点
2.1 硬件模块清单与选型理由
整个项目用到的模块清单如下:
| 模块 | 型号/规格 | 接口 | 供电 | 作用 |
|---|---|---|---|---|
| 主控 | STM32F103C8T6 | - | 3.3V | 系统调度与逻辑控制 |
| 人脸识别 | K210 + OV2640摄像头 | UART | 5V | 人脸检测、特征比对 |
| RFID | MFRC522 | SPI | 3.3V | 读取卡片UID |
| 蓝牙 | HC-05 | UART | 5V(板载3.3V LDO) | 手机APP通信 |
| 密码输入 | 4x4矩阵键盘 | GPIO | 3.3V | 密码输入 |
| 显示 | OLED SSD1306(128x64) | I2C | 3.3V | 状态显示 |
| 开锁执行 | SG90舵机 | PWM | 5V | 控制锁舌 |
每个模块的选型,我都是经过实测对比的。比如蓝牙模块,HC-05和HC-06都试过:HC-06只能做从机,适合"手机主动连开发板"的场景,但如果以后想升级成"开发板主动连手机"(比如远程升级固件),HC-06就不行了。所以直接上HC-05,它支持主从一体,AT指令可以配置角色,通用性更强。
再比如矩阵键盘,4x4的比1x4薄膜按键好用的多——1x4只能输数字,连"确认"和"删除"都要额外做按键组合。4x4直接可以把"*"当确认键、"#"当删除键,操作逻辑简单很多。
2.2 接线表与电平匹配细节
接线这块是软件调试之前最需要仔细核对的部分。我把各个模块和STM32的连接整理成了一张表,照着接线基本不会出错:
| STM32引脚 | 连接的模块引脚 | 说明 |
|---|---|---|
| PA0-PA7 | 矩阵键盘的8根线(4行4列) | 行接PA0-PA3,列接PA4-PA7 |
| PA8 | SG90舵机信号线 | TIM1_CH1,PWM输出 |
| PA5 | RC522 SCK | SPI1_SCK |
| PA6 | RC522 MISO | SPI1_MISO |
| PA7 | RC522 MOSI | SPI1_MOSI |
| PA4 | RC522 SDA(CS) | SPI1_NSS,软件片选 |
| PA2 | HC-05 TXD | USART2_RX |
| PA3 | HC-05 RXD | USART2_TX |
| PB6 | OLED SCL | I2C1_SCL |
| PB7 | OLED SDA | I2C1_SDA |
| PB10 | K210 TXD | USART3_RX |
| PB11 | K210 RXD | USART3_TX |
这里有两个接线时必须注意的细节:
电平匹配问题。HC-05模块的供电是5V,但它的RXD引脚如果直接接3.3V的STM32 TXD是没问题的(5V模块的RXD通常兼容3.3V逻辑),可反过来就不一定——STM32的RXD承受不了5V。稳妥做法是给模块供电5V,但确保信号线都是3.3V逻辑。HC-05的设计一般是兼容的,但别的蓝牙模块不好说,保险起见可以用万用表量一下RXD在空闲状态的电平。
舵机供电单独走。SG90舵机转动时峰值电流可能到几百毫安,如果和STM32共用一路5V供电,会拉低电压导致MCU复位。我试过直接从一个USB口取电,舵机一打角度,OLED闪屏、蓝牙掉线,全是电源问题。最终给舵机单独一路5V供电(用独立USB口或一个AMS1117-5.0的降压模块),地和STM32共地,问题立刻消失。
2.3 供电设计与抗干扰处理
整板供电用的是最常见的方案:USB 5V输入 → AMS1117-3.3稳压 → STM32及各3.3V模块。如果你想让系统做到12V/24V门禁供电,可以在入口加一个MP1584或LM2596降压模块,先降到5V,再接LDO降到3.3V。但要注意,降压模块的纹波可能会比较大,如果OLED显示不稳定或蓝牙误码率升高,要考虑在5V和3.3V之间加个几十微法的钽电容滤波。
人脸识别部分我单独说一句:K210模组对供电质量比较敏感,建议在它的5V供电入口并联一个100uF电解电容和0.1uF陶瓷电容,实测能明显减少人脸识别偶发死机的问题。这个细节在大多数教程里不会提,但实际做下来非常关键。
3. 软件实现:从底层驱动到APP联调
软件部分是这个项目的大头,也是大多数朋友卡住最多的地方。我按照功能模块拆开讲,最后再讲它们是怎么被"调度"到一块的。
3.1 密码锁模块:矩阵键盘扫描与防爆破设计
密码锁的实现核心是矩阵键盘的扫描。4x4矩阵键盘的原理是8个IO口组成4行4列,通过"逐行拉低、逐列读取"的方式判断按键。我写了一个常用的扫描函数,思路是:先把所有行引脚设为输出低电平,所有列引脚设为输入上拉;然后逐行将某一行设为低电平,读取列的电平变化,有按键按下时对应列会被拉低。
uint8_t MatrixKey_Scan(void) { uint8_t row, col, key = 0xFF; // 所有行输出低电平,列输入上拉 for (row = 0; row < 4; row++) { // 将当前行设为低电平,其他行设为高电平 Set_Row_Low(row); for (col = 0; col < 4; col++) { if (GPIO_ReadPin(Column_Pin[col]) == RESET) { delay_ms(10); // 消抖 if (GPIO_ReadPin(Column_Pin[col]) == RESET) { while (GPIO_ReadPin(Column_Pin[col]) == RESET); // 等待松开 key = keymap[row][col]; break; } } } Set_Row_High(row); if (key != 0xFF) break; } return key; }密码存储方面,我坚持不把明文密码写死在代码里。做法是把密码哈希后存到STM32内部Flash的特定扇区,每次输入密码后做同样的哈希运算再比对。F103没有硬件加密单元,我就用了简单的CRC32或MD5的软件实现,虽然谈不上绝对安全,但比明文强得多。管理员密码可以通过连续按住"*"键3秒进入管理模式来修改,修改过程要求先输入旧密码再输入新密码,防止误操作。
防爆破是密码锁必须做的功能:连续输错5次密码,系统锁定60秒。锁定期间无论输入什么都不会校验,OLED上显示"Locked 60s"倒计时。这个逻辑看着简单,但真能挡住绝大多数"暴力试密码"的尝试。
3.2 RFID模块:RC522读卡与卡片白名单管理
RFID模块的软件核心是RC522的SPI通信和ISO14443A协议的卡片防碰撞流程。RC522读卡的底层流程大概是:复位RC522 → 设置天线 → 发送"寻卡"指令 → 收到卡片响应 → 读取卡片UID → 校验。大多数教程都会用现成的RC522库,我不建议自己从零写底层协议,直接用现成驱动库效率最高。但要注意两个细节:
// 读取卡片UID的核心伪代码 uint8_t RF_ReadCardUID(uint8_t *uid_buf) { uint8_t status; uint8_t tag_type[2]; status = PcdRequest(PICC_REQALL, tag_type); // 寻卡 if (status != MI_OK) return 0; status = PcdAnticoll(uid_buf); // 防碰撞,获取UID if (status != MI_OK) return 0; PcdHalt(); // 休眠卡片 return 1; }卡片白名单管理是我额外加的:Flash里存一个最多20个UID的数组,管理员卡(默认UID为0xXXXXXXXX的那张)在系统启动5秒内刷卡,可以进入"注册模式",之后刷的每一张新卡都会自动加入白名单。这个设计避免了每次改卡都要重新烧录固件的尴尬,实际使用中非常方便。
还有一个坑是RC522的天线线圈。如果你用的是那种带线圈的分体天线板,连接线长度超过10cm就可能出现读卡距离急剧缩短的情况。我遇到过这个问题,排查一晚上,最后把连接线缩短到5cm内,读卡距离立刻恢复到3cm以上。模块自带PCB天线的版本就没这个问题,建议新手直接买自带天线的模块。
3.3 蓝牙模块:HC-05配置与应用层协议
先讲HC-05的配置。新买的HC-05一般默认是从模式,波特率9600或38400。我把它配置成了从模式、38400波特率、配对码1234。用USB转TTL在电脑上通过AT指令配置:
AT+ORGL # 恢复默认设置 AT+UART=38400,0,0 # 设置波特率38400,8N1 AT+ROLE=0 # 从模式 AT+PSWD="1234" # 设置配对码 AT+NAME="SmartDoor" # 设备名配置完成后,STM32通过USART2和HC-05通信。通信协议我自定义了一套简单帧结构:帧头(0xAA) + 指令码 + 数据长度 + 数据 + 校验和。门禁场景只需要少数几条指令:
| 指令码 | 方向 | 含义 |
|---|---|---|
| 0x01 | APP→STM32 | 请求开门 |
| 0x02 | STM32→APP | 开门成功(回传开锁时间戳) |
| 0x03 | STM32→APP | 密码错误 |
| 0x04 | APP→STM32 | 查询门锁状态 |
| 0x05 | STM32→APP | 门锁状态反馈(0=锁定,1=解锁) |
APP端我用的是经典的Android蓝牙串口开发方案——BluetoothAdapter+BluetoothSocket,UUID走标准的SPP服务UUID00001101-0000-1000-8000-00805F9B34FB。如果你不想从零写原生APP,可以用MIT App Inventor或E4A这类图形化平台拖一个按钮出来,逻辑就是"点击按钮→建立蓝牙连接→发送0xAA 0x01 0x00 0x00 0xAB"。实际测试下来,图形化平台的APP稳定性确实不如原生,但演示和毕设完全够用。
有个细节必须提醒:APP重连时一定要主动断开旧的Socket。Android的蓝牙连接很"轴",你上次连接没有正常close,下次连接大概率报错。我在APP里每次连接前都强制做一次bluetoothSocket.close(),实测可以解决90%的"连不上"问题。
3.4 人脸识别模块:K210方案与代码衔接
人脸识别是整个项目里软件最重的一块,但好消息是大部分算力工作不在STM32上跑。我的方案是K210跑MaixPy固件,内置的人脸识别使用的是KPU硬件加速器,模型是face_detect(检测)+face_recognize(识别)。K210端的代码逻辑大概是:
import sensor, image, lcd, time from Maix import KPU import ustruct # 初始化摄像头、LCD、KPU加载人脸检测模型 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.run(1) kpu = KPU() kpu.load_kmodel("/sd/face_detect.kmodel") kpu2 = KPU() kpu2.load_kmodel("/sd/face_recognize.kmodel") features = [] # 已注册的人脸特征 while True: img = sensor.snapshot() # 检测人脸框 faces = kpu.run_with_output(img) # 对每个检测到的人脸计算特征,和features比对 # 相似度超过阈值,通过UART发送 'C' 给STM32 # 相似度不足,发送 'F' 给STM32实际操作中,先让K210巡游采集5张人脸特征存成数组,然后循环识别。K210通过UART发给STM32的数据很简单——一个字节就够了:'O'代表识别通过,'X'代表不通过。STM32那边用USART3接收中断,收到'O'就执行开锁。
这里有个关键调参点:K210输出的相似度阈值。默认0.6左右时,有时候两个人长得比较像会误判;调到0.8以上,又会出现识别不出来的情况。我实测下来,在光线均匀的室内环境,0.72是一个比较靠谱的阈值。如果你的门禁装在有背光的门口,建议代码里加一个简单的光照补偿——先统计画面亮度均值,低于阈值就提示"光线不足",别硬识别。
3.5 主程序状态机设计
最后是整个系统怎么"转"起来的。我用了最简单的状态机模型,把门禁系统分成四个状态:STATE_IDLE(待机)、STATE_CHECK(验证中)、STATE_OPEN(开锁中)、STATE_LOCKED(锁定中)。主循环里不断轮询四个输入源(键盘、RC522、蓝牙、K210),一旦任一输入源触发了"验证请求",就进入STATE_CHECK对应分支处理,验证成功跳到STATE_OPEN,这期间其他输入源请求会被丢弃。
while (1) { switch (door_state) { case STATE_IDLE: // 轮询四种输入源 if (Key_Scan() != NO_KEY) door_state = STATE_CHECK_PWD; if (RFID_Check()) door_state = STATE_CHECK_RFID; if (BLE_ReceiveFrame()) door_state = STATE_CHECK_BLE; if (Face_ReceiveFlag()) door_state = STATE_CHECK_FACE; break; case STATE_CHECK_PWD: // 密码校验逻辑,通过则STATE_OPEN,失败则回IDLE break; // ... 其他状态类似 case STATE_OPEN: Servo_Open(); delay_ms(5000); Servo_Close(); door_state = STATE_IDLE; break; } }这个状态机看着简单,但它解决了多模态系统最大的"资源竞争"问题。有人同时刷卡又按密码,系统只处理最先触发的那个,不会出现舵机乱转的情况。如果你后续要加语音播报、加远程日志上报,也是往这个状态机里加分支,结构不会乱。
4. 联调踩坑记:那些我替你踩过的坑
4.1 高频问题速查表
实际调试过程中,我遇到的问题远不止上面提到的那些。挑几个典型的整理成表格,方便你按图索骥:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| HC-05连不上手机 | 模块默认波特率和手机APP不一致,或模块处于AT模式 | 确认模块退出AT模式(上电前不要按住按键),用38400重试 |
| RC522读卡距离只有1cm | 天线线圈信号衰减或供电不足 | 检查SPI线是否过长(<20cm),天线区域远离金属和杜邦线 |
| 人脸识别经常识别失败 | 光线变化大,阈值固定 | 加光照补偿,阈值动态调整,或换更高清的OV5640摄像头 |
| 舵机转动时MCU复位 | 舵机电流拉低电源电压 | 舵机独立供电,共地,电源入口加大电容 |
| OLED间歇性花屏 | I2C线太长、上拉电阻没接 | 缩短I2C线,板载OLED模块需确认是否有上拉电阻 |
| 蓝牙指令时好时坏 | 帧校验不严,出现粘包 | 严格校验帧头和校验和,接收用环形缓冲区 |
| K210上电后一直红屏 | 供电电流不足导致反复重启 | 换2A以上的5V适配器,T型口供电时别用劣质数据线 |
4.2 三个让我印象最深的排障过程
第一个坑是蓝牙粘包问题。APP发送的字节流如果连续帧间隔太短,STM32端可能一次中断收到两帧,导致第二帧校验失败。排查方式是在串口调试助手里连续发100帧数据看丢包率,最后用环形缓冲区接收、主循环里统一解析解决。这个经验对以后做任何串口通信项目都适用。
第二个坑是K210的人脸误检。一开始把检测阈值设得很低,结果K210把实验室门口挂的消防面具当成人,门自己开了。后来在代码里加了"连续3帧检测到人脸且相似度达标才触发"的防抖逻辑,误报率才降下来。这个教训比任何教程都深刻——做门禁,误开门比打不开门更吓人。
第三个坑是舵机机械限位。SG90舵机的转动范围只有0~180度,而我用的锁舌需要转90度左右。刚开始没设置PWM占空比的上下限,结果舵机一上电就狂转到物理极限位置,咔咔响,差点把齿轮打坏。后来在软件里限制了PWM脉宽范围(0.5ms~2.5ms对应0~180度),同时舵机摇臂上加了物理限位块,双保险。
4.3 联调顺序的建议:先通再全
最后给一个联调顺序的实战建议。很多人习惯把所有模块都焊好、全部初始化,一次性上电调试,结果出问题完全不知道从哪排查。我的做法是分层联调,先通再全:
- 先单独调密码锁:矩阵键盘+OLED显示+舵机。把整个"输入到执行"的闭环打通。
- 再加RFID:验证刷卡能开锁,白名单注册、注销功能正常。
- 再加蓝牙:在密码锁和RFID都正常的前提下,加上蓝牙指令控制,验证UART通信和协议解析。
- 最后加人脸识别:因为K210的调试需要串口监视器,如果一上来就一起跑,K210串口输出的调试信息和蓝牙的USART容易混淆。
这样每加一个模块,系统仍然处在"基本可用"的状态,出了问题可以精准定位到新加模块上。
写在最后的一点体会
这个项目做完,我总结出一个做多模态嵌入式系统的核心心得:硬件选型决定上限,软件架构决定下限。如果一开始拍脑袋把OpenCV跑在树莓派上、把RFID和蓝牙全堆在一块电源上,后面可能有一半时间都在跟硬件打架。而把"人脸识别放K210、控制逻辑放STM32"这个架构想清楚之后,每个模块的调试都是独立完成的,系统集成反而变得很顺。
另外一个小技巧分享给你:在每个模块正常工作的状态下,把串口调试输出加上模块单词的错误码(比如[RFID] Read failed、[BLE] Checksum error),调试时一眼就能看出问题出在哪个环节。这个小习惯在毕业设计答辩时也很加分。
这个项目后续还能往几个方向扩展:加一个ESP8266模块做云端日志上报,密码验证记录和刷卡记录推送到钉钉/企业微信;增加TFT-LCD触摸屏菜单,把系统参数设置界面化;或者把K210替换成更强的边缘AI模组,提升复杂光线下的识别鲁棒性。如果你正好在做类似的项目,欢迎多交流,共同把这个方案磨得更完善。
本文还有配套的精品资源,点击获取