1. 项目缘起与整体方案拆解
1.1 为什么选STM32做门禁主控
做智能门禁这个想法,最早来自一个很实际的需求:公司小办公室的机械锁太麻烦,钥匙配来配去,谁离职了还得换锁芯。市面上成品门禁动辄上千,功能还未必贴合自己的使用习惯。于是琢磨着自己搭一套,成本控制在两百块以内,功能上要支持密码开锁、指纹开锁,再加一个手机蓝牙近场开锁作为备用方案。
主控芯片选型上,我几乎没怎么犹豫就定了STM32。原因很直接:第一,GPIO资源丰富,驱动电磁锁、继电器、蜂鸣器、OLED屏幕这些外设绰绰有余;第二,UART接口多,指纹模块和蓝牙模块可以各占一个,互不干扰;第三,Flash容量足够存几百条指纹特征和密码记录;第四,生态成熟,HAL库和标准库的资料遍地都是,遇到问题搜一下基本能找到答案。具体型号我选的是STM32F103C8T6,也就是常说的“蓝板”,48引脚,72MHz主频,64KB Flash,20KB RAM,对于门禁这种控制逻辑不算复杂的场景完全够用,关键是价格便宜,一片才十块钱出头。
这里插一句选型心得:很多人一上来就想着用F4系列或者带网络功能的型号,觉得性能冗余更保险。但实际做下来,门禁系统的瓶颈根本不在主控算力,而在指纹识别算法的响应速度和电磁锁的机械延迟。F103的算力处理这些任务绰绰有余,多花的钱不如投在指纹模块和电源质量上。
1.2 三种开锁方式的取舍逻辑
密码、指纹、蓝牙这三种方式,不是随便凑数的,它们各自覆盖了不同的使用场景。
密码开锁是最基础的兜底方案。指纹识别虽然方便,但手指脱皮、沾水、有污渍的时候识别率会明显下降,这时候密码就是可靠的备选。而且密码可以临时告诉访客,不需要录入指纹。我设计的是6位数字密码,支持修改,存储在STM32的Flash模拟EEPROM区域,掉电不丢失。
指纹开锁是日常使用频率最高的方式。我选的是AS608光学指纹模块,通过UART与STM32通信。这个模块自带指纹录入、比对、搜索功能,STM32只需要发送指令包和解析返回包就行,不需要自己跑指纹算法。AS608可以存储最多300枚指纹,对小型办公场景完全够用。它的识别速度在1秒以内,误识率低于0.001%,这个指标在实际使用中表现很稳。
蓝牙开锁是给手机准备的。用的是HC-05串口蓝牙模块,手机端配一个简单的蓝牙串口助手APP,发送特定指令就能开锁。这个功能听起来有点“极客”,但实际用起来很顺手——比如双手拎着东西走到门口,掏手机按一下比按密码快得多。HC-05配置成从机模式,STM32通过UART接收数据,解析到约定指令后驱动电磁锁动作。
三种方式的优先级逻辑是这样的:指纹优先,因为最快;密码次之,作为兜底;蓝牙最后,作为辅助。但实际代码里三者是并行的,谁先触发就响应谁,不存在排队等待的问题。
1.3 系统整体架构与模块划分
整个系统按功能划分为五个模块:主控模块、指纹识别模块、蓝牙通信模块、人机交互模块、电控锁驱动模块。
主控模块以STM32F103C8T6为核心,负责协调各模块工作、管理用户数据、执行开锁逻辑。指纹模块通过UART2与主控通信,波特率57600。蓝牙模块通过UART3与主控通信,波特率9600。人机交互模块包括一个0.96寸OLED显示屏(I2C接口)和一个4x4矩阵键盘,OLED用于显示操作提示和系统状态,键盘用于输入密码和功能选择。电控锁驱动模块用一个5V继电器控制12V电磁锁的通断,继电器线圈由STM32的GPIO通过三极管驱动,续流二极管防止反向电动势损坏主控。
电源部分需要特别注意。整个系统需要12V(电磁锁)、5V(继电器、指纹模块)、3.3V(STM32、OLED)三种电压。我用一个12V/2A的直流电源适配器输入,经过LM2596降压到5V,再经过AMS1117-3.3降压到3.3V。这里有个坑:电磁锁在吸合瞬间电流会冲到1A以上,如果电源功率不够,会导致STM32复位。所以电源适配器一定要选2A以上的,并且在12V输入端并一个大电容缓冲。
2. 核心硬件选型与电路设计要点
2.1 主控最小系统与引脚分配
STM32F103C8T6的最小系统包括晶振电路(8MHz外部晶振+32.768kHz RTC晶振)、复位电路、BOOT选择电路、SWD调试接口。这些是标准配置,照着官方参考设计画就行。重点说一下引脚分配,因为外设多,引脚规划不好后面改代码会很痛苦。
我把PA0-PA7分配给4x4矩阵键盘的行列扫描,PB0-PB1接OLED的I2C(SCL、SDA),PA9-PA10用作UART1(预留调试打印),PA2-PA3用作UART2(接指纹模块),PB10-PB11用作UART3(接蓝牙模块),PC13用作继电器控制输出,PB5用作蜂鸣器输出,PB6-PB7用作OLED的I2C备用引脚(实际用PB0-PB1)。
这里有个细节:PC13是STM32F103C8T6上唯一直接连接到用户LED的引脚,但它的驱动能力有限,只能灌电流不能拉电流,所以继电器控制我换到了PB12,通过NPN三极管S8050驱动继电器线圈。基极串联1kΩ电阻,发射极接地,集电极接继电器线圈一端,线圈另一端接5V,线圈两端反并联一个1N4148续流二极管。
2.2 指纹模块AS608的接线与配置
AS608指纹模块有6个引脚:VCC(3.3V)、GND、TX、RX、WAKE、TOUCH。实际接线只需要VCC、GND、TX、RX四根。注意AS608的TX要接STM32的RX(PA3),AS608的RX要接STM32的TX(PA2),交叉连接。
模块出厂默认波特率是57600,地址是0xFFFFFFFF。如果你买的是二手模块或者之前被配置过,可能需要先恢复出厂设置。恢复方法:把WAKE引脚拉高3秒以上,模块会自动恢复默认波特率和地址。这个技巧在模块“失联”的时候特别管用,我遇到过好几次因为波特率被改乱导致通信失败的情况。
AS608的供电必须稳定在3.3V,电流需求峰值约120mA。如果直接用STM32板子上的3.3V输出,在大电流动作时可能会拉低电压导致模块重启。我的做法是单独用一个AMS1117-3.3给指纹模块供电,输入端并100μF电解电容和0.1μF陶瓷电容滤波。
2.3 蓝牙模块HC-05的配对与指令集
HC-05是经典蓝牙模块,支持主从模式切换。门禁系统里配置成从机模式,等待手机连接。配置方法:按住模块上的按键上电,进入AT指令模式,波特率固定9600。发送AT+ROLE=0设置为从机,AT+CMODE=1允许任意地址连接,AT+PSWD="1234"设置配对密码,AT+UART=9600,0,0设置通信波特率。
配对密码建议改掉默认的1234,虽然蓝牙通信距离只有10米左右,但改一下更稳妥。手机端用“蓝牙串口助手”这类APP,搜索到HC-05后输入配对密码连接,然后发送约定指令。我定义的指令是“OPEN#”,STM32收到后解析并执行开锁。
HC-05有个常见问题:模块在未连接状态下电流约30mA,连接后约20mA,但某些批次的模块在连接瞬间会有较大电流波动。如果发现蓝牙连接时STM32复位,可以在HC-05的VCC和GND之间并一个100μF电容。
2.4 电磁锁与继电器的驱动电路
电磁锁选的是12V常闭型,断电时锁舌弹出(关门),通电时锁舌缩回(开门)。这意味着如果停电,门是锁着的,符合安全逻辑。继电器用5V驱动的SRD-05VDC-SL-C,触点容量10A/250VAC,驱动12V/1A的电磁锁绰绰有余。
驱动电路的关键是续流二极管。继电器线圈在断电瞬间会产生反向电动势,峰值可能达到几十伏,不处理的话会击穿三极管甚至干扰STM32。1N4148的反向耐压是100V,恢复时间4ns,完全够用。三极管用S8050,集电极电流最大500mA,继电器线圈电流约70mA,余量充足。
电磁锁的电源线和信号线要分开走。我一开始把12V电源线和指纹模块的信号线捆在一起,结果指纹识别率明显下降。后来把电源线单独走一边,信号线走另一边,问题就解决了。这个经验在很多教程里不会提,但实际布线时非常关键。
3. 软件架构与核心逻辑实现
3.1 主程序状态机设计
门禁系统的软件用状态机来组织最清晰。我定义了五个状态:IDLE(空闲)、INPUT_PWD(密码输入)、VERIFY_FP(指纹验证)、VERIFY_BT(蓝牙验证)、UNLOCK(开锁)。
IDLE状态下,OLED显示“请刷卡/输入密码/按指纹”,键盘扫描和指纹模块、蓝牙模块都在监听。键盘有按键按下进入INPUT_PWD,指纹模块检测到手指进入VERIFY_FP,蓝牙收到数据进入VERIFY_BT。验证通过后进入UNLOCK状态,继电器吸合3秒后自动断开,回到IDLE。验证失败则蜂鸣器短鸣两声,回到IDLE。
状态机的优势在于逻辑清晰,每个状态只关心自己的输入和输出,不会出现“同时处理多个事件”的混乱。比如在密码输入过程中,指纹模块如果检测到手指,我选择忽略,因为用户正在输入密码,不应该被指纹打断。这种优先级逻辑在状态机里很容易实现。
3.2 密码存储与验证的Flash模拟EEPROM
STM32F103C8T6没有真正的EEPROM,但可以用Flash模拟。我划出最后一页Flash(地址0x0801FC00,大小1KB)作为数据存储区,存密码和指纹ID映射表。
写Flash之前必须先擦除,而擦除的最小单位是一页(1KB)。所以不能每次改密码都擦整页,那样会缩短Flash寿命。我的做法是:在RAM里维护一个密码缓冲区,修改密码时先更新缓冲区,然后擦除整页,再把缓冲区写回去。密码修改频率很低,一年可能就改几次,对Flash寿命影响可以忽略。
读密码就简单了,直接指针访问对应地址就行。这里注意:Flash读取不需要解锁,但写入和擦除需要先调用HAL_FLASH_Unlock(),操作完再HAL_FLASH_Lock()。忘记解锁是新手常犯的错误,编译不报错但运行没效果。
密码验证逻辑:用户输入6位数字,与存储的密码逐位比较。如果连续错误3次,锁定键盘1分钟,期间OLED显示“尝试次数过多,请稍后再试”。这个防暴力破解机制在实际使用中很有必要,尤其是门禁这种安全相关的设备。
3.3 AS608指纹模块的指令包解析
AS608的通信协议是“包头+地址+包标识+包长度+指令+参数+校验和”。包头固定为0xEF01,地址默认0xFFFFFFFF,包标识0x01表示命令包,0x02表示数据包,0x08表示结束包。
以“搜索指纹”指令为例,发送的指令包是:EF01 FFFFFFFF 01 0003 01 0005。其中01是搜索指令码,0005是校验和(从包标识到参数的所有字节累加)。模块返回的确认包是:EF01 FFFFFFFF 07 0003 00 000A,其中07表示确认包,00表示操作成功,000A是校验和。如果搜索到匹配指纹,还会返回一个数据包,包含指纹ID。
解析返回包的时候要注意:模块返回的数据可能分多次到达,不能假设一次UART接收中断就能收完。我的做法是在UART中断里把数据存入环形缓冲区,主循环里检查缓冲区是否有完整包(根据包长度字段判断),有就解析,没有就继续等。这个环形缓冲区的设计是稳定通信的关键,直接决定指纹识别会不会“时好时坏”。
3.4 蓝牙指令的接收与安全校验
HC-05透传模式下,手机发什么STM32就收什么。我定义的指令格式是“CMD:OPEN#”,其中“#”是结束符。STM32在UART3中断里逐字节接收,遇到“#”就认为一帧结束,然后解析指令内容。
安全校验方面,我加了一个简单的滚动码机制。手机端每次发送的指令包含一个随机数,STM32收到后检查这个随机数是否在最近10次使用过,如果重复则拒绝执行。这个机制能防止有人用蓝牙抓包工具重放指令。虽然HC-05的通信距离只有10米,但加一层校验成本很低,何乐而不为。
蓝牙模块的另一个坑是:HC-05在上电后需要几百毫秒初始化,如果STM32在这期间发送AT指令,模块可能不响应。我的做法是在初始化代码里加500ms延时,等模块稳定后再配置。这个延时看起来不起眼,但少了它蓝牙功能可能时灵时不灵。
4. 实操调试与问题排查实录
4.1 指纹模块通信失败的排查流程
指纹模块通信失败是最常见的问题,表现是STM32发送指令后收不到任何返回。排查步骤我总结了一个顺序:
第一步,检查接线。TX和RX是否交叉连接?VCC是否稳定在3.3V?用万用表量一下模块VCC对GND的电压,如果低于3.2V,模块可能工作不正常。
第二步,检查波特率。AS608默认57600,但如果你买的是“定制版”可能是9600或115200。用USB转TTL模块直接连电脑,用串口助手发送“EF01 FFFFFFFF 01 0003 01 0005”,看有没有返回。如果没有,尝试其他波特率。
第三步,检查指令包。校验和计算是否正确?包长度字段是否与实际参数长度一致?我遇到过因为包长度写错导致模块不响应的情况,排查了半天才发现是长度字段多算了一个字节。
第四步,检查电源。指纹模块在识别瞬间电流会冲到120mA,如果电源带不动,模块会重启。用示波器看VCC波形,如果有明显跌落,就需要换更大电流的LDO或者加电容。
4.2 电磁锁误动作与电源干扰处理
电磁锁误动作的表现是:没有开锁指令,锁自己“啪”一声吸合又断开。这个问题困扰了我两天,最后发现是电源干扰。
电磁锁的线圈在断电瞬间会产生反向电动势,通过电源线传导到STM32的供电轨,导致STM32复位或GPIO误触发。解决方法有三个:第一,在电磁锁两端反并联一个1N4007二极管,吸收反向电动势;第二,在继电器触点两端并一个RC吸收电路(100Ω+0.1μF);第三,STM32的电源输入端加LC滤波(10μH电感+100μF电容)。
我三个方法都用了,误动作彻底消失。这里要强调:电磁锁的续流二极管和继电器的续流二极管是两回事,前者保护电磁锁驱动电路,后者保护继电器驱动电路,两个都不能省。
4.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 指纹模块无响应 | 接线错误/波特率不对/电源不足 | 用USB转TTL直连电脑测试 | 交叉接线/改波特率/独立供电 |
| 蓝牙连接不上 | 配对密码错误/模块未进入AT模式 | 按住按键上电/检查手机配对记录 | 恢复默认设置/删除配对重连 |
| 电磁锁误动作 | 电源干扰/续流二极管缺失 | 示波器看电源波形 | 加续流二极管/LC滤波 |
| 密码掉电丢失 | Flash未正确写入 | 读Flash对应地址 | 检查解锁和擦除流程 |
| OLED不显示 | I2C地址错误/初始化失败 | 用I2C扫描工具找地址 | 改地址/检查上拉电阻 |
| 键盘按键不灵敏 | 上拉电阻缺失/扫描频率过低 | 万用表量按键通断 | 加10kΩ上拉/提高扫描频率 |
| 系统频繁复位 | 电源功率不足/看门狗触发 | 量12V输入电压 | 换2A以上电源/喂狗 |
4.4 几个容易被忽略的实操细节
第一个细节:STM32的BOOT0引脚必须接地,否则程序可能从系统存储器启动而不是Flash启动。我一开始忘了接,程序烧进去不运行,查了半天才发现是BOOT0悬空。
第二个细节:SWD调试接口的SWCLK和SWDIO线不要太长,最好在10cm以内。线太长会导致下载失败或者调试时断时续。如果必须延长,用屏蔽线并降低SWD时钟频率。
第三个细节:OLED屏幕的I2C上拉电阻。很多OLED模块自带4.7kΩ上拉,但如果你的模块没有,需要在SCL和SDA上各接一个4.7kΩ到3.3V。没有上拉电阻的话,I2C通信会不稳定,表现为屏幕偶尔花屏或完全不亮。
第四个细节:矩阵键盘的扫描频率。我一开始用10ms扫描一次,发现快速按键时偶尔漏检。后来改成5ms扫描一次,配合20ms的消抖延时,按键识别就很稳了。但扫描频率也不能太高,否则CPU占用率上去了,会影响指纹模块的响应速度。
5. 系统优化与功能扩展思路
5.1 提升指纹识别速度的几点尝试
AS608的默认识别速度在1秒左右,实际使用中感觉还是有点慢。我尝试了几个优化方向:
一是降低指纹模块的 security level。AS608的安全等级从1到5可调,等级越高识别越严格但速度越慢。默认是3,我改成2之后识别速度明显提升,误识率也没有明显增加。对于门禁这种场景,安全等级2完全够用。
二是优化UART通信。把指纹模块的波特率从57600提高到115200,指令传输时间缩短一半。但要注意:提高波特率后通信误码率会上升,需要确保接线质量。我实测在15cm以内的杜邦线下,115200很稳定。
三是预加载指纹模板。AS608支持将常用指纹模板加载到模块的RAM中,搜索时优先比对RAM中的模板。但这个功能需要额外发送加载指令,对于只有几十个用户的场景,提升效果有限。
5.2 增加刷卡功能的硬件扩展
指纹和密码都有了,但有时候手上拿着东西不方便按指纹,刷卡就成了一个很自然的补充。我选的是RC522 RFID模块,支持Mifare S50卡,通过SPI接口与STM32通信。
RC522的接线是:SDA接PA4(片选),SCK接PA5,MOSI接PA7,MISO接PA6,RST接PB0。注意RC522的工作电压是3.3V,不能接5V。SPI通信速率可以设到10MHz,读卡时间在100ms以内。
软件上,RC522的驱动库很多,我用的是一份开源的MFRC522库,移植到STM32上只需要改几个GPIO定义。读到的卡号存在Flash里,与指纹ID做映射。刷卡验证的逻辑和指纹类似,验证通过就开锁。
5.3 低功耗模式的可行性分析
门禁系统通常是常供电的,但如果想用电池供电,就需要考虑低功耗。STM32F103C8T6支持睡眠、停止、待机三种低功耗模式。待机模式功耗最低,约2μA,但唤醒后相当于复位,所有状态丢失。停止模式功耗约20μA,唤醒后从停止处继续执行,比较适合门禁场景。
具体做法:在IDLE状态下,如果没有按键、没有指纹、没有蓝牙数据,就让STM32进入停止模式。指纹模块的TOUCH引脚接到STM32的外部中断引脚,有人触摸时产生中断唤醒STM32。键盘按键通过二极管“或”到一起,接到另一个外部中断引脚。蓝牙模块的数据接收引脚也接到外部中断。
但这里有个问题:HC-05在停止模式下仍然耗电约30mA,整体功耗降不下来。如果要真正低功耗,需要换用BLE模块(如JDY-31),BLE在未连接时功耗只有几百微安。不过BLE的透传协议和HC-05不同,需要改手机端APP,这是另一个工作量了。
5.4 数据记录与审计功能
门禁系统如果能记录每次开锁的时间、方式和用户ID,对安全管理很有帮助。STM32F103C8T6的Flash有64KB,划出4KB存日志,每条日志8字节(时间戳4字节+用户ID 2字节+方式1字节+校验1字节),可以存512条。
时间戳用RTC提供,STM32F103C8T6内部有RTC,配32.768kHz晶振就能走时。但内部RTC精度一般,一天可能差几秒。如果要精确时间,可以加一个DS3231外部RTC模块,精度在±2ppm以内,一年误差不超过1分钟。
日志的读取可以通过蓝牙发送到手机,或者加一个SD卡模块存成文本文件。SD卡模块用SPI接口,与RC522共用SPI总线,通过不同的片选引脚区分。这个功能我还在调试中,主要问题是SD卡的FATFS文件系统占用Flash较多,需要裁剪。
6. 个人实操体会与避坑建议
6.1 电源质量决定系统稳定性
这个项目做下来,最大的体会就是:电源质量比主控性能重要得多。我一开始用一个旧的5V/1A手机充电器供电,结果指纹识别十次有三次失败,蓝牙连接也时断时续。换了一个12V/2A的工业电源适配器,加上LM2596和AMS1117两级稳压,所有问题都消失了。
具体来说,指纹模块和蓝牙模块对电源纹波很敏感。AMS1117的电源抑制比在低频段约60dB,但高频段只有20dB左右。如果前级开关电源的纹波较大,后级LDO滤不干净,就会影响模块工作。我的做法是在LM2596输出端加一个LC滤波(10μH+100μF),再进AMS1117,纹波从50mV降到5mV以内。
6.2 杜邦线的可靠性问题
调试阶段用杜邦线接线很方便,但杜邦线的接触电阻不稳定,尤其是经过几次插拔之后。我遇到过指纹模块突然不响应,查了半天发现是杜邦线松了。后来所有连接都改成焊接,用排针和排母配合,可靠性大幅提升。
如果必须用杜邦线,建议用镀金针脚的,接触电阻小且抗氧化。另外,杜邦线不要走太长,尤其是UART和I2C信号线,超过20cm就容易受干扰。电源线可以长一些,但也要保证线径足够,12V/1A的电流至少用22AWG的线。
6.3 代码版本管理的重要性
这个项目代码量不大,但涉及多个模块的驱动和业务逻辑,改着改着就容易乱。我一开始没有用版本管理,结果改密码存储逻辑的时候把指纹验证的代码覆盖了,又得重写。后来用Git管理,每次改完提交一次,出问题可以回退,效率高很多。
建议用Git或者至少用文件夹按日期备份。STM32的工程文件比较多,备份的时候把整个工程目录打包,不要只备份main.c。另外,HAL库的版本也要记录,不同版本的HAL库API可能有差异,换版本后编译不过的情况很常见。
6.4 关于扩展性的思考
这个门禁系统目前支持密码、指纹、蓝牙三种方式,后续还可以扩展NFC刷卡、人脸识别、WiFi远程控制等。但每加一种方式,代码复杂度和硬件成本都会增加。我的建议是先把基础功能做稳定,再考虑扩展。
如果要做WiFi远程控制,ESP8266或ESP32是常见选择,通过UART与STM32通信。但WiFi模块的功耗较大,且需要处理网络协议栈,对STM32的RAM是个考验。更合理的方案是用ESP32做主控,STM32只做外设扩展,这样分工更清晰。
最后分享一个调试小技巧:在main循环里加一个LED闪烁,周期1秒。如果程序跑飞了,LED就不闪了,一眼就能看出问题。这个习惯帮我省了很多调试时间,尤其是排查硬件故障和软件死循环的时候。