ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于STM32的智能门禁系统程序设计:从状态机到Flash存储

基于STM32的智能门禁系统程序设计:从状态机到Flash存储 简介面向嵌入式开发者与电子类毕设学生STM32智能门锁硬件端工程实现了密码与指纹解锁、虚位密码、超时锁定以及通过Wi-Fi/MQTT接入阿里云平台的完整逻辑。资源包共135个文件以75个.h头文件和46个.c源文件为核心覆盖HAL库外设驱动、FatFs文件系统、中文字库、SPI/UART/SD卡等模块另有5个json配置文件、少量txt说明、png电路或界面截图及md文档压缩包整体约4.49MB。当前已有325人浏览学习。工程将密码与指纹特征加密存储于外部Flash中并支持门锁状态上报、开门通知、钥匙修改通知和门未锁好提醒等事件推送便于理解嵌入式端如何与阿里云物联网平台完成双向交互。需要留意的是压缩包仅包含门锁硬件端程序安卓应用与云端配置不在其内对正在准备毕业设计或开发实际门锁产品的开发者可直接复用其屏幕交互、错误提示与自动锁屏机制有效缩短固件开发与联调周期。1. 把“智能门禁”拆成程序才看得见 stm32 真正要做的事门禁硬件的单价现在已经被各种模块厂商压得很低刷卡头、矩阵键盘、继电器输出焊在一起不过十几根飞线。真正让“基于 stm32 的智能门禁系统程序”从实验室里的“能开门”变成现场挂机半年不出错的从来不是硬件选型而是程序里对输入可靠性、掉电行为和异常恢复的处理。矩阵键盘按一次进来两个数字、RC522 偶尔读卡超时、继电器吸合的一瞬间单片机复位这三类问题消耗的调试时间比开门逻辑本身多得多。这套程序的本质是用状态机把输入验证、锁控输出、异常恢复串成一个闭环用统一的定时节拍驱动扫描和去抖再用片内 Flash 把密码、白名单和开锁日志留住。读卡、按键、门磁这些输入可以被任意顺序打断但程序行为必须始终聚焦在“空闲、校验、开锁、报警”这几个状态上。做基于 stm32 的毕业设计或者在一个 stm32 项目里新开一个门禁分支都值得先把这套骨架搭正确再往上面加功能。下面按我平时落地的顺序展开先定引脚和状态机再写双因子鉴权链随后处理掉电保存与锁控驱动最后用三件工具把程序验证到可以交付的状态。2. 先定输入输出再写状态机stm32智能门禁的系统骨架2.1 引脚分配与最小电路哪些脚不能省我一般先用 CubeMX 把时钟、GPIO 和外设排好再动飞线。控制器用 STM32F103C8T6 就足够64KB Flash 放几百条白名单和一大段日志绰绰有余。RC522 走 SPI1占用 PA5/PA6/PA7片选和复位单独分 PA4、PA3不与其它外设共享矩阵键盘行列各四个占 PB0 到 PB7继电器用 PB8 输出门磁接 PB9UART1 的 PA9/PA10 留给上位机管理。这个分配不是唯一解但有几个引脚一定要慎重PA13/PA14 是 SWD 调试口PB3/PB4 默认复用 JTAG把它们当普通 GPIO 用不是不行代价是下载调试变慢严重时烧录器直接连接失败。外部晶振按 8MHz 配两个 22pF 负载电容有条件的用负载电容公式 CL(C1×C2)/(C1C2)Cstray 估算Cstray 按 3 到 5pF 算保证等效负载落在晶振手册范围内。这个参数偏了初期仪表看不出问题跑几个月后 RTC 时间戳和定时器节拍会慢慢漂。另一个隐藏点用 CubeMX 后期把芯片型号从 C8T6 换到 RBT6 很方便但迁移后必须重新核对所有引脚的复用功能不能只改型号就编译下载。功能块接口推荐引脚关键说明RC522SPI1 CS/RSTPA5/PA6/PA7、PA4/PA3SPI 时钟降到 1Mbps 左右更稳矩阵键盘4 行输出 / 4 列输入PB0~PB3、PB4~PB7行推挽、列上拉禁止列下拉继电器开锁GPIO 推挽输出PB8经 ULN2003 或三极管驱动线圈并联续流二极管门磁检测GPIO 上拉输入PB9门体开合检测配合软件滤波调试与管理口UART1PA9/PA10日志输出、白名单管理、协议回放2.2 用状态机代替散排逻辑运行时序才能一屏讲清门禁的输入种类并不多键盘按下、刷卡、门磁动作、超时但如果把这些事件用 if 散落在各自的回调里验证顺序一乱程序行为立刻不可预测。我通常把系统限制成四个状态空闲、校验、开锁、报警每个状态只响应自己该响应的事件非法事件直接丢弃。状态机不仅让代码可读更重要的是让“锁定时拒绝一切输入”这种安全策略有了明确的落地位置。调度不建议上 RTOS门禁任务的实时性要求只有毫秒级超级循环加定时器节拍足够还少一层任务切换的心智负担。主循环按 10ms 周期调用一次状态机SysTick 用 HAL_GetTick() 提供时间基准需要精确时序的蜂鸣器鸣叫再用 TIM 定时器单独控制。核心状态机如下typedef enum { ST_IDLE, ST_VERIFY, ST_UNLOCK, ST_ALARM } door_state_t; static door_state_t g_state ST_IDLE; static uint32_t g_enter_tick 0; static uint8_t g_fail_count 0; void door_fsm_run(void) { switch (g_state) { case ST_IDLE: // 只有空闲态才接受新的鉴权输入 if (key_event_pending() || rfid_event_pending()) { g_state ST_VERIFY; g_enter_tick HAL_GetTick(); set_panel_led(LED_VERIFY, ON); } break; case ST_VERIFY: if (auth_result AUTH_PASS) { g_fail_count 0; g_state ST_UNLOCK; g_enter_tick HAL_GetTick(); set_relay(RELAY_ON); } else if (auth_result AUTH_FAIL g_fail_count 3) { // 连续失败 3 次进入报警锁定 g_state ST_ALARM; g_enter_tick HAL_GetTick(); } else if (HAL_GetTick() - g_enter_tick VERIFY_TIMEOUT_MS) { g_state ST_IDLE; set_panel_led(LED_VERIFY, OFF); } break; case ST_UNLOCK: // 开锁持续时间到后自动回锁 if (HAL_GetTick() - g_enter_tick UNLOCK_KEEP_MS) { set_relay(RELAY_OFF); g_state ST_IDLE; } break; case ST_ALARM: // 锁定 30 秒期间键盘和刷卡一律不收 if (HAL_GetTick() - g_enter_tick LOCK_DURATION_MS) { g_fail_count 0; g_state ST_IDLE; } break; } }这段代码的关键在于状态迁移记录 g_enter_tick所有超时判定都基于它与当前 HAL_GetTick 的差值不需要为每个状态单独开定时器。VERIFY_TIMEOUT_MS 通常取 5000UNLOCK_KEEP_MS 取 3000 到 5000LOCK_DURATION_MS 取 30000这些宏集中放在配置头文件里现场交付时只需要调这一处。注意 door_fsm_run 只能放在主循环轮询不能进中断中断回调只负责置事件标志鉴权和状态迁移全部留在主循环完成避免 SPI 读卡和 Flash 写入在中断上下文里卡死。3. 刷卡与密码并存的双因子鉴权stm32门禁程序的完整鉴权链3.1 矩阵键盘扫键与按键去抖先解决“按一次进来两字”矩阵键盘电路设计本身不复杂四行四列的机械开关本质是一堆抖动源。没有软硬件去抖时一次按键经常被采样成多个脉冲轻则密码输入重复重则直接触发重试锁定。硬件上可以给每个按键并联 104 电容但更可靠的是软件处理扫描到低电平后不立即确认间隔 10ms 连续扫描两次两次结果一致才上报按键。门禁键盘不追求游戏手柄的微秒级响应20ms 的处理延迟完全无感。扫描代码用行推挽、列上拉的标准接法const uint8_t row_pin[4] { GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3 }; const uint16_t col_mask GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; uint8_t key_scan(void) { for (uint8_t r 0; r 4; r) { // 先把四行全部拉高再把当前行拉低 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, row_pin[r], GPIO_PIN_RESET); // 读取列口取反后获得按下位 uint16_t raw (~HAL_GPIO_ReadPin(GPIOB, col_mask)) col_mask; if (raw) { for (uint8_t c 0; c 4; c) { if (raw (1U (4 c))) { return r * 4 c; // 返回 0~15 的键值 } } } } return 0xFF; // 无按键 }这里先置高所有行线再把当前扫描行拉低目的是避免上一行扫描残留的电平影响本次判断。列口配置为上拉输入无键按下时读到全高取反后为 0有键闭合时对应列被拉低取反后对应位置 1。raw 与 col_mask 做位与排除掉行口的干扰。实际工程中不把去抖逻辑揉进 key_scan而是在主循环里每 10ms 调用一次连续两次返回相同键值才向状态机投递按键事件返回值 0xFF 直接过滤。3.2 RC522 过 SPI 读卡号先验证版本号再谈寻卡RC522 和 stm32 的通信在 HAL 库环境下我习惯拆成三步第一步只验证总线第二步再寻卡第三步才是读 UID 和鉴权。网上不少例程把三步全包在一个大函数里一旦读不到卡根本分不清是 SPI 没通还是天线场没建立。验证方式很直接读取 RC522 的版本寄存器地址 0x37读回常见值是 0x92 或 0x91。如果读回 0xFF 或 0x00先查 CS 片选是否悬浮、SPI 极性和相位是否配置为模式 0而不是急着改寻卡重试次数。CubeMX 里配置 SPI1 时分频系数往大了调实践下来 1Mbps 左右的时钟在飞线环境下最稳RC522 模块规格虽然支持更高速度但杜邦线和模块插座带来的寄生电容会把波形边沿拖垮。防碰撞和选卡流程走完后拿到 4 字节 UID白名单匹配用 memcmp这里有个细节memcmp 的第三个参数必须固定传 4不能依赖外部传入的长度字段防止恶意构造的卡号长度绕过比较。每步流程的状态码通过 UART 打印现场调试时串口能看到“寻卡成功、防碰撞成功、选卡成功”到哪一步断掉。RC522 状态表示含义处理建议0x26 应答成功检测到卡片进入射频场继续防碰撞流程防碰撞成功已取得 4 字节 UID进入选卡和验证超时错误卡片未在场或天线未启间隔 100ms 重新检测位冲突错误多张卡同时靠近延时几毫秒后重试3.3 鉴权结果处理三次失败进报警防止暴力枚举鉴权链最容易犯的设计错误是“密码对就开门密码错就提示”缺少失败后的抑制策略。6 位数字密码空间只有一百万种交给暴力脚本按每秒十次的节奏试一天之内必然被跑穿门禁程序必须内置失败计数和锁定窗口。上面的状态机里已经预留了 g_fail_count鉴权失败一次就加一连续达到三次直接进入 ST_ALARM 状态这个状态下矩阵键盘和 RC522 全部停止响应持续 30 秒。只有管理员串口指令能提前解除锁定这条口令在程序里单独写死不能与用户密码共用一套存储结构。不管是键盘输入的密码还是刷卡得到的 UID最后都收敛成同一个鉴权接口例如 auth_check_uid(uint8_t *uid, uint8_t len) 和 auth_check_password(uint8_t *buf, uint8_t len)。两个函数内部都对长度做上限校验密码长度不足四位或者超过八位直接返回失败不进入比较逻辑。卡片鉴权还有一个常见坑多张卡放到读卡器上时RC522 会返回位冲突程序容易误判成非法卡正确做法是原卡保持不动、延迟重试而不是立刻累加失败次数。4. 掉电保存与锁控输出stm32门禁里最容易翻车的三个细节4.1 密码、白名单、事件日志写进片内 Flash授权数据必须掉电保持。外部 AT24C02 是一种选择但 F103 片内 Flash 在典型门禁项目中往往还空着一大半直接用 Flash 能省一路 I2C也少一个器件失效点。F103 的 Flash 是页擦除架构64KB 型号共 64 页、每页 1KB写入粒度是半字或字。存密码和白名单前先规划布局我的习惯是密码哈希单独占一页白名单从下一页开始顺序存放事件日志再往后放并做环形覆盖。任何整页擦除动作都要先把这页内容读进 RAM 缓冲区构造好新数据再擦除回写否则中途掉电整页变 0xFF。这里给出一个带备份思想的存储函数示例#define PIN_PAGE_0 0x0800C000 // 密码区主备份页 #define PIN_PAGE_1 0x0800C400 // 密码区副备份页 void flash_store_pin(uint32_t *hash, uint32_t len) { uint32_t page_err 0; FLASH_EraseInitTypeDef erase_cfg; HAL_FLASH_Unlock(); __disable_irq(); // 写 Flash 期间关中断 for (int copy 0; copy 2; copy) { uint32_t dest (copy 0) ? PIN_PAGE_0 : PIN_PAGE_1; erase_cfg.TypeErase FLASH_TYPEERASE_PAGES; erase_cfg.PageAddress dest; erase_cfg.NbPages 1; HAL_FLASHEx_Erase(erase_cfg, page_err); for (uint32_t i 0; i len; i 4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dest i, hash[i / 4]); } } __enable_irq(); HAL_FLASH_Lock(); }这段代码把同一份哈希写入两个物理页启动时分别读出并比对 CRC一页损坏就用另一页恢复。HAL_FLASH_Unlock 必须在擦写前调用HAL_FLASH_Lock 在结束后调用写 Flash 期间关闭中断是因为擦写操作需要占用指令总线中断服务程序里若有 Flash 访问会产生总线忙等待。密码不能明文存储至少做一次 FNV 或 CRC 混淆工程上可以升级为带盐的摘要但毕业设计阶段先保证“不是明文落盘”。存储方案擦写方式寿命与代价适用场景片内 Flash页擦除、字写入约 1 万次擦写零成本白名单变化不频繁的普通门禁AT24C02字节写、页写约 100 万次需 I2C日志频繁追加、需要高耐用W25Q64 外挂 SPI Flash扇区擦除约 10 万次成本低需要存大量开门记录4.2 继电器驱动与续流二极管一类典型的“吸合即复位”继电器吸合瞬间把 stm32 复位是门禁项目里最经典的玄学问题根因几乎都出在驱动电路上。GPIO 直接驱动继电器线圈电流不够是小事更大的问题是线圈断开瞬间会产生反向感应电动势电压尖峰可以直接打穿 GPIO 或让电源跌落。常规做法是 GPIO 先接三极管或 ULN2003 放大电流继电器线圈两端反向并联一只 1N4148 或 1N4007 续流二极管让反电动势在二极管和线圈的环路里消耗掉。电磁锁的功率更大最好连光耦一起上PC817 输入端接 GPIO输出端单独供电给继电器。这里有一个接线细节续流二极管方向不能接反阴极接继电器线圈正极、阳极接负极接反就变成短路路径继电器一上电就烧驱动管。锁控输出最好能有独立的 5V 电源轨不要从 stm32 的 3.3V LDO 后面取瞬时电流会拉低 MCU 供电电压。现场如果已经出现吸合复位的现象先用示波器抓继电器供电引脚在动作瞬间的跌落波形再检查续流管方向不要一上来就怀疑程序跑飞去加大喂狗频率。4.3 低功耗唤醒STOP 模式、门磁中断与电池电压检测电池供电门禁要把待机电流压下来F103 最常用的是 STOP 模式典型待机电流几十微安级别。进入 STOP 前把不用的外设时钟关闭把矩阵键盘的列线和门磁接成 EXTI 外部中断任意一路信号变化都可以唤醒。唤醒后先重新初始化时钟树再恢复外设注意 F103 从 STOP 退出后默认运行在 HSI 上如果主频是 72MHz必须重新配置 PLL否则定时器节拍和串口波特率全部错位表现为“唤醒后第一次按键失灵或者串口乱码”。低电量告警可以顺带用 ADC 做电阻分压后采样电池电压主循环里每 10ms 判断一次低于阈值时只允许开门三次并点亮欠压指示灯。ADC 这里不需要开 DMA门禁不采集连续波形单次转换足够省掉 DMA 配置带来的外设关联复杂度。待机电流测试要断开调试器因为 st-link 的参考电压会通过 SWD 接口反向供电测出来的数据比真实值虚高不少这是低功耗验证里最容易出现的假数据来源。5. 三件工具把 stm32 门禁程序验证到可以交付5.1 逻辑分析仪抓 SPI 时序定位读卡异常RC522 的调试别靠猜。逻辑分析仪四个通道分别接 CS、SCK、MOSI、MISO触发沿设置在 CS 下降沿先看读版本号的波形主机发出命令后MISO 上必须回来一段数据。采样率不用太高10M 左右足够覆盖 1Mbps 的 SPI 时钟。如果 MISO 一直是高电平问题多半在片选配置或者 MISO 引脚复用如果波形断断续续检查杜邦线长度和 SPI 分频系数。键盘扫描同样能用逻辑分析仪验证行线拉低、对应列线被拉低的过程直接证明键值计算没有接反行列。5.2 串口回放协议帧把状态迁移变成可重复测试把按键、刷卡、门磁统一封装成串口协议帧等于给状态机装了一台信号发生器。常见做法是定义$KEY,05*C7、$RFID,1234ABCD*5A这样的短帧帧尾固定一个校验和。主循环里解析到合法帧后直接调用对应的输入处理函数不经过真实的 GPIO 扫描。测试时用支持循环发送的串口工具预置二十条正常帧加两三条非法帧循环发送同时开启板子的状态迁移日志观察 IDLE 到 VERIFY 到 UNLOCK 的轨迹是否符合预期。非法帧必须包含长度错误和校验错误两类确认这些帧被丢弃且不触发失败计数才能证明协议层能挡住构造数据。5.3 打开 IWDG 做压测让复位问题无处遁形看门狗不是用来兜住 Bug 的但门禁设备挂墙后没人天天去断电重启IWDG 作为最后一道防线仍然必要。在 CubeMX 里开启 IWDG溢出时间设 1 秒主循环末尾喂狗喂狗代码必须放在状态机函数之后因为状态机在 ST_ALARM 锁定期间也要正常返回否则锁定 30 秒内看门狗会误复位整个系统。压测方法是对继电器连续开关 100 次同时统计 stm32 的复位次数如果喂狗间隔正常但仍然出现复位优先怀疑电源跌落而不是程序死循环。IWDG 一旦使能就只能复位关闭所以它不能替代驱动电路整改只用来保证异常发生后系统自动恢复到空闲状态。本文还有配套的精品资源点击获取
返回列表