简介:这份资源是一套基于STM32F103ZET6设计的智能门锁完整项目源码包,面向嵌入式爱好者、毕业设计学生及物联网开发者,覆盖蓝牙开锁、微信小程序远程开锁、按键模拟钥匙、RC522刷卡识别四种功能。主控采用STM32F103ZET6,蓝牙模块HC05、WiFi模块ESP8266、刷卡模块RFID-RC522,并通过步进电机正反转模拟锁体开合动作。压缩包共163个文件,核心包括STM32固件C/H源码、Keil工程文件、生成的hex固件、Android蓝牙开锁APK、微信小程序相关配置、原理图与PDF设计文档等,整体大小约39.74MB,目录结构清晰,可快速定位硬件设计、程序源码和使用说明。目前已有982人学习下载,具备一定参考热度。资料包内含详细设计文档、接线说明、实物图以及配套工具软件,按文档购买硬件、编译下载即可复现整个项目,有助于系统掌握外设驱动、蓝牙串口通信、ESP8266接入服务器及小程序远程控制等关键技能。
1. 基于STM32的智能门锁,微信小程序只是多了一路遥控
基于STM32的智能门锁,再加一个微信小程序远程开锁,很多人第一反应是“这不就是蓝牙串口换个壳吗”。实际把HC05蓝牙、ESP8266 WiFi、RFID-RC522刷卡和按键都接到STM32F103ZET6之后,会发现真正的难点不是某一项驱动,而是四路开锁指令怎么去抢同一个门锁状态机。这份2023升级版完整源码包正好覆盖了这条链路:Android蓝牙APP、微信小程序、RC522刷卡驱动、MQTT透传、步进电机模拟门锁全部齐了。适合嵌入式毕设、物联网课设,或者想快速搭一套智能门锁原型的工程师。资料里已经带编译好的axf和APK,拿到手主要精力可以放在接线和理解状态机,而不是从零调SPI和串口。
2. 硬件选型与接线:STM32F103ZET6 如何同时带四路开锁外设
2.1 主控与外设的选型理由
STM32F103ZET6 在这个场景属于“性能冗余但接口刚好”的选择。它提供144引脚、5个USART、3个SPI和512KB Flash。当前方案里,HC05占一个串口,ESP8266占一个串口,调试串口再占一个,RC522走SPI,按键用外部中断,步进电机占四五个GPIO,引脚数量和DMA通道都刚好够用。如果换成F103C8T6,串口和Flash会比较紧张,而且LQFP48封装手工焊接难度并不低。
用ZET6的好处是后续想加指纹模块、OLED菜单、舵机锁体,都有富余的定时器和USART。更重要的是,网上大量STM32智能门锁源码都基于这个型号,遇到问题能找到的参考更多。这里说的“选型理由”不是堆参数,而是从源码包里的主控、蓝牙、WiFi、刷卡模块四条线往回推:每一个外设都有明确的接口抢占点。
2.2 外设接口与供电规划
拿到资料包先不要上电,先看设计文档里的接线说明。下面这个表格是常见接法,也是典型STM32F103ZET6的默认外设映射,具体以设计文档为准。
| 外设 | 通信方式 | STM32引脚 | 供电要求 | 注意事项 |
|---|---|---|---|---|
| HC05蓝牙 | USART2 | TX2-PA2,RX2-PA3 | 3.3V | 电平兼容,波特率需与代码一致 |
| ESP8266 WiFi | USART1 | TX1-PA9,RX1-PA10 | 3.3V,峰值电流300mA | 独立稳压,勿与RC522共用LDO |
| RFID-RC522 | SPI1 | SCLK-PA5,MISO-PA6,MOSI-PA7,CS-PA4,RST-PB1 | 3.3V | SPI时钟建议2MHz左右 |
| 步进电机 | GPIO | PB0-PB3 | 5V/12V独立电源 | 必须用ULN2003驱动板 |
| 按键 | EXTI | PA0-PA2 | 3.3V上拉 | 按下接地 |
供电是最容易出问题的一环。ESP8266在发射WiFi信号时瞬时电流可能到300mA,如果和RC522共用一个AMS1117-3.3,刷卡瞬间WiFi刚好在重连服务器,就会出现RC522读卡失败。我一般会把ESP8266的VCC单独从5V输入串一个低压差LDO,并加大电容到470uF。步进电机也不要从STM32板载5V取电,电机反转时会拉低电源,导致HC05掉线。正确做法是ULN2003的VM接外部5V适配器,GND和STM32共地。
2.3 初始化代码骨架:先把GPIO和串口跑通
不要急着看门锁逻辑,先核对工程里的GPIO初始化。下面这段是源码包main.c里常见的初始化片段:
void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // 步进电机 PB0-PB3:推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 按键 PA0-PA1:上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); // RC522 RST引脚:推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); }这段代码有三个关键点。第一,电机引脚必须用推挽输出,不能开漏,否则ULN2003输入高电平驱动不足。第二,按键要配置成上拉输入,因为按键一端接地,按下时引脚被拉低,上拉输入避免悬空误触。第三,RC522的RST引脚如果复用为SPI片选之外的普通GPIO,初始化必须在SPI初始化之前完成,否则模块可能复位失败。
2.4 串口分配与中断优先级
源码包里的串口分配是有讲究的:USART1给ESP8266,USART2给HC05,USART3给调试打印。这样分配的原因是ESP8266的AT指令返回长字符串,会占用大量串口中断时间;而HC05蓝牙开锁要求响应速度快。把USART1的NVIC优先级调低,USART2设成最高,可以避免WiFi连接的“忙等”拖慢蓝牙开锁。
RC522通常使用SPI1,片选CS接PA4,RST接PB1。这里要注意:SPI的默认极性和相位需要匹配RC522的MFRC522数据手册,标准配置是SPI_Mode0,即CPOL=0、CPHA=0。如果读卡返回超时,先检查这两个参数。
注意:HC05和ESP8266的串口电平都是3.3V,STM32的TX直接连即可。但如果HC05是5V版本,需要加电阻分压,否则长时间工作会拉高PA3输入电压。
3. 源码包文件拆解:main.c、RFID_RC522.c、mqtt.c 的协同逻辑
3.1 解压后这些文件各是什么
打开压缩包会看到一长串文件,很多是Keil工程自动生成的,不用逐行读。
| 文件/名称 | 作用 | 需要改吗 |
|---|---|---|
| STM32HD.uvguix.11266 | Keil MDK窗口布局文件 | 不需要 |
| STM32HD.axf | 编译后的调试文件,可加载到Keil或ST-Link Utility | 不需要 |
| system_stm32f10x.c | 时钟初始化,设置72MHz主频 | 不需要 |
| core_cm3.c | Cortex-M3内核支持文件 | 不需要 |
| main.c | 主循环、状态机、按键扫描、命令解析 | 需要 |
| RFID_RC522.c | RC522底层驱动与寻卡实现 | 需要看 |
| mqtt.c | ESP8266 WiFi连接与MQTT消息收发 | 需要改 |
| VCL60.BPL / RTL60.BPL | Delphi运行时库,可能是上位机调试工具依赖 | 不需要 |
| 蓝牙开锁APP_1.apk | Android蓝牙开锁APP | 直接安装 |
其中VCL60.BPL和RTL60.BPL很容易被误认为病毒或工程垃圾,其实它们是Borland Delphi的运行时库。有的上位机调试工具用Delphi写成,运行时需要这两个文件,和STM32固件没有关系。
3.2 main.c 主循环状态机:防止四路指令“抢锁”
门锁最忌讳的是“蓝牙正在开锁,微信小程序又发来开锁,然后电机同时正转反转”。源码包的做法是把门锁状态收敛到一个状态机,所有开锁指令统一走LockOpen()入口。
// main.c 状态机要点 enum LOCK_STATE { LOCK_CLOSED = 0, LOCK_OPENING, // 电机正转中 LOCK_OPEN, // 门锁已打开 LOCK_CLOSING, // 电机反转中 }; uint8_t LockOpen(uint8_t source) { if (state != LOCK_CLOSED) { return 0; // 非闭锁状态,忽略重复开锁 } state = LOCK_OPENING; printf("OPEN source=%d\r\n", source); MotorRun(180); // 电机正转半圈,模拟开锁 state = LOCK_OPEN; StartCloseTimer(5000); // 5秒后自动上锁 return 1; }这个函数里最重要的参数是source,它标识本次开锁来自哪里:1是蓝牙,2是WiFi小程序,3是刷卡,4是按键。在串口调试助手上看到OPEN source=1,就能确定蓝牙路径已经跑通。StartCloseTimer(5000)是软件定时器,不要改成HAL_Delay,因为RC522寻卡和串口中断都需要主循环持续运行。
3.3 RC522 刷卡驱动:寻卡、防碰撞、白名单判断
RC522驱动的核心是PCD_Request和PCD_Anticoll。PCD_Request向射频场发出寻卡请求,PCD_Anticoll从多个卡片中分离出4字节序列号。源码包里的RFID_RC522.c已经封装好,使用时只需要匹配白名单。
// RFID_RC522.c 刷卡识别片段 uint8_t RC522_CheckCard(void) { uint8_t status; uint8_t snr[4]; status = PCD_Request(PICC_REQIDL, g_atqa); // 寻卡 if (status != MI_OK) return status; status = PCD_Anticoll(snr); // 防碰撞,取卡号 if (status != MI_OK) return status; if (memcmp(snr, white_list[0], 4) == 0) { // 与白名单比较 LockOpen(3); // source=3 表示刷卡 } PCD_StopCrypto1(); // 结束加密,释放射频场 return MI_OK; }代码里g_atqa是卡片对Request命令的应答数据,可以忽略。white_list[0]是预存的4字节卡号,这里只放了一张卡。如果需要多卡管理,就要把white_list改成二维数组,并用循环遍历。注意PCD_StopCrypto1()必须每次流程结束后调用,否则下一张卡可能无法正常进入防碰撞流程。
3.4 mqtt.c:ESP8266 如何接入微信小程序的远程指令
mqtt.c的本质是把ESP8266的AT指令封装成几个函数:连WiFi、连Broker、订阅Topic、接收消息。微信小程序下发OPEN后,Broker转发给ESP8266,STM32从串口读取并触发LockOpen(2)。
// mqtt.c 中连接Broker的典型AT序列 void MQTT_Connect(const char *ssid, const char *pwd, const char *broker) { char buf[160]; ESP8266_SendCmd("AT+RST\r\n", "ready", 3000); ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK", 1000); sprintf(buf, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd); ESP8266_SendCmd(buf, "WIFI GOT IP", 10000); sprintf(buf, "AT+MQTTCONN=\"%s\",1883,1,0,\"dev_lock_01\"\r\n", broker); ESP8266_SendCmd(buf, "OK", 5000); }这里的AT+MQTTCONN依赖于ESP8266固件支持MQTT扩展AT指令。如果固件版本老,会返回ERROR,这时要么换固件,要么改用AT+CIPSTART走TCP透传,在STM32端手动拼MQTT报文。源码包里既然用了mqtt.c,说明作者的固件已经支持这个指令,拿到后不要随便刷机,否则还要改一整套发送逻辑。
3.5 调试日志:给四路开锁挂上“追踪ID”
在调试蓝牙、WiFi、刷卡、按键四路输入时,我习惯在串口打印里加上来源编号。修改printf的格式化串,把开锁来源变成可读字符串。
void PrintOpenSource(uint8_t source) { const char *src_str[] = {"UNKNOWN", "BLUETOOTH", "MQTT", "RFID", "KEY"}; printf("OPEN by %s\r\n", src_str[source]); }这样在上位机上能看到是BLE还是MQTT,避免怀疑到错误的驱动上。很多单片机工程只打印OPEN,一旦按键和蓝牙同时触发,根本分不清谁先到。加一个来源枚举,排错效率能提高一半。
4. 微信小程序远程开锁:WSS、MQTT Topic 与指令格式
4.1 远程链路设计:小程序为什么不能直连ESP8266
微信小程序运行在微信客户端里,只能访问HTTPS和WSS协议,不能直接连接局域网IP,更不可能直连ESP8266的TCP端口。因此需要一条中转链路:小程序通过WebSocket连接公网MQTT Broker,ESP8266通过MQTT协议连接同一个Broker,两者通过Topic交换消息。
微信小程序 -> wss://broker.example.com:8084/mqtt -> Topic: lock/device001/cmd ESP8266 -> mqtt://broker.example.com:1883 -> Topic: lock/device001/statecmd主题用于下发指令,state主题用于设备上报状态。ESP8266只要能主动外连网络,就能在公网环境下被小程序控制,不需要公网IP,也不需要路由器端口映射。
4.2 小程序开锁代码:发布OPEN指令
源码包里的小程序工程已经封装好了MQTT客户端,核心代码在pages/index/index.js中。
// pages/index/index.js 开锁事件 const mqtt = require('../../utils/mqtt.min.js'); Page({ openLock() { const clientId = 'wx_lock_' + Math.random().toString(16).substr(2, 8); const client = mqtt.connect('wss://broker.example.com:8084/mqtt', { clientId, timeout: 10, keepalive: 60, clean: true }); client.on('connect', () => { client.publish('lock/device001/cmd', 'OPEN', { qos: 1, retain: false }); client.end(); }); client.on('error', () => { wx.showToast({ title: '连接失败', icon: 'none' }); }); } })这里有两个必须注意的点。第一,clientId必须唯一,如果两台手机使用同一个ClientID,后连接的小程序会把前一个踢下线。我一般在ClientID后追加随机数。第二,qos: 1意味着Broker会重发消息,如果ESP8266侧没有去重,远程开锁可能触发两次。源码包里默认用简单字符串,你应该自行在STM32端记录最近一次消息ID,重复则丢弃。
4.3 MQTT参数与Topic约定
| 参数 | 建议值 | 说明 |
|---|---|---|
| Broker地址 | 云服务器或公共MQTT Broker | 商用必须自建,公共Broker不稳定 |
| WSS端口 | 443或8084 | 小程序要求wss协议 |
| QoS | 1 | 门锁指令建议至少QoS1 |
| cmd Topic | lock/device001/cmd | 小程序发布,设备订阅 |
| state Topic | lock/device001/state | 设备发布,小程序订阅 |
| 指令内容 | OPEN / QUERY | 纯字符串,方便串口查看 |
公共MQTT Broker如broker.emqx.io适合测试,但生产环境不要用。公共Broker没有SLA,高峰期可能断开,而且消息容易被其他topic订阅者看到。换成自建EMQX后,只需改mqtt.connect里的URL,其他逻辑不用动。
4.4 蓝牙开锁APP与HC05配对
Android蓝牙APP直接安装蓝牙开锁APP_1.apk,打开后搜索HC05配对,默认PIN码一般是1234或0000。配对后APP会以串口透传方式向HC05发送字符串OPEN。HC05出厂波特率常见9600,但源码里的USART2不一定设置成9600,可能用的是115200。如果蓝牙连上但门锁不动,先用USB转TTL接HC05,执行AT命令确认参数。
# 用USB转TTL连接HC05,进入AT模式 AT+ORGL AT+ROLE=0 AT+UART=9600,0,0 AT+RESETAT+ROLE=0表示从模式,等待手机连接;AT+UART=9600,0,0分别指波特率9600、停止位1、校验位无。设置后要把STM32端USART2初始化修改成相同波特率,否则串口收到的全是乱码。注意回复OK后需要断电重启才生效。
4.5 用开发者工具快速替换Broker地址
拿到源码包后,用微信开发者工具导入小程序目录。打开app.js文件,找到全局配置:
// app.js 全局配置 globalData: { brokerUrl: 'wss://broker.example.com:8084/mqtt', deviceId: 'lock/device001' }把brokerUrl改成自己的Broker地址。如果还没有服务器,测试阶段可以在开发者工具右上角点击“详情”,勾选“不校验合法域名”,同时把brokerUrl改成ws://,这样能在电脑上联调。但真机预览时,微信强制要求合法域名和wss协议,这个跳过方式只在开发者工具里有效。
5. 编译烧录与故障排查:芯片包、AXF下载和门锁状态上报
5.1 Keil5 安装STM32芯片包
原始工程使用Keil MDK开发,打开.uvprojx之前要先确认软件包里有STM32F1系列支持。点击Pack Installer,搜索STM32F1,安装Keil.STM32F1xx_DFP.2.x.x。如果网络安装失败,到Keil官网下载离线pack双击安装,路径不要带中文。
工程编译前还要确认两个配置。第一,Device选择STM32F103ZET6;第二,C/C++选项卡Define里必须有STM32F10X_HD,USE_STDPERIPH_DRIVER。HD代表高密度Flash,如果漏掉,外设库很多函数不会被条件编译进来,编译会报未定义。
5.2 烧录与AXF调试
使用ST-Link连接STM32的SWDIO、SWCLK、GND和3.3V。在Keil里选择Flash Download,如果已经编译成功,可以直接下载。源码包里的STM32HD.axf也可以直接用ST-Link Utility加载,适合不重新编译只想快速看效果的情况。下载后按复位键,串口调试助手接USART3,波特率取代码中设置的数值,正常会看到启动日志。
5.3 高频故障排查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| HC05连接后数据乱码 | STM32波特率与HC05不匹配 | 用AT设置相同波特率 |
| 微信小程序提示连接失败 | 使用了ws://或域名未备案 | 开发者工具勾选不校验合法域名 |
| RC522偶尔读不到卡 | SPI时钟过高或天线规格不匹配 | 将SPI时钟降到2MHz,卡片放中间 |
| 步进电机不转 | 供电电压不足或相位顺序错 | 独立电源,核对PB0-PB3顺序 |
| ESP8266返回ERROR | AT固件不支持MQTT指令 | 换固件或改用TCP透传 |
其中RC522读不到卡最容易被忽略的是天线线圈。RC522的PCB天线对匹配电容很敏感,不同厂家的模块需要微调发射部分的电阻电容。如果刷卡距离小于1cm,不要先怀疑程序,应该检查模块下方的铺铜是否影响磁场。
5.4 技巧:把门锁状态上报到微信小程序
源码包里的MQTT链路更多是“下发指令”,未必附带状态回传。我拿到后第一件事就是在mqtt.c里增加一个状态发布函数,让开锁完成后设备主动上报。
// mqtt.c 状态上报 void MQTT_PublishState(uint8_t source) { char msg[16]; sprintf(msg, "OPEN_OK:%d", source); ESP8266_SendData("lock/device001/state", msg); }在LockOpen()的最后调用MQTT_PublishState(source),小程序端订阅lock/device001/state后,就能在收到OPEN_OK:1时把页面图标变成蓝色。这样远程开锁有了明确反馈,不会因为网络延迟或丢包,用户连点三次按钮造成重复操作。实际调试时,小程序收到OPEN_OK后要关闭连接,否则长时间保持WebSocket连接会消耗手机电量,而ESP8266侧也最好只在状态变化时发送,不要每秒上报。
本文还有配套的精品资源,点击获取