
简介基于STM32的智能门禁开锁控制系统毕业论文适合电子、嵌入式方向本科生及毕业设计者参考。文档以密码与蓝牙双重开锁方式为主线完整涵盖研究背景、任务目标、总体方案、硬件电路与软件流程涉及STM32F103C8T6单片机、矩阵键盘、OLED显示、蓝牙模块和继电器等核心环节并配有目录、中英文摘要、关键词及参考文献。资源为单个docx文件大小约741KB论文章节编排严谨可直接作为毕业设计写作模板或答辩准备素材。已有287人学习下载对有相似选题的读者具备实用参考价值通过学习可快速理解智能门禁系统的模块化设计思路、主程序与各子程序流程的编写方法以及系统调试中的改进思路。1. 双通道开锁思路密码键盘与蓝牙的职责拆分办公楼、宿舍楼这类场景里门禁最大的两个痛点是磁卡容易复制、机械钥匙丢了就要整套换锁芯。基于STM32的智能门禁开锁控制系统用一块STM32F103C8T6同时管住矩阵键盘、JDY-31蓝牙模块、OLED显示屏和继电器四个外设开锁方式拆成两条独立链路密码键盘负责本地的身份验证蓝牙通道负责手机远程控制。两条链路各管一件事密码链路保证没有手机时也能开门蓝牙链路解决忘密码和远程授权的场景。整个项目把GPIO扫描、串口中断、模拟I2C、状态机这四类嵌入式基本功全部串在一个小系统里对做嵌入式相关毕业设计或刚入门STM32的人来说是一套能直接对照着抄的工程框架硬件选型、引脚分配和调试结论都有了。2. STM32F103C8T6最小系统与四类外设的引脚规划2.1 为什么是F103C8T6与51单片机的方案对比做门禁控制主控选型的第一轮筛选通常是在51单片机和STM32之间。51单片机做简单控制任务没有问题但门禁系统要求串口、I2C、GPIO中断同时跑51的外设数量和中断优先级设置明显吃紧尤其是蓝牙数据接收和键盘扫描并发时容易丢数据。STM32F103C8T6虽然只是F1系列的入门型号72MHz主频、64KB Flash、20KB RAM、37个可用I/O加上USART、I2C、SPI、DMA这些外设齐全撑起键盘、蓝牙、OLED、继电器四个模块绰绰有余。表1是项目里实际需要关注的参数。表1 STM32F103C8T6项目关注参数参数数值工作主频72MHzFlash64KBRAM20KBGPIO数量37个工作电压2.0~3.6V通信接口USART、I2C、SPI、CAN选型时还有一个隐性成本要考虑STM32的调试工具链和固件库资料比51丰富得多遇到问题能搜到的现成方案也多这对毕业设计阶段的开发效率影响很大。2.2 显示与按键选型OLED和矩阵键盘的取舍显示模块在OLED和数码管之间选。数码管只能显示数字和简单字符门禁系统要提示“Open”“Error”这类状态词数码管做起来非常费劲要么靠编码硬凑字母要么加多个指示灯。OLED是自发光器件不需要背光宽视角能耗低还能直接显示汉字和图标在门禁这种人机交互频繁的场景里体验好得多。项目里用的OLED屏幕通过I2C接口通信SCL接PB9SDA接PB8两根线就完成数据交互。按键部分选了4x4矩阵键盘而不是独立按键。16个按键如果全部用独立按键要占16个GPIOPCB走线和引脚资源都紧张。矩阵键盘把按键排成4行4列8个GPIO就能覆盖16个键位行线做输出、列线做输入扫描时逐行拉低电平读回列线状态就能定位具体按键。对门禁这种需要数字0~9加功能键的场景矩阵键盘是更合理的方案也方便以后扩展功能键。2.3 最小系统三件套晶振、复位与电源STM32F103C8T6的电路设计围绕最小系统展开最小系统由晶振电路、复位电路和电源电路三部分组成。晶振采用8MHz无源晶振配合两个负载电容接到OSC_IN和OSC_OUT引脚经过内部PLL倍频到72MHz。这部分电路决定了整个系统能不能跑起来晶振起振失败时单片机会完全没反应。复位电路用RC复位上电时电容充电维持一段低电平时间让MCU完成内部初始化。电源部分通过USB接口取5V再经LDO稳压到3.3V给单片机和外设供电。USB供电的优势是调试阶段不需要额外电源插上数据线就能跑。这里有一个容易被忽略的细节继电器模块如果直接用单片机引脚驱动电流可能不够常见做法是加一个NPN三极管或光耦做隔离驱动防止继电器线圈反电动势损坏GPIO。2.4 引脚分配总表与JTAG引脚释放表2是项目四个外设的GPIO分配情况连接时对照这张表检查能避免大部分接线错误。表2 外设与GPIO引脚分配外设引脚说明矩阵键盘行线PB0~PB3推挽输出扫描时逐行拉低矩阵键盘列线PB4~PB7上拉输入读取按键状态OLED SCLPB9时钟线软件模拟I2COLED SDAPB8数据线软件模拟I2CJDY-31 TXDPA3接USART2_RXJDY-31 RXDPA2接USART2_TX继电器控制PC13控制门锁开关分配引脚时要注意PB4这个脚。STM32F103C8T6上电后PB3、PB4默认是JTAG调试口的一部分PB4对应NJTRST。如果不在初始化时关闭JTAG功能直接把PB4当普通GPIO用按键扫描会时好时坏跟调试器抢引脚。初始化时需要显式调用重映射函数释放这两个引脚GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure);第一行打开AFIO时钟第二行关闭JTAG功能但保留SWD第四行把PB4重新配置成浮空输入。这样做的代价是失去JTAG调试能力但SWD下载调试不受影响Keil里用ST-Link或J-Link的SWD模式照常烧录。凡是矩阵键盘用到PB3、PB4的项目这一步都是必须的。3. 矩阵键盘扫描算法与密码验证状态机的落地3.1 逐行拉低、读列电平的扫描原理4x4矩阵键盘的连接方式是4根行线接PB0~PB34根列线接PB4~PB7。按键按下时对应的行线和列线被短路。扫描的原理是每次只把一行拉低其余行保持高电平然后读回所有列线的电平状态。如果读到某列变低说明该行该列的按键被按下。要注意的是列线必须配置成上拉输入否则没有按键按下时列线电平漂移读回来全是随机值。行线配置成推挽输出扫描时置低。一个完整的扫描周期需要依次拉低4行每行停留2~5毫秒保证电平稳定后再读取列状态。3.2 带去抖的按键扫描代码按键是机械开关按下和释放的瞬间会产生抖动通常持续5~10毫秒。直接读电平会读到多次跳变一次按键被识别成好几次。常见做法是检测到电平变化后延时10毫秒再读一次确认电平稳定才认为按键有效。下面的扫描函数用BSRR寄存器操作行线避免误伤同组的其他引脚uint8_t Key_Scan(void) { uint8_t row, col; uint8_t colValue; for (row 0; row 4; row) { // 先把4根行线全部拉低再单独拉高当前行以外的行 GPIOB-BRR 0x000F; // PB0~PB3 全部输出低电平 GPIOB-BSRR (uint16_t)(0x01 row); // 当前行输出高电平 Delay_ms(2); // 等待电平稳定 colValue (GPIOB-IDR 4) 0x0F; // 读取PB4~PB7的输入状态 if (colValue ! 0x0F) // 有列线被拉低说明有按键 { Delay_ms(10); // 去抖延时确认不是噪声 colValue (GPIOB-IDR 4) 0x0F; if (colValue ! 0x0F) { for (col 0; col 4; col) { if ((colValue (0x08 col)) 0) { return row * 4 col; // 返回0~15的按键编号 } } } } } return 0xFF; // 无按键按下 }这里用BRR和BSRR而不是GPIO_Write原因是GPIO_Write会一次性写入整个GPIOB的ODR寄存器PB8、PB9正在做OLED数据通信扫描键盘时如果被整体覆盖OLED会闪屏甚至乱码。BRR和BSRR是硬件原子操作只影响低4位安全得多。colValue里高4位是列线状态0x08、0x04、0x02、0x01分别对应PB7、PB6、PB5、PB4哪个bit为0就说明哪列被拉低。按键编号拿到后通过查表映射成实际字符。4x4矩阵键盘的常见布局是const uint8_t KeyMap[16] { 1, 2, 3, A, 4, 5, 6, B, 7, 8, 9, C, *, 0, #, D };这样返回的按键编号0~15直接查表就得到对应字符密码判断逻辑只需要处理字符0~9*和#作为功能键单独分支处理。3.3 密码输入状态机与错误计数门禁系统的密码逻辑适合用状态机实现。系统上电后处于空闲状态ST_IDLE按下#进入密码输入状态每按一个数字键存入缓冲区攒够4位就与存储的密码比较。按*进入修改密码状态要求先验证旧密码再输入新密码并二次确认。这个流程对应论文图15的密码设置流程旧密码错误累计三次触发报警两次新密码不一致则重新输入。typedef enum { ST_IDLE, // 空闲等待功能键 ST_INPUT_PWD, // 密码输入中 ST_CHANGE_PWD_OLD, // 修改密码验证旧密码 ST_CHANGE_PWD_NEW, // 修改密码输入新密码 ST_CHANGE_PWD_CONFIRM // 修改密码再次确认 } PwdState; uint8_t pwdBuf[4] {0}; uint8_t pass 0; uint8_t storedPwd[4] {1, 2, 3, 4}; // 默认密码1234 PwdState state ST_IDLE; uint8_t errCnt 0; void Password_Handler(uint8_t key) { switch (state) { case ST_IDLE: if (key #) // 进入密码解锁 state ST_INPUT_PWD; else if (key *) // 进入修改密码 state ST_CHANGE_PWD_OLD; break; case ST_INPUT_PWD: if (key 0 key 9) { pwdBuf[pass] key; // 逐位存入缓冲区 if (pass 4) // 攒满4位后比较 { if (memcmp(pwdBuf, storedPwd, 4) 0) { Door_Open(); // PC13拉高触发继电器 OLED_ShowString(0, 2, Open); errCnt 0; } else { errCnt; OLED_ShowString(0, 2, Error); if (errCnt 3) { System_Lock_30s(); // 连续3次错误锁定30秒 errCnt 0; } } pass 0; // 位数回0等待下一次输入 } } break; case ST_CHANGE_PWD_OLD: // 输入长度为4的旧密码验证通过后进入ST_CHANGE_PWD_NEW break; case ST_CHANGE_PWD_NEW: // 输入长度为4的新密码存到临时buf进入ST_CHANGE_PWD_CONFIRM break; case ST_CHANGE_PWD_CONFIRM: // 再次输入新密码两次一致则写入storedPwd不一致重新输入 break; } }memcmp逐字节比较两个4字节数组完全一致返回0这就是密码验证的核心逻辑。pass每次输入递增满4位后比较并清零对应论文代码片段里的“pass 0; //位数回0”。错误计数errCnt连续3次清零并锁定锁定期间按键和蓝牙指令都不响应只显示剩余锁定时长这个机制防止暴力试密码。修改密码的分支逻辑论文里有完整流程描述实际实现时维护一个临时数组存放新密码确认输入一致后再整体拷贝到storedPwd。3.4 密码存储与掉电保持上面的代码把默认密码1234写死在数组里掉电重新上电后密码会恢复成初始值这显然不满足真实门禁需求。产品化时两个方案一是把密码写入STM32内部Flash的最后几页用Flash模拟EEPROM写入前先擦除整个扇区注意不要覆盖程序区二是外挂AT24C02这类I2C EEPROM芯片改密时写入上电时读出。毕业设计阶段用数组初始化足够但如果论文里要展示掉电保存能力内部Flash方案不增加任何硬件成本推荐优先用。4. JDY-31蓝牙串口与Keil5工程搭建4.1 Keil5芯片包安装与标准库工程配置用Keil uVision 5开发STM32第一步不是写代码而是装芯片支持包。新建工程时如果器件列表里找不到STM32F103C8说明Pack Installer里没有安装Keil::STM32F1xx_DFP这个包。打开Pack Installer在Device列表里找到STMicroelectronics下的STM32F1系列安装对应DFP包然后再回到Project窗口新建工程Device选项卡里输入STM32F103C8就能匹配到C8T6Flash容量自动识别为64KB。工程建立后还有两个配置容易漏。标准库工程需要在C/C选项卡的Define里加上USE_STDPERIPH_DEVICE否则标准库的外设驱动代码不会参与编译一调用GPIO_Init就报undefined identifier。另外要把标准库源码路径添加进Include Paths缺了这步头文件全部飘红。调试器配置在Debug选项卡选择ST-Link或J-Link后进入Settings能看到芯片IDCODE说明连接正常再在Flash Download里勾选Reset and Run烧录完自动复位运行。4.2 JDY-31模块接线与AT指令配置JDY-31是串口透传模块工作电压支持3.3~5V默认波特率9600。接线按表2来模块TXD接单片机PA3RXD接PA2VCC和GND与单片机共地。串口发送和接收要交叉连接如果接成TXD对TXD、RXD对RXD蓝牙收不到任何数据。模块上电默认工作在透传模式收发内容直接走串口。要修改蓝牙名称或波特率需要给模块发送AT指令。下面是JDY-31常用的几条指令表3 JDY-31常用AT指令指令作用说明ATNAMEble_door修改蓝牙设备名手机搜索到的模块名称变为ble_doorATBAUD4修改波特率4对应9600设置后模块重启生效ATPIN1234设置配对密码部分固件版本支持不支持会返回ERRORATRST复位模块修改参数后执行让配置生效串口调试助手发AT指令时要注意JDY-31每条指令必须以回车换行结尾否则模块不识别。配置完成后拔掉USB转TTL模块重新上电手机蓝牙列表里就能搜到设置好的设备名。4.3 USART2中断接收与指令解析蓝牙透传的数据最终从串口进入单片机这里用USART2接收中断。接收中断服务函数里只做一件事把收到的字节存进缓冲区。解析和判断放到主循环里做不在中断函数里处理耗时的字符串比较操作。uint8_t rxBuf[16] {0}; uint8_t rxLen 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { rxBuf[rxLen] USART_ReceiveData(USART2); if (rxLen sizeof(rxBuf) - 1) // 防止缓冲区溢出 rxLen 0; } } void BlueTooth_Task(void) { if (strstr((char *)rxBuf, OPEN)) { Door_Open(); memset(rxBuf, 0, sizeof(rxBuf)); rxLen 0; } else if (strstr((char *)rxBuf, CLOSE)) { Door_Close(); memset(rxBuf, 0, sizeof(rxBuf)); rxLen 0; } }中断函数里RXNE标志位由硬件置位表示接收数据寄存器非空USART_ReceiveData读取数据同时自动清除该标志。缓冲区大小16字节手机端发来的控制指令控制在8字节以内留足余量。主循环里用strstr在缓冲区里搜索关键字匹配到OPEN就开门匹配到CLOSE就关门。手机端常见的蓝牙串口类APP都可以发送这些字符串。串口初始化时需要配置USART2的TX为复用推挽输出、RX为浮空输入波特率96008数据位1停止位无校验最后使能USART2中断。4.4 OLED显示驱动软件模拟I2C更省心论文中OLED的SCL接PB9、SDA接PB8。F103的硬件I2C1默认引脚是PB6/PB7要重映射才能到PB8/PB9而且F1系列硬件I2C在部分场景下会有时序稳定性问题。实践里很多项目直接用软件模拟I2C协议两个引脚可以任意指定时序由延时控制可靠性反而更好。OLED屏幕采用SSD1306驱动7位I2C地址常见是0x3C部分屏幕是0x3D。初始化流程是先发关闭显示指令0xAE配置时钟分频和显示参数清屏最后开显示。驱动初始化完成后封装OLED_ShowString函数传入横纵坐标和字符串内容就能在屏幕上显示字符。项目主循环里显示“Welcome”、密码输入提示和开锁结果OLED内容更新放在主循环末尾不需要额外刷新任务。4.5 主循环里的任务调度系统主程序流程按论文4.2节的结构组织先做硬件初始化包括RCC时钟配置、GPIO初始化、USART2初始化、OLED初始化和继电器引脚配置然后进入无限循环。主循环里依次执行键盘扫描、密码状态机处理、蓝牙指令解析和OLED显示刷新。int main(void) { System_Init(); // 时钟、GPIO、USART2、OLED、继电器 while (1) { uint8_t key Key_Scan(); if (key ! 0xFF) Password_Handler(KeyMap[key]); BlueTooth_Task(); OLED_Refresh(); Delay_ms(5); } }键盘扫描返回值不是0xFF时说明有按键按下先通过KeyMap查表转成字符再交给密码状态机处理。Delay_ms(5)控制主循环频率防止扫描和显示任务空转吃掉过多CPU时间。这个结构简单直接键盘扫描的耗时很短不会阻塞蓝牙数据的及时接收。5. 联调阶段的五个高频故障与验证方法5.1 烧录配置与常见报错下载调试用J-Link或ST-Link均可以SWD模式只需要4根线SWDIO、SWCLK、GND、3.3V。Keil工程里Debug选项卡选择对应仿真器Settings里如果能看到IDCODE说明接线正确。烧录报错集中在两类一是Device型号选错C8T6必须选STM32F103C8错选成CB会导致Flash容量校验失败二是SWD线缆太长超过20厘米时经常报RDDI-DAP Error把调试速度降到1MHz以下通常能解决。5.2 高频故障排查表联调阶段把实物按照论文图16的布局连接后逐个功能验证。下面这五个问题出现频率最高排查思路在表4里。表4 联调高频故障与排查方向症状大概率原因排查方法OLED不亮或花屏I2C地址不对或SDA/SCL接反核对驱动代码里的地址是0x3C还是0x3D用示波器量SCL/SDA有无波形按键无响应或串键列线没有上拉、去抖时间不足万用表量列线上电后是否3.3V扫描延时加到10ms继电器不动作GPIO推挽配置错误或驱动电流不足先用LED接PC13验证GPIO逻辑改三极管驱动蓝牙收不到数据TX/RX接反、波特率不匹配用USB转TTL单独测模块确认9600能收到AT回显程序卡在延时函数SysTick被改或中断里调用延时单独跑LED点灯验证延时中断服务函数里不调用Delay程序卡在延时函数这个坑值得多说一句。标准库的Delay_ms依赖SysTick中断如果代码里手动修改了SysTick的优先级或者关闭了全局中断延时函数会永远等不到中断触发表现就是程序跑进Delay函数后死了。排查办法是确认NVIC配置里SysTick的抢占优先级没有被其他中断挤掉并且延时函数只放在主循环或普通的按键处理流程里不放在串口中断、定时器中断里。5.3 两条开锁链路的验收步骤密码链路的验收按论文图17和图18的步骤走输入正确密码1234OLED显示Open继电器吸合观察门锁动作输入错误密码OLED显示Error继电器不动作连续错误3次系统锁定30秒。蓝牙链路用手机APP连接JDY-31发送字符串OPEN观察门锁打开发送CLOSE门锁关闭。两条链路各测10次出现失灵就按表4的维度定位。5.4 串口打印定位问题的具体技巧联调阶段最实用的手段是串口打印。在USART1上挂一个调试串口通过重定向fputc把printf输出转到串口发送int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }然后在关键路径上打印调试信息比如键盘扫描返回值、状态机状态、蓝牙接收到的原始帧printf(key%c state%d\r\n, key, state); printf(bt:%s\r\n, rxBuf);USB转TTL接到PA9USART1_TX和PA10USART1_RX电脑上打开串口助手就能看到程序运行轨迹。按键识别不出来时先看串口打印键值是否正常蓝牙指令丢失时把rxBuf的原始内容打印出来对比实际收到的字节和预期的OPEN/CLOSE差异。大多数联调问题靠这一行printf就能定位到具体模块比猜硬件故障快得多。本文还有配套的精品资源点击获取