资讯动态

STM32智能门禁系统:多身份验证与状态机设计

发布时间:2026/9/17 12:45:15 来源:尧图企业网站定制
简介面向电子信息、自动化与嵌入式方向本科生的毕业设计文档围绕以STM32F407ZGT6为核心、集成密码键盘、指纹识别与RFID读卡模块的智能门禁系统展开覆盖硬件选型与电路连接、软件流程调度、用户信息增删与门禁记录管理等关键环节可用于论文撰写参考、课程设计选题与嵌入式入门实践。文档以生物识别与卡片识别双重验证为主线兼顾安全与通行便利并涉及数据加密存储、隐私保护与网络防护等设计思路。压缩包共1个文件为863KB的docx毕业论文包含摘要、研究背景与意义、研究目的、系统总体方案及软硬件设计等章节结构完整可直接借用论文框架也可据此拆解STM32外设驱动与模块联调任务。目前已有178人学习下载适合需要理清门禁类课题思路、补齐论文结构或对照实现细节的读者。1. 从答辩被问懵说起STM32 门禁系统到底在做什么有个现象挺有意思每年毕业季STM32 门禁类课题的实物能跑通但答辩一问“你这个身份验证的状态机怎么设计的”很多人就卡住了。问题不在焊板子在于没把系统当成一个完整的信息流来看。这套基于 STM32 的智能门禁系统本质上是一个以单片机为中枢、把三种身份凭据密码、指纹、RFID 卡统一收敛到一套权限模型的嵌入式控制系统。它的输入层是 4×4 矩阵键盘、AS608 指纹模块、RC522 射频读卡器输出层是 OLED 状态显示和 SG90 舵机模拟的门锁执行机构中间靠 STM32F103C8T6 做调度。适合已经会点亮 LED、会写串口收发但没做过完整多模块协同项目的人也适合被“毕设怎么从散装例程变成一套系统”困住的开发者。2. STM32F103C8T6 主控选型与外设资源分配2.1 为什么这颗芯片够用以及哪里会不够STM32F103C8T6 是 Cortex-M3 内核、LQFP48 封装、72MHz 主频、64KB Flash、20KB SRAM。门禁系统的外设需求算下来并不重OLED 走 I2C 占 2 根线RC522 走 SPI 占 4 根线NSS、SCK、MISO、MOSIAS608 走 UART 占 2 根线矩阵键盘占 8 根 GPIO舵机占 1 个定时器通道蜂鸣器占 1 根。加起来 GPIO 占用在 20 根以内C8T6 的 37 个可用 IO 完全兜得住。真正吃紧的是 SRAM。AS608 的指纹特征模板一个用户约 512 字节如果不在本地存模板、只存指纹 ID10 个用户也就几十字节但如果你打算把指纹图像缓冲区AS608 单幅图像约 36864 字节也塞进片内20KB 直接爆。常见做法是让 AS608 自己管模板库STM32 只存“指纹 ID → 权限等级”的映射表这样 SRAM 消耗可以压到 2KB 以内。外设模块接口方式占用引脚对应外设资源0.96 寸 OLEDI2CPB6/PB7I2C1RC522SPIPA4-PA7SPI1AS608 指纹UARTPA9/PA10USART14×4 矩阵键盘GPIO 扫描PB0-PB7GPIOBSG90 舵机PWMPA0TIM2_CH1蜂鸣器GPIOPC13GPIO 输出提示PA9/PA10 同时也是烧录后常用的串口调试口接 AS608 之后就没法直接用串口打印调试信息了。我一般会把调试口换到 USART2PA2/PA3避免拆线才能看日志。2.2 时钟树与启动配置F103 默认走内部 8MHz RCHSI直接跑会飘。外部晶振 8MHz 经 PLL 9 倍频到 72MHz这是标准配置必须在SystemInit之前把 HSE 使能否则定时器算出来的 PWM 脉宽全错。下面是用标准库配置时钟的关键片段// system_stm32f10x.c 中的时钟配置确认 HSE_VALUE 为 8000000 #define HSE_VALUE ((uint32_t)8000000) // PLL 倍频 9 倍得到 72MHz 系统时钟 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); // 等待 PLL 锁定 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() ! 0x08); // 等待切换完成逻辑说明先等 PLL 就绪再切系统时钟源顺序反了会导致切到未锁定的 PLL 上程序跑飞。参数说明RCC_PLLMul_9决定了最终主频改成RCC_PLLMul_4就是 32MHz舵机 PWM 的预分频值要跟着重算。2.3 舵机的 PWM 参数怎么算才不抖SG90 的周期是 20ms50Hz脉宽 0.5ms 对应 0 度2.5ms 对应 180 度。F103 的 TIM2 挂在 APB1 上时钟 72MHz。预分频PSC71得到 1MHz 计数频率自动重装值ARR19999得到 20ms 周期。这样每个计数单位是 1μs脉宽直接写微秒数就行// TIM2 初始化72MHz / (711) 1MHz 计数频率 TIM_TimeBaseInitTypeDef t; t.TIM_Prescaler 71; // 1us 一个计数 t.TIM_Period 19999; // 20ms 周期对应 50Hz t.TIM_ClockDivision 0; t.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, t); // 设置 90 度脉宽 1.5ms TIM_SetCompare1(TIM2, 1500); // 单位是 us参数说明TIM_SetCompare1的值就是脉宽微秒数500 对应 0 度2500 对应 180 度。门禁开锁常用 0→90 度关门 90→0 度。注意舵机上电瞬间会往某个角度猛冲机械门锁的限位要留余量否则堵转电流会把 USB 供电拉垮表现为单片机复位——这个坑我踩过换独立 5V 2A 供电就好了。3. 三种身份验证模块的驱动与状态机设计3.1 矩阵键盘扫描与密码输入的防抖处理4×4 矩阵键盘 16 个键需要 8 根线比 16 个独立按键省一半 IO。原理是行线输出低电平、列线配置为上拉输入逐行拉低读取列状态哪一列为低就说明哪一行那一列的键被按下。扫描周期设在 10ms 左右配合软件消抖。// 行线 PB0-PB3 推挽输出列线 PB4-PB7 上拉输入 const uint8_t keymap[4][4] { {1,2,3,A}, // A 键切换密码模式 {4,5,6,B}, // B 键切换指纹模式 {7,8,9,C}, // C 键切换刷卡模式 {*,0,#,D} // # 确认* 取消D 进管理员 }; uint8_t KeyScan(void) { for (uint8_t row 0; row 4; row) { GPIO_WriteBit(GPIOB, 0x0F, (uint16_t)~(1 row)); // 逐行拉低 for (uint8_t col 0; col 4; col) { if (!GPIO_ReadInputDataBit(GPIOB, 1 (col 4))) { delay_ms(10); // 消抖 if (!GPIO_ReadInputDataBit(GPIOB, 1 (col 4))) return keymap[row][col]; } } } return 0; // 无按键 }逻辑说明GPIO_WriteBit的掩码只操作低 4 位不会误改列线状态。参数说明delay_ms(10)是消抖延时太短会连击太长会漏键10~20ms 是经验区间。注意如果键盘和 OLED 共用 GPIOB扫描时会短暂改变 PB4-PB7 状态OLED 若也接在 I2C 的 PB6/PB7 上就会花屏。这是硬件规划阶段就该避开的重叠别等调不出来才查。3.2 AS608 指纹模块的指令帧格式与权限分级AS608 走 UART默认波特率 57600。它用固定帧格式通信帧头 2 字节0xEF 0x01然后是地址默认0xFF 0xFF 0xFF 0xFF、包标识、包长度、指令码、参数、校验和。校验和 包标识 包长度高字节 包长度低字节 指令 参数逐字节累加取低 16 位。// 采集指纹并搜索模板库返回匹配到的指纹 ID uint8_t FingerSearch(void) { uint8_t cmd[] {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x01,0x00,0x05}; // 最后两字节 0x00 0x05 是校验和由 send_cmd 内部重算 uart_send(cmd, 12); delay_ms(200); // AS608 比对需要时间 if (uart_read(buf, len) ACK_OK) { return buf[10]; // 返回模板 ID } return 0xFF; // 未匹配 }逻辑说明帧里的0x01是搜索指令码参数0x00是缓冲区号。参数说明delay_ms(200)要覆盖模块比对耗时AS608 搜索 1000 个模板约 300ms模板少时可以缩短但别低于 100ms。权限分级用一张映射表实现指纹 ID 0~4 为管理员5~9 为普通用户。管理员指纹只能通过“管理员进入流程”录入普通用户不能修改权限表这是整个系统安全性的下限。3.3 RC522 的 SPI 读卡流程与扇区结构RC522 支持 SPI、I2C、UART 三种接口SPI 最常用。读卡分四步寻卡REQA、防冲突防多卡同时响应、选卡、读取扇区。每张 Mifare S50 卡有 16 个扇区、每扇区 4 个块块 0 是厂商信息块 3 是密钥块用户数据存在块 1、块 2。门禁系统一般只在块 1 存一个 4 字节卡号或者用户编号。uint8_t ReadCardUID(uint8_t *uid) { if (PcdRequest(PICC_REQIDL, buf) ! MI_OK) return ERR_NO_CARD; // 寻卡 if (PcdAnticoll(buf) ! MI_OK) return ERR_CONFLICT; // 防冲突拿到 4 字节 UID memcpy(uid, buf, 4); // 用 UID 前 4 字节做比对无需读扇区速度更快 return MI_OK; }逻辑说明UID 是卡的唯一标识直接比对 UID 比读扇区更快也不涉及密钥验证。参数说明PICC_REQIDL表示寻感应区内所有未休眠的卡如果要读休眠卡需要发PICC_REQA。3.4 统一身份验证状态机三个模块各自为政会写出一堆 if-else维护成本极高。我一般抽出一个状态机把“等待输入身份 → 验证 → 判定权限 → 执行动作”四个阶段固定下来状态触发条件动作下一步IDLE系统上电OLED 显示主菜单WAIT_INPUTWAIT_INPUT按键选择模块切换当前验证方式VERIFYVERIFY输入提交调用对应模块校验CHECK_PERMCHECK_PERM校验通过判断管理员/用户权限UNLOCK 或 ADMINUNLOCK权限为普通用户舵机开锁蜂鸣器短鸣IDLEADMIN权限为管理员进入设置菜单IDLE这个结构让新增一种验证方式只需在 WAIT_INPUT 里加分支校验和权限逻辑不用动。4. 断电保存、错误锁定与实测排错4.1 用 Flash 模拟 EEPROM 存用户数据F103C8T6 没有片内 EEPROM密码、指纹 ID 映射、卡号列表都得存 Flash 或者外挂芯片。最省事的方案是用片内 Flash 的最后一页Page 63地址 0x0800FC00当存储区。写之前必须先擦除整页擦除后是 0xFF再按半字16 位写入。#define PARA_ADDR 0x0800FC00 void SaveParams(Params *p) { FLASH_Unlock(); FLASH_ErasePage(PARA_ADDR); // 必须整页擦除 uint16_t *src (uint16_t *)p; for (uint16_t i 0; i sizeof(Params)/2; i) { FLASH_ProgramHalfWord(PARA_ADDR i*2, src[i]); } FLASH_Lock(); }逻辑说明FLASH_ErasePage擦除 2KB 一页写入数据不能超过这个大小。参数说明sizeof(Params)/2是因为按半字写结构体大小必须是偶数。注意 Flash 擦写寿命约 1 万次别在按键循环里频繁保存只在退出设置时存一次。4.2 连续错误锁定的实现与解锁路径密码连续错 3 次转管理员验证管理员再错 3 次锁定刷卡连续错 3 次同理。这个逻辑的核心是计数器要区分“当前会话”和“累计错误”会话级计数器在成功验证后清零锁定标志写进 Flash 保证断电后仍然锁定。if (WrongCount 3) { if (CheckAdmin(pwd)) { WrongCount 0; // 管理员兜底计数器清零 UnlockDoor(); } else { LockFlag 1; SaveParams(params); // 锁定状态持久化 OLED_ShowString(0, 2, SYSTEM LOCKED); } }注意锁定后必须有硬件级恢复路径我一般留一个隐藏组合键比如上电时长按 D 键 5 秒触发恢复出厂设置否则演示时锁死了只能重新烧录。4.3 实测中最容易遇到的四个问题第一OLED 显示乱码。八成是 I2C 地址写错SSD1306 的地址是 0x78写或 0x7A读如果驱动里写的 0x3C那是 7 位地址没左移。第二指纹模块无响应。先量波特率用逻辑分析仪抓帧头是不是0xEF 0x01如果不是就是波特率不匹配AS608 支持 9600 到 115200。第三读卡距离太短。RC522 天线是板载 PCB 天线周围有金属或者锡箔会严重衰减卡片贴合到 1cm 内才响应是正常的想改善得换大天线版本。第四舵机一转单片机就复位。这就是前面说的供电问题舵机堵转电流能到 1A 以上USB 口给不了独立供电是最快的排查手段。5. 从能跑到好用把状态机改成表驱动原型能跑之后下一层痛点是状态转移逻辑散落在各模块的 if-else 里加一个“临时密码”或者“防尾随延时上锁”就得动好几处代码。我习惯把手写状态机改成表驱动用一张常量表描述所有转移执行引擎只有一个switchtypedef struct { uint8_t cur; // 当前状态 uint8_t event; // 触发事件 uint8_t next; // 目标状态 void (*action)(void); // 转移动作 } Trans; const Trans fsm[] { {IDLE, EV_KEY_A, WAIT_INPUT, SwitchToPwd}, {IDLE, EV_KEY_B, WAIT_INPUT, SwitchToFinger}, {VERIFY,EV_OK, CHECK_PERM, NULL}, {VERIFY,EV_FAIL, IDLE, IncWrongCount}, {CHECK_PERM, EV_IS_ADMIN, ADMIN, EnterAdmin}, }; void FSM_Run(uint8_t event) { for (uint8_t i 0; i sizeof(fsm)/sizeof(Trans); i) { if (fsm[i].cur g_state fsm[i].event event) { if (fsm[i].action) fsm[i].action(); g_state fsm[i].next; return; } } // 未匹配到转移忽略该事件 }逻辑说明事件由按键扫描和模块回调统一产生引擎只负责查表新增功能就是加一行表项不动引擎代码。参数说明sizeof(fsm)/sizeof(Trans)是表项数量表越大线性查找越慢条目过百可以按cur做分段索引但对门禁这种十几个状态的系统没必要。验证这张表对不对可以写一个简单的自动化脚本把状态转移序列打印成日志上电后依次触发 A 键、输入错误密码三次、触发 D 键、输入管理员密码检查日志里的状态序列是不是IDLE → WAIT_INPUT → VERIFY → IDLE → ... → ADMIN。手工点按键验证容易漏分支用串口日志回放能覆盖到边角情况。调试阶段还可以把 AS608 的返回码全部映射成可读字符串打出来0x00是成功0x09是未搜索到模板0x0A是合并模板失败0x1C是图像太干排错时一眼就能定位是手指问题还是通信问题。这类返回码映射表在开发后期比任何断点都好用尤其是现场演示不能接调试器的时候。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价