一、前言
1.1 项目开发背景
随着高校学生宿舍用电设备不断增多,宿舍内部通常同时存在电脑、手机充电器、热水壶、电风扇等多种用电设备,宿舍用电安全问题也越来越受到重视。在日常使用过程中,如果出现线路过载、用电设备异常、可燃气体或烟雾泄漏以及火灾等情况,仅依靠传统的人工巡查和普通报警设备,难以及时获取宿舍内部的实时安全状态。因此,利用嵌入式技术、传感器技术和物联网通信技术构建集安全防护、环境监测和远程管理于一体的宿舍监测系统,具有较强的实际应用价值。
STM32系列单片机具有处理能力较强、接口资源丰富、运行稳定等特点,能够满足多种传感器数据采集、执行设备控制以及通信模块管理等需求。本项目以STM32F103RCT6作为系统主控,通过火焰检测传感器、MQ2烟雾及可燃气体传感器、人体红外热释电传感器以及电力参数采集模块,对宿舍内部的火焰、烟雾、可燃气体、人员活动以及用电状态进行实时监测。当检测到火焰、烟雾、可燃气体、人员入侵或者电流、功率超过设定阈值时,系统能够及时进行声光或相应执行控制,降低异常情况持续发展的风险。
在宿舍门禁管理方面,系统采用SG90舵机实现门锁的开关控制,既可以通过本地数字按键输入密码进行开门,也可以通过手机APP和Windows电脑上位机进行远程开门控制。通过本地按键与远程控制相结合的方式,可以满足宿舍日常门锁使用和远程管理的需求。同时,系统配备LCD显示屏,可以在本地显示采集到的各项监测数据,使使用者能够直接了解当前宿舍的安全及用电状态。
随着物联网技术的发展,传统的本地监测方式逐渐向网络化、远程化方向发展。本项目采用ESP8266无线通信模块,通过MQTT协议将STM32采集的数据上传至华为云物联网平台。Android手机APP和Windows电脑上位机均采用Qt5和C++进行开发,通过华为云物联网平台获取设备数据,并实现相应的远程控制功能。当宿舍出现火焰、人体入侵等异常情况时,APP可以弹出相应的提示信息,使管理人员能够更加及时地了解设备状态。
因此,本项目以STM32嵌入式系统为核心,将传感器检测、执行机构控制、门禁管理、用电监测以及物联网通信等技术进行结合,设计一套面向宿舍场景的安全防护与环境监测系统。通过本地监测与云端远程管理相结合的方式,实现对宿舍安全状态和用电状态的实时掌握,为宿舍安全管理提供一种具有实际应用意义的技术方案。
1.2 设计实现的功能
(1)火焰检测与灭火功能
系统通过火焰检测传感器实时检测宿舍内部是否存在火焰。当检测到火焰时,系统控制蜂鸣器进行报警,同时打开5V水泵进行喷水灭火,并通过手机APP提示检测到火焰。
(2)烟雾与可燃气体检测功能
系统采用MQ2传感器对宿舍内部的烟雾和可燃气体进行检测。当检测到烟雾或可燃气体超过设定阈值时,控制蜂鸣器报警,同时启动5V排风扇进行通风换气。
(3)本地密码门锁控制功能
系统通过4颗数字按键实现本地密码输入,用户输入正确密码后控制SG90舵机开门。舵机通过0°和90°两个角度分别表示门锁的开门和关门状态。
(4)远程门锁控制功能
系统支持通过Android手机APP和Windows电脑上位机进行远程开门控制。远程控制指令通过华为云物联网平台传输至设备端,由STM32控制SG90舵机完成门锁开关。
(5)人体感应与防盗报警功能
系统采用红外热释电人体检测传感器检测宿舍内部是否有人。在防盗模式下,当检测到有人进入时,系统控制蜂鸣器进行报警,同时手机APP弹出提示信息,提示检测到有人入侵。
(6)交流电压、电流及功率监测功能
系统通过串口电力参数采集模块对单相交流电的电压、电流和功率等参数进行采集,并将采集结果传输至STM32进行处理。
(7)用电超限报警与断电保护功能
系统对检测到的线路电流和功率进行判断。当电流或者功率超过设定阈值时,系统控制蜂鸣器进行报警,同时切断宿舍内部插板、插座等负载的供电。
(8)物联网数据上传功能
系统通过ESP8266 WiFi模块连接网络,采用MQTT协议将设备采集的监测数据上传至华为云物联网平台,实现宿舍监测数据的云端管理。
(9)手机APP远程监测功能
Android手机APP通过华为云物联网平台获取设备上传的数据,可以远程查看宿舍当前的安全监测及用电参数等信息,并支持远程开门控制。
(10)Windows电脑上位机功能
系统配套Windows电脑上位机软件,采用Qt5和C++进行开发,通过华为云物联网平台获取设备数据,并支持远程开门控制。
(11)本地LCD数据显示功能
系统采用1.44寸LCD显示屏,对设备采集到的监测数据进行本地显示,方便用户直接查看当前宿舍的安全状态和用电监测数据。
(12)系统本地声响报警功能
系统采用高电平触发的有源蜂鸣器作为报警设备,当检测到火焰、烟雾、可燃气体、人员入侵或用电参数超过设定阈值等情况时,根据对应的报警条件控制蜂鸣器发出报警。
1.3 项目硬件模块组成
(1)STM32F103RCT6主控模块
系统采用STM32F103RCT6作为主控制芯片,负责各类传感器数据采集、监测数据处理、报警判断、执行设备控制、门锁控制以及与ESP8266和电力参数采集模块进行通信。
(2)MQ2烟雾及可燃气体检测模块
采用MQ2传感器对宿舍内部的烟雾和可燃气体进行检测,并将检测结果传输给STM32,用于烟雾及可燃气体异常判断。
(3)火焰检测模块
采用火焰检测传感器进行火焰检测,传感器输出高低电平信号,由STM32读取检测结果,并根据检测状态控制蜂鸣器和水泵。
(4)红外热释电人体检测模块
采用红外热释电人体检测传感器检测宿舍内部是否存在人员,传感器通过高低电平输出检测结果,为系统人体感应和防盗报警提供检测信号。
(5)电力参数采集模块
采用串口接口的电力参数采集模块,对单相交流电的电压、电流、功率等电力参数进行采集,并通过串口与STM32进行数据通信。
(6)1.44寸LCD显示模块
采用1.44寸LCD显示屏,用于显示系统采集到的各项监测数据,包括环境安全状态以及电压、电流、功率等用电参数。
(7)ESP8266 WiFi通信模块
采用ESP8266作为系统无线联网模块,负责设备与网络之间的数据通信,通过MQTT协议将STM32采集的数据上传至华为云物联网平台,同时接收云端下发的远程控制数据。
(8)SG90舵机门锁模块
采用SG90舵机实现宿舍门锁的开关控制,通过控制舵机转动角度实现门锁状态切换,其中0°和90°分别对应门锁的开门和关门状态。
(9)数字按键输入模块
系统采用4颗数字按键作为本地密码输入设备,用户通过按键输入门锁密码,STM32对输入的密码进行判断,并控制SG90舵机完成开门操作。
(10)有源蜂鸣器报警模块
采用高电平触发的有源蜂鸣器作为系统报警器件,当检测到火焰、烟雾、可燃气体、人员入侵或电流、功率超过设定阈值等情况时,由STM32控制蜂鸣器进行报警。
(11)5V排风扇模块
采用5V小型风扇作为通风执行设备,当MQ2检测到烟雾或可燃气体异常时,由STM32控制排风扇启动,实现宿舍内部通风换气。
(12)5V抽水水泵模块
采用5V抽水小电机作为灭火水泵,当火焰检测传感器检测到火焰后,由STM32控制水泵启动,用于喷水灭火。
(13)负载断电控制模块
系统设置负载电源控制部分,当检测到线路电流或功率超过设定阈值时,由STM32控制负载供电回路断开,从而切断宿舍内部插板、插座等负载的供电。
(14)5V 2A稳压电源模块
系统采用5V 2A稳压电源作为设备供电电源,为STM32主控、传感器、ESP8266、LCD、蜂鸣器、SG90舵机、排风扇和水泵等模块提供工作电源。
项目开发使用的全部软件工具以及源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
1.4 系统框架图
1.5 运行流程图
二、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA。
2.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
2.2 开通物联网服务
地址: https://www.huaweicloud.com/product/iothub.html
开通免费单元。
点击立即创建。
正在创建标准版实例,需要等待片刻。
创建完成之后,点击详情。 可以看到标准版实例的设备接入端口和地址。
下面框起来的就是端口号和域名
点击实例名称,可以查看当前免费单元的配置情况。
开通之后,点击接入信息,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址: dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com
根据域名地址得到IP地址信息:
打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。
Microsoft Windows [版本 10.0.19045.5011]
(c) Microsoft Corporation。保留所有权利。
C:UsersLenovo>ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 37ms,最长 = 37ms,平均 = 37ms
C:UsersLenovo>
MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口合适。
2.3 创建产品
链接:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-dev/all-product?instanceId=03c5c68c-e588-458c-90c3-9e4c640be7af
(1)创建产品
(2)填写产品信息
根据自己产品名字填写,下面的设备类型选择自定义类型。
(3)产品创建成功
创建完成之后点击查看详情。
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。
你可以根据自己的产品进行创建。
比如:
烟雾可以叫 MQ2
温度可以叫 Temperature
湿度可以叫 humidity
火焰可以叫 flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。
先点击自定义模型。
再创建一个服务ID。
接着点击新增属性。
2.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
(4)设备创建完成
(5)设备详情
2.5 MQTT协议主题订阅与发布
(1)MQTT协议介绍
当前的设备是采用MQTT协议与华为云平台进行通信。
MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。
MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。
华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
业务流程:
(2)华为云平台MQTT协议使用限制
| 描述 | 限制 |
|---|---|
| 支持的MQTT协议版本 | 3.1.1 |
| 与标准MQTT协议的区别 | 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg |
| MQTTS支持的安全等级 | 采用TCP通道基础 + TLS协议(最高TLSv1.3版本) |
| 单帐号每秒最大MQTT连接请求数 | 无限制 |
| 单个设备每分钟支持的最大MQTT连接数 | 1 |
| 单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 | 3KB/s |
| MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 | 1MB |
| MQTT连接心跳时间建议值 | 心跳时间限定为30至1200秒,推荐设置为120秒 |
| 产品是否支持自定义Topic | 支持 |
| 消息发布与订阅 | 设备只能对自己的Topic进行消息发布与订阅 |
| 每个订阅请求的最大订阅数 | 无限制 |
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主题发布格式
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}
2.6 MQTT三元组
MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。
接下来介绍,华为云平台的MQTT三元组参数如何得到。
(1)MQTT服务器地址
要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。
帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。
根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元组
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
2.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
MQTT软件下载地址【免费】: https://download.csdn.net/download/xiaolong1126626497/89928772
(1)填入登录信息
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)MQTT登录测试参数总结
MQTT服务器: 117.78.5.125
MQTT端口号: 183
//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //发布
发布的数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}
2.8 创建IAM账户
创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证
项目凭证:
28add376c01e4a61ac8b621c714bf459
【2】创建IAM用户
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。
点击左上角创建用户。
创建成功:
【3】创建完成
用户信息如下:
主用户名 l19504562721
IAM用户 ds_abc
密码 DS12345678
2.9 获取影子数据
帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
设备影子介绍:
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性
简单来说:设备影子就是保存,设备最新上传的一次数据。
我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。
如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。
调试完成看右下角的响应体,就是返回的影子数据。
设备影子接口返回的数据如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。
链接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
三、上位机开发
3.1 Qt开发环境安装
Qt的中文官网: https://www.qt.io/zh-cn/
QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6
打开下载链接后选择下面的版本进行下载:
如果下载不了,可以在网盘里找到安装包下载: 飞书文档记录的网盘地址:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。
选择编译器: (一定要看清楚了)
3.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
3.3 切换编译器
在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。
目前新建工程的时候选择了2种编译器。 一种是mingw32这个编译Windows下运行的程序。 一种是Android编译器,可以生成Android手机APP。
不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。
windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。
下面我这里就选择的 mingw32这个编译器,编译Windows下运行的程序。
3.4 编译测试功能
创建完毕之后,编译测试一下功能是否OK。
点击左下角的绿色三角形按钮。
正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。
3.5 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
3.6 设计代码
widget.h(头文件)
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QtMqtt/QMqttClient>
#include <QTimer>
#include <QMap>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void onMQTTStateChanged(QMqttClient::ClientState state);
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
void onPublishSuccess();
void onTimeout();
void on_connectBtn_clicked();
void on_unlockBtn_clicked();
private:
Ui::Widget *ui;
QMqttClient *mqttClient;
QTimer *keepAliveTimer;
// 华为云IoT配置信息(请替换为您自己的)
QString serverHost = "your-server.iot-mqtts.cn-north-4.myhuaweicloud.com";
QString clientId = "your-device-id_0_0_20260825"; // 设备ID
QString userName = "your-device-id"; // 设备ID
QString password = "your-password"; // 设备密钥生成的密码
void parseDeviceData(const QJsonObject &obj);
void showAlertMessage(const QString &title, const QString &msg);
};
#endif // WIDGET_H
widget.cpp(核心实现)
#include "widget.h"
#include "ui_widget.h"
#include <QtMqtt/QMqttTopicName>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>
#include <QDateTime>
#include <QCryptographicHash>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("宿舍安全防护系统 - QT客户端");
// 初始化MQTT客户端
mqttClient = new QMqttClient(this);
mqttClient->setHostname(serverHost);
mqttClient->setPort(1883);
mqttClient->setClientId(clientId);
mqttClient->setUsername(userName);
// 华为云IoT要求密码为:设备密钥的HMAC-SHA256加密结果(需根据实际规则生成)
// 此处简化,实际项目需要动态生成
mqttClient->setPassword(password);
// 连接状态变化信号
connect(mqttClient, &QMqttClient::stateChanged, this, &Widget::onMQTTStateChanged);
// 接收到消息信号
connect(mqttClient, &QMqttClient::messageReceived, this, &Widget::onMessageReceived);
// 保持连接定时器(每30秒发一次ping)
keepAliveTimer = new QTimer(this);
connect(keepAliveTimer, &QTimer::timeout, this, &Widget::onTimeout);
keepAliveTimer->start(30000);
// 界面初始状态
ui->statusLabel->setText("未连接");
ui->unlockBtn->setEnabled(false);
}
Widget::~Widget()
{
delete ui;
}
// ------------------ MQTT连接与订阅 ------------------
void Widget::on_connectBtn_clicked()
{
if (mqttClient->state() == QMqttClient::Connected) {
mqttClient->disconnectFromHost();
return;
}
qDebug() << "正在连接华为云IoT...";
mqttClient->connectToHost();
}
void Widget::onMQTTStateChanged(QMqttClient::ClientState state)
{
switch (state) {
case QMqttClient::Connected:
ui->statusLabel->setText("已连接");
ui->connectBtn->setText("断开");
ui->unlockBtn->setEnabled(true);
// 订阅设备属性上报主题(华为云格式)
QString subTopic = "$oc/devices/" + clientId + "/sys/properties/report";
mqttClient->subscribe(QMqttTopicName(subTopic), 1);
// 订阅命令下发响应(可选)
subTopic = "$oc/devices/" + clientId + "/sys/commands/response";
mqttClient->subscribe(QMqttTopicName(subTopic), 1);
qDebug() << "MQTT连接成功,已订阅主题";
break;
case QMqttClient::Disconnected:
ui->statusLabel->setText("未连接");
ui->connectBtn->setText("连接");
ui->unlockBtn->setEnabled(false);
qDebug() << "MQTT已断开";
break;
default:
break;
}
}
// ------------------ 接收并解析设备上报数据 ------------------
void Widget::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
QString topicStr = topic.name();
qDebug() << "收到消息:" << topicStr << message;
QJsonDocument doc = QJsonDocument::fromJson(message);
if (doc.isNull()) {
qWarning() << "无效JSON";
return;
}
QJsonObject obj = doc.object();
// 华为云设备影子上报的数据在 "services" 数组中
if (obj.contains("services") && obj["services"].isArray()) {
QJsonArray services = obj["services"].toArray();
for (const auto &serviceVal : services) {
QJsonObject serviceObj = serviceVal.toObject();
if (serviceObj.contains("properties") && serviceObj["properties"].isObject()) {
QJsonObject properties = serviceObj["properties"].toObject();
parseDeviceData(properties);
}
}
} else if (obj.contains("properties")) {
// 直接解析属性
parseDeviceData(obj["properties"].toObject());
}
}
void Widget::parseDeviceData(const QJsonObject &props)
{
// ----- 环境参数 -----
if (props.contains("temperature")) {
double temp = props["temperature"].toDouble();
ui->tempLabel->setText(QString::number(temp, 'f', 1) + " °C");
}
if (props.contains("humidity")) {
double humi = props["humidity"].toDouble();
ui->humiLabel->setText(QString::number(humi, 'f', 1) + " %");
}
if (props.contains("smoke")) {
int smoke = props["smoke"].toInt();
ui->smokeLabel->setText(QString::number(smoke));
}
if (props.contains("gas")) {
int gas = props["gas"].toInt();
ui->gasLabel->setText(QString::number(gas));
}
// ----- 火焰 & 人体入侵 报警弹窗 -----
if (props.contains("flame")) {
int flame = props["flame"].toInt();
ui->flameLabel->setText(flame ? "检测到火焰!" : "正常");
if (flame == 1) {
showAlertMessage("火焰报警", "⚠️ 检测到火焰!已启动水泵灭火!");
}
}
if (props.contains("pir")) {
int pir = props["pir"].toInt();
ui->pirLabel->setText(pir ? "有人" : "无人");
// 防盗模式下报警(假设防盗模式开启时pir=1)
if (pir == 1) {
showAlertMessage("入侵报警", "⚠️ 检测到非法入侵!");
}
}
// ----- 电力参数 -----
if (props.contains("voltage")) {
double v = props["voltage"].toDouble();
ui->voltageLabel->setText(QString::number(v, 'f', 1) + " V");
}
if (props.contains("current")) {
double cur = props["current"].toDouble();
ui->currentLabel->setText(QString::number(cur, 'f', 2) + " A");
}
if (props.contains("power")) {
double p = props["power"].toDouble();
ui->powerLabel->setText(QString::number(p, 'f', 1) + " W");
}
// ----- 门锁状态 -----
if (props.contains("doorlock")) {
int door = props["doorlock"].toInt();
ui->doorLabel->setText(door == 0 ? "已关门" : "已开门");
}
}
// ------------------ 远程开门控制(发布命令) ------------------
void Widget::on_unlockBtn_clicked()
{
if (mqttClient->state() != QMqttClient::Connected) {
QMessageBox::warning(this, "提示", "MQTT未连接,请先连接!");
return;
}
// 华为云命令下发格式(同步命令)
QJsonObject cmdObj;
cmdObj["command_name"] = "door_control";
QJsonObject paras;
paras["angle"] = 90; // 90°开门,0°关门
cmdObj["paras"] = paras;
QJsonDocument doc(cmdObj);
QByteArray payload = doc.toJson();
QString cmdTopic = "$oc/devices/" + clientId + "/sys/commands/request_id=" +
QString::number(QDateTime::currentSecsSinceEpoch());
mqttClient->publish(QMqttTopicName(cmdTopic), payload, 1);
qDebug() << "发布开门指令:" << payload;
ui->statusLabel->setText("已发送开门指令,等待执行...");
}
// ------------------ 辅助功能:弹窗提示 ------------------
void Widget::showAlertMessage(const QString &title, const QString &msg)
{
// 在Android和Windows下都弹窗提示
QMessageBox::critical(this, title, msg);
// 同时可将信息显示在界面的日志区域
ui->logTextEdit->append(QDateTime::currentDateTime().toString("hh:mm:ss") + " " + title + ": " + msg);
}
// ------------------ 心跳保持 ------------------
void Widget::onTimeout()
{
if (mqttClient->state() == QMqttClient::Connected) {
// 发送PING保持连接(QT MQTT内部自动处理,此处仅做log)
// qDebug() << "保持连接心跳";
}
}
// 程序退出时断开连接
void Widget::closeEvent(QCloseEvent *event)
{
if (mqttClient->state() == QMqttClient::Connected) {
mqttClient->disconnectFromHost();
}
event->accept();
}
配套 UI 界面(widget.ui)布局
| 控件名称 | 类型 | 显示内容 |
|---|---|---|
connectBtn |
QPushButton | 连接/断开 |
statusLabel |
QLabel | 连接状态 |
tempLabel |
QLabel | 温度 |
humiLabel |
QLabel | 湿度 |
smokeLabel |
QLabel | 烟雾值 |
gasLabel |
QLabel | 可燃气体值 |
flameLabel |
QLabel | 火焰状态 |
pirLabel |
QLabel | 人体检测 |
voltageLabel |
QLabel | 电压 |
currentLabel |
QLabel | 电流 |
powerLabel |
QLabel | 功率 |
doorLabel |
QLabel | 门锁状态 |
unlockBtn |
QPushButton | 开门按钮 |
logTextEdit |
QTextEdit | 日志显示区域 |
项目配置要点(pro 文件)
QT += core widgets mqtt
TARGET = DormitorySecurity
TEMPLATE = app
# Android 需要添加权限
android: {
QT += androidextras
}
华为云侧配置提醒
1. 在华为云IoT平台创建产品、设备,获取 Device ID 和 Device Secret。
2. 设备密码需使用 HMAC-SHA256 算法生成,格式为:DeviceSecret + 时间戳(具体参考华为云文档)。
3. 设备上报的数据格式必须与华为云物模型一致,否则解析失败。
完整项目结构
DormitorySecurity/
├── DormitorySecurity.pro
├── widget.h
├── widget.cpp
├── widget.ui
└── main.cpp
四、STM32代码设计
4.1 硬件连线说明
(1)STM32F103RCT6主控模块
STM32F103RCT6作为整个系统的核心控制器,各传感器、LCD显示屏、ESP8266、按键、电力参数采集模块以及执行设备均与STM32连接。STM32负责采集传感器数据、处理监测结果,并根据判断结果控制蜂鸣器、水泵、排风扇、SG90舵机以及负载供电控制。
(2)MQ2烟雾及可燃气体检测模块
MQ2模块的VCC接系统5V电源,GND接系统GND,数字输出端DO连接STM32的一个GPIO输入引脚。STM32读取MQ2输出的高低电平,用于判断当前是否检测到烟雾或可燃气体。
(3)火焰检测传感器
火焰检测传感器VCC接5V电源,GND接系统GND,数字输出端DO连接STM32的GPIO输入引脚。STM32通过读取高低电平判断是否检测到火焰。
(4)红外热释电人体检测传感器
人体检测模块VCC接5V电源,GND接系统GND,OUT输出端连接STM32的GPIO输入引脚。当检测到人体活动时,传感器输出相应的高低电平信号,STM32据此判断是否有人存在。
(5)4颗数字按键模块
4颗数字按键分别连接STM32的4个GPIO输入引脚,按键另一端连接GND,并采用STM32内部上拉或外部上拉方式进行按键检测。STM32通过读取4颗按键的状态获取密码输入信息。
(6)1.44寸LCD显示屏
1.44寸LCD显示屏与STM32的SPI相关GPIO进行连接,包括VCC、GND、SCK、MOSI、CS、DC、RST等信号线。STM32通过SPI通信方式向LCD发送显示数据,用于显示系统当前的监测数据。
(7)ESP8266 WiFi模块
ESP8266的VCC连接对应的电源,GND连接系统GND,TXD连接STM32的USART_RX,RXD连接STM32的USART_TX。STM32通过串口与ESP8266进行数据通信,由ESP8266负责连接WiFi网络,并通过MQTT协议与华为云物联网平台进行数据交互。
(8)电力参数采集模块
电力参数采集模块的串口TX连接STM32的USART_RX,串口RX连接STM32的USART_TX,GND与STM32系统GND连接,电源按照具体模块规格连接。STM32通过串口读取模块发送的电压、电流、功率等参数。
(9)SG90舵机模块
SG90舵机的VCC接5V电源,GND接系统GND,PWM信号线连接STM32的定时器PWM输出引脚。STM32通过PWM控制舵机转动角度,使用**0°和90°**两个角度分别表示门锁的开门和关门状态。
(10)有源蜂鸣器模块
有源蜂鸣器的控制端连接STM32的GPIO输出引脚,电源端接对应电源,GND接系统GND。由于采用高电平触发方式,STM32输出高电平时蜂鸣器启动,用于火焰、烟雾、可燃气体、人体入侵以及电流或功率超限等情况的报警。
(11)5V排风扇模块
5V小风扇不能直接由STM32 GPIO引脚驱动,应通过相应的开关驱动电路进行控制。驱动电路控制端连接STM32 GPIO,风扇使用5V电源供电。当MQ2检测到烟雾或可燃气体异常时,STM32输出控制信号启动风扇进行通风换气。
(12)5V水泵模块
5V抽水小电机通过开关驱动电路与STM32连接,水泵使用5V电源供电,STM32 GPIO连接驱动电路控制端。当火焰检测传感器检测到火焰后,STM32输出控制信号启动水泵喷水。
(13)宿舍负载断电控制模块
负载供电控制部分的控制端连接STM32 GPIO输出引脚。当检测到电流或功率超过设定阈值时,STM32输出控制信号,使负载供电回路断开,从而切断宿舍内部插板、插座等负载的供电。
(14)5V 2A稳压电源模块
系统采用5V 2A稳压电源作为主要供电电源。电源正极连接系统5V电源线路,负极连接系统GND,为STM32及各低压模块提供电源。ESP8266、SG90舵机、水泵和风扇等模块工作时的实际电流需求需要单独考虑,避免所有负载直接共用单片机板载电源造成供电不足。
主要通信及控制连接关系
| 模块 | STM32连接方式 | 主要作用 |
|---|---|---|
| MQ2 | GPIO输入 | 烟雾、可燃气体检测 |
| 火焰传感器 | GPIO输入 | 火焰检测 |
| 人体红外传感器 | GPIO输入 | 人体感应 |
| 4颗数字按键 | GPIO输入 | 本地密码输入 |
| LCD | SPI/GPIO | 本地数据显示 |
| ESP8266 | USART | WiFi联网、MQTT通信 |
| 电力参数采集模块 | USART | 电压、电流、功率数据采集 |
| SG90 | 定时器PWM | 门锁开关 |
| 蜂鸣器 | GPIO输出 | 声音报警 |
| 5V风扇 | GPIO+驱动电路 | 通风换气 |
| 5V水泵 | GPIO+驱动电路 | 喷水灭火 |
| 负载断电控制 | GPIO+驱动电路 | 切断宿舍负载供电 |
**特别说明:**本项目涉及单相交流市电测量和宿舍负载断电控制,交流侧与STM32低压控制侧必须进行可靠的电气隔离,实际制作时应采用符合额定电压、电流要求的电力参数采集模块和隔离型控制器件,不能直接将220V交流电接入STM32或普通低压模块。
4.2 项目完整代码设计
【1】main.c 完整代码
/**
******************************************************************************
* @file main.c
* @author 宿舍安全防护系统
* @version V1.0
* @date 2026-08-25
* @brief 基于STM32F103RCT6的宿舍安全防护与环境监测系统主程序
* - 支持火焰检测+水泵灭火
* - 支持烟雾/可燃气体检测+排风扇
* - 支持4位按键密码开门+远程开门(SG90舵机)
* - 支持人体红外检测+防盗报警
* - 支持电力参数监测(串口读取)
* - 支持LCD显示(1.44寸)
* - 支持ESP8266+MQTT上传华为云
******************************************************************************
*/
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>
/*---------------------- 硬件引脚定义(寄存器方式) ----------------------*/
// LED指示灯 (PC13)
#define LED_ON() GPIOC->BRR = (1<<13)
#define LED_OFF() GPIOC->BSRR = (1<<13)
// 蜂鸣器 (PA8) - 高电平触发
#define BEEP_ON() GPIOA->BSRR = (1<<8)
#define BEEP_OFF() GPIOA->BRR = (1<<8)
// 火焰传感器 (PA0) - 高电平表示有火焰
#define FLAME_READ() ((GPIOA->IDR & (1<<0)) ? 1 : 0)
// 人体红外传感器 (PA1) - 高电平表示有人
#define PIR_READ() ((GPIOA->IDR & (1<<1)) ? 1 : 0)
// 烟雾传感器 (PA2) - ADC采集
#define SMOKE_ADC_CH ADC_Channel_2
// 可燃气体传感器 (PA3) - ADC采集
#define GAS_ADC_CH ADC_Channel_3
// 舵机控制 (PA6) - PWM输出
#define SERVO_PWM_CH TIM3_CH1
// 排风扇控制 (PA7)
#define FAN_ON() GPIOA->BSRR = (1<<7)
#define FAN_OFF() GPIOA->BRR = (1<<7)
// 水泵控制 (PB0)
#define PUMP_ON() GPIOB->BSRR = (1<<0)
#define PUMP_OFF() GPIOB->BRR = (1<<0)
// 门锁继电器/负载控制 (PB1) - 过载时切断电源
#define RELAY_ON() GPIOB->BSRR = (1<<1)
#define RELAY_OFF() GPIOB->BRR = (1<<1)
// 按键矩阵 (4位数字按键 - PB12~PB15)
#define KEY1_READ() ((GPIOB->IDR & (1<<12)) ? 0 : 1) // 按键1
#define KEY2_READ() ((GPIOB->IDR & (1<<13)) ? 0 : 1) // 按键2
#define KEY3_READ() ((GPIOB->IDR & (1<<14)) ? 0 : 1) // 按键3
#define KEY4_READ() ((GPIOB->IDR & (1<<15)) ? 0 : 1) // 按键4
/*---------------------- 全局变量定义 ----------------------*/
// 传感器数据
typedef struct {
uint16_t smoke; // 烟雾值 (0-4095)
uint16_t gas; // 可燃气体值 (0-4095)
uint8_t flame; // 火焰状态 (0/1)
uint8_t pir; // 人体状态 (0/1)
float voltage; // 电压 (V)
float current; // 电流 (A)
float power; // 功率 (W)
uint8_t doorLock; // 门锁状态 (0=关门, 1=开门)
uint8_t fanStatus; // 排风扇状态
uint8_t pumpStatus; // 水泵状态
uint8_t alarmStatus; // 报警状态
} SensorData_t;
SensorData_t g_sensorData = {0};
// 密码相关
#define PASSWORD_LEN 4
const uint8_t g_password[PASSWORD_LEN] = {1, 2, 3, 4}; // 默认密码:1234
uint8_t g_keyBuffer[PASSWORD_LEN] = {0};
uint8_t g_keyIndex = 0;
uint8_t g_doorLocked = 1; // 1=锁定, 0=解锁
// 系统模式
#define MODE_NORMAL 0
#define MODE_ANTI_THEFT 1
uint8_t g_systemMode = MODE_NORMAL;
// 电力参数阈值
#define CURRENT_THRESHOLD 5.0f // 5A
#define POWER_THRESHOLD 1000.0f // 1000W
// 串口缓存
char g_uartBuf[256];
uint8_t g_uartRxFlag = 0;
// 定时器计数
volatile uint32_t g_sysTick = 0;
volatile uint8_t g_1sFlag = 0;
volatile uint8_t g_500msFlag = 0;
// ESP8266状态
uint8_t g_esp8266_ready = 0;
/*---------------------- 函数声明 ----------------------*/
void SystemClock_Config(void);
void GPIO_Init(void);
void ADC_Init(void);
void TIM3_PWM_Init(void);
void USART1_Init(void);
void USART2_Init(void);
void I2C_Init(void);
void LCD_Init(void);
void LCD_ShowString(uint8_t x, uint8_t y, const char *str);
void LCD_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void LCD_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t dec);
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
// 传感器读取函数
void Read_All_Sensors(void);
void Read_PowerMeter(void);
void Process_Servo(uint8_t angle);
void Control_Door(uint8_t open);
// 按键处理
void Key_Scan(void);
void Check_Password(void);
// 报警处理
void Alarm_Process(void);
void Check_Threshold(void);
// ESP8266和MQTT
void ESP8266_SendCommand(const char *cmd);
void ESP8266_SendData(const char *data);
void MQTT_Publish_SensorData(void);
// 串口中断处理
void USART1_IRQHandler(void);
void USART2_IRQHandler(void);
/*---------------------- 主函数 ----------------------*/
int main(void)
{
// 关闭所有中断
__disable_irq();
// 系统时钟配置 - 72MHz
SystemClock_Config();
// 初始化硬件
GPIO_Init();
ADC_Init();
TIM3_PWM_Init();
USART1_Init(); // 用于ESP8266通信
USART2_Init(); // 用于电力参数模块
I2C_Init(); // 用于LCD(如果是I2C接口)
LCD_Init();
// 初始状态
BEEP_OFF();
FAN_OFF();
PUMP_OFF();
RELAY_ON(); // 默认供电
LED_ON(); // 指示灯亮
// 初始化舵机 - 关门状态
Control_Door(0);
// 使能中断
__enable_irq();
// 初始化ESP8266并连接WiFi
ESP8266_SendCommand("AT+RSTrn");
Delay_ms(3000);
ESP8266_SendCommand("AT+CWMODE=1rn");
Delay_ms(500);
ESP8266_SendCommand("AT+CWJAP="SSID","PASSWORD"rn"); // 替换为实际WiFi
Delay_ms(5000);
ESP8266_SendCommand("AT+MQTTUSERCFG=0,1,"your-device-id","your-user","your-password",0,0,""rn");
Delay_ms(500);
ESP8266_SendCommand("AT+MQTTCONN=0,"your-server.iot-mqtts.cn-north-4.myhuaweicloud.com",1883,0rn");
Delay_ms(1000);
g_esp8266_ready = 1;
// 主循环
while(1)
{
// 1秒定时任务
if(g_1sFlag)
{
g_1sFlag = 0;
// 读取所有传感器
Read_All_Sensors();
// 读取电力参数
Read_PowerMeter();
// 检查阈值
Check_Threshold();
// 报警处理
Alarm_Process();
// 更新LCD显示
LCD_ShowString(0, 0, "Dormitory Security");
LCD_ShowString(0, 16, "Smoke:");
LCD_ShowNum(56, 16, g_sensorData.smoke, 4);
LCD_ShowString(0, 32, "Gas:");
LCD_ShowNum(56, 32, g_sensorData.gas, 4);
LCD_ShowString(0, 48, "Flame:");
LCD_ShowNum(56, 48, g_sensorData.flame, 1);
LCD_ShowString(0, 64, "PIR:");
LCD_ShowNum(56, 64, g_sensorData.pir, 1);
LCD_ShowString(96, 0, "V:");
LCD_ShowFloat(112, 0, g_sensorData.voltage, 1);
LCD_ShowString(96, 16, "A:");
LCD_ShowFloat(112, 16, g_sensorData.current, 2);
LCD_ShowString(96, 32, "W:");
LCD_ShowFloat(112, 32, g_sensorData.power, 1);
LCD_ShowString(96, 48, "Door:");
LCD_ShowNum(136, 48, g_doorLocked, 1);
// MQTT上传数据到华为云
if(g_esp8266_ready)
{
MQTT_Publish_SensorData();
}
// 指示灯闪烁
LED_ON();
Delay_ms(50);
LED_OFF();
}
// 500ms按键扫描
if(g_500msFlag)
{
g_500msFlag = 0;
Key_Scan();
}
// 串口数据处理
if(g_uartRxFlag)
{
// 处理电力参数模块返回的数据
// 实际解析代码根据模块协议编写
g_uartRxFlag = 0;
}
}
}
/*---------------------- 硬件初始化函数 ----------------------*/
/**
* @brief 系统时钟配置为72MHz
*/
void SystemClock_Config(void)
{
// 开启外部高速晶振HSE
RCC->CR |= (1<<16);
while(!(RCC->CR & (1<<17)));
// 配置PLL: HSE 8MHz * 9 = 72MHz
RCC->CFGR |= (1<<16); // PLLSRC = HSE
RCC->CFGR &= ~(0xF<<18);
RCC->CFGR |= (7<<18); // PLLMUL = 9
RCC->CFGR |= (2<<13); // ADCPRE = 6分频
// 开启PLL
RCC->CR |= (1<<24);
while(!(RCC->CR & (1<<25)));
// 切换系统时钟到PLL
RCC->CFGR |= (2<<0);
while((RCC->CFGR & (3<<2)) != (2<<2));
// 设置AHB/APB分频
RCC->CFGR &= ~(0xF<<4);
RCC->CFGR |= (4<<4); // AHB = 72MHz
RCC->CFGR |= (4<<8); // APB1 = 36MHz
RCC->CFGR |= (2<<11); // APB2 = 72MHz
// 更新系统时钟变量
SystemCoreClock = 72000000;
}
/**
* @brief GPIO初始化
*/
void GPIO_Init(void)
{
// 开启GPIO时钟
RCC->APB2ENR |= (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);
RCC->APB1ENR |= (1<<3); // TIM3时钟
// PA0 - 火焰传感器输入
GPIOA->CRL &= ~(0xF<<0);
GPIOA->CRL |= (4<<0); // 浮空输入
// PA1 - 人体红外输入
GPIOA->CRL &= ~(0xF<<4);
GPIOA->CRL |= (4<<4);
// PA2, PA3 - ADC输入
GPIOA->CRL &= ~(0xF<<8);
GPIOA->CRL |= (4<<8);
GPIOA->CRL &= ~(0xF<<12);
GPIOA->CRL |= (4<<12);
// PA6 - 舵机PWM输出 (TIM3_CH1)
GPIOA->CRL &= ~(0xF<<24);
GPIOA->CRL |= (0xB<<24); // 复用推挽输出 50MHz
// PA7 - 排风扇控制
GPIOA->CRL &= ~(0xF<<28);
GPIOA->CRL |= (3<<28); // 推挽输出 50MHz
// PA8 - 蜂鸣器
GPIOA->CRH &= ~(0xF<<0);
GPIOA->CRH |= (3<<0);
// PB0 - 水泵控制
GPIOB->CRL &= ~(0xF<<0);
GPIOB->CRL |= (3<<0);
// PB1 - 继电器控制
GPIOB->CRL &= ~(0xF<<4);
GPIOB->CRL |= (3<<4);
// PB12~PB15 - 按键输入
GPIOB->CRH &= ~(0xF<<16);
GPIOB->CRH |= (8<<16); // 上拉输入
GPIOB->CRH &= ~(0xF<<20);
GPIOB->CRH |= (8<<20);
GPIOB->CRH &= ~(0xF<<24);
GPIOB->CRH |= (8<<24);
GPIOB->CRH &= ~(0xF<<28);
GPIOB->CRH |= (8<<28);
// PC13 - LED
GPIOC->CRH &= ~(0xF<<20);
GPIOC->CRH |= (3<<20);
}
/**
* @brief ADC初始化 (使用DMA模式)
*/
void ADC_Init(void)
{
// 开启ADC和DMA时钟
RCC->APB2ENR |= (1<<9);
RCC->AHBENR |= (1<<0);
// 配置ADC
ADC1->CR2 &= ~(1<<0); // 关闭ADC
ADC1->CR1 &= ~(0xF<<16); // 独立模式
ADC1->CR1 |= (0<<16);
ADC1->CR2 |= (1<<1); // 连续转换
ADC1->CR2 |= (1<<8); // DMA模式
ADC1->CR2 &= ~(1<<11); // 右对齐
ADC1->CR2 |= (1<<20); // 外部触发
ADC1->CR2 |= (1<<17); // 软件触发
// 设置采样时间
ADC1->SMPR1 |= (7<<6); // 通道2 239.5周期
ADC1->SMPR1 |= (7<<9); // 通道3 239.5周期
// 开启ADC
ADC1->CR2 |= (1<<0);
// 校准
ADC1->CR2 |= (1<<3);
while(ADC1->CR2 & (1<<3));
}
/**
* @brief TIM3 PWM初始化 (用于舵机)
*/
void TIM3_PWM_Init(void)
{
// 定时器配置
TIM3->PSC = 719; // 72MHz / 720 = 100kHz
TIM3->ARR = 2000; // 100kHz / 2000 = 50Hz (20ms周期)
// 通道1 PWM模式
TIM3->CCMR1 |= (6<<4); // PWM模式1
TIM3->CCMR1 |= (1<<3); // 预装载使能
TIM3->CCER |= (1<<0); // 输出使能
TIM3->CCR1 = 150; // 1.5ms (90°)
// 使能定时器
TIM3->CR1 |= (1<<0);
// 使能预装载
TIM3->CR1 |= (1<<7);
}
/**
* @brief USART1初始化 (ESP8266)
*/
void USART1_Init(void)
{
// 使能USART1时钟
RCC->APB2ENR |= (1<<14);
// PA9(TX), PA10(RX)
GPIOA->CRH &= ~(0xF<<4);
GPIOA->CRH |= (0xB<<4); // 复用推挽
GPIOA->CRH &= ~(0xF<<8);
GPIOA->CRH |= (4<<8); // 浮空输入
// 波特率 115200
USART1->BRR = 72000000 / 115200; // 625
USART1->CR1 |= (1<<2) | (1<<3); // RX, TX使能
USART1->CR1 |= (1<<5); // RX中断使能
USART1->CR1 |= (1<<13); // 使能USART
NVIC_EnableIRQ(USART1_IRQn);
NVIC_SetPriority(USART1_IRQn, 1);
}
/**
* @brief USART2初始化 (电力参数模块)
*/
void USART2_Init(void)
{
RCC->APB1ENR |= (1<<17);
// PA2(TX), PA3(RX)
GPIOA->CRL &= ~(0xF<<8);
GPIOA->CRL |= (0xB<<8);
GPIOA->CRL &= ~(0xF<<12);
GPIOA->CRL |= (4<<12);
USART2->BRR = 72000000 / 9600; // 9600波特率
USART2->CR1 |= (1<<2) | (1<<3);
USART2->CR1 |= (1<<5);
USART2->CR1 |= (1<<13);
NVIC_EnableIRQ(USART2_IRQn);
NVIC_SetPriority(USART2_IRQn, 2);
}
/**
* @brief I2C初始化 (用于LCD - 假设使用I2C接口)
*/
void I2C_Init(void)
{
// 此处根据实际LCD接口初始化
// 如果是SPI接口, 需修改对应代码
}
/**
* @brief LCD初始化
*/
void LCD_Init(void)
{
// 根据具体的LCD驱动芯片编写
// 例如ST7735S驱动
}
/*---------------------- 传感器读取函数 ----------------------*/
/**
* @brief 读取所有传感器数据
*/
void Read_All_Sensors(void)
{
// 读取ADC值 - 烟雾
ADC1->SQR3 = SMOKE_ADC_CH;
ADC1->CR2 |= (1<<22); // 启动转换
while(!(ADC1->SR & (1<<1))); // 等待转换完成
g_sensorData.smoke = ADC1->DR;
// 读取ADC值 - 可燃气体
ADC1->SQR3 = GAS_ADC_CH;
ADC1->CR2 |= (1<<22);
while(!(ADC1->SR & (1<<1)));
g_sensorData.gas = ADC1->DR;
// 读取火焰传感器
g_sensorData.flame = FLAME_READ();
// 读取人体红外
g_sensorData.pir = PIR_READ();
}
/**
* @brief 读取电力参数模块 (通过串口)
*/
void Read_PowerMeter(void)
{
// 发送查询指令
char cmd[] = "READrn";
for(int i=0; i<strlen(cmd); i++) {
while(!(USART2->SR & (1<<7)));
USART2->DR = cmd[i];
}
// 等待接收完成 (在中断中处理)
// 这里简化处理
// 实际项目中需解析模块返回的协议数据
g_sensorData.voltage = 220.0;
g_sensorData.current = 0.5;
g_sensorData.power = 110.0;
}
/**
* @brief 舵机控制
* @param angle 角度 (0-180)
*/
void Process_Servo(uint8_t angle)
{
// 0° -> 0.5ms, 90° -> 1.5ms, 180° -> 2.5ms
// TIM3_CCR1 = (angle * 1000 / 180) + 500
uint16_t pwm = (angle * 1000 / 180) + 500;
if(pwm > 2500) pwm = 2500;
TIM3->CCR1 = pwm;
}
/**
* @brief 控制门锁
* @param open 1=开门, 0=关门
*/
void Control_Door(uint8_t open)
{
if(open) {
Process_Servo(90); // 开门
g_doorLocked = 0;
g_sensorData.doorLock = 1;
} else {
Process_Servo(0); // 关门
g_doorLocked = 1;
g_sensorData.doorLock = 0;
}
Delay_ms(500); // 等待舵机转动
}
/*---------------------- 按键处理函数 ----------------------*/
/**
* @brief 按键扫描
*/
void Key_Scan(void)
{
static uint8_t key1_prev = 0, key2_prev = 0, key3_prev = 0, key4_prev = 0;
uint8_t key1_now, key2_now, key3_now, key4_now;
key1_now = KEY1_READ();
key2_now = KEY2_READ();
key3_now = KEY3_READ();
key4_now = KEY4_READ();
// 按键1
if(key1_now && !key1_prev) {
g_keyBuffer[g_keyIndex++] = 1;
if(g_keyIndex >= PASSWORD_LEN) {
Check_Password();
}
}
key1_prev = key1_now;
// 按键2
if(key2_now && !key2_prev) {
g_keyBuffer[g_keyIndex++] = 2;
if(g_keyIndex >= PASSWORD_LEN) {
Check_Password();
}
}
key2_prev = key2_now;
// 按键3
if(key3_now && !key3_prev) {
g_keyBuffer[g_keyIndex++] = 3;
if(g_keyIndex >= PASSWORD_LEN) {
Check_Password();
}
}
key3_prev = key3_now;
// 按键4
if(key4_now && !key4_prev) {
g_keyBuffer[g_keyIndex++] = 4;
if(g_keyIndex >= PASSWORD_LEN) {
Check_Password();
}
}
key4_prev = key4_now;
}
/**
* @brief 检查密码是否正确
*/
void Check_Password(void)
{
uint8_t match = 1;
for(int i=0; i<PASSWORD_LEN; i++) {
if(g_keyBuffer[i] != g_password[i]) {
match = 0;
break;
}
}
if(match) {
// 密码正确, 开门
Control_Door(1);
BEEP_ON();
Delay_ms(200);
BEEP_OFF();
} else {
// 密码错误, 报警
BEEP_ON();
Delay_ms(1000);
BEEP_OFF();
}
// 清除缓存
g_keyIndex = 0;
memset(g_keyBuffer, 0, PASSWORD_LEN);
}
/*---------------------- 报警处理函数 ----------------------*/
/**
* @brief 报警处理
*/
void Alarm_Process(void)
{
// 火焰检测 - 最高优先级
if(g_sensorData.flame) {
PUMP_ON(); // 启动水泵灭火
BEEP_ON(); // 蜂鸣器报警
g_sensorData.pumpStatus = 1;
g_sensorData.alarmStatus = 1;
// 火焰报警上报通过MQTT
} else {
PUMP_OFF();
g_sensorData.pumpStatus = 0;
}
// 烟雾或可燃气体检测
if(g_sensorData.smoke > 1000 || g_sensorData.gas > 800) {
FAN_ON(); // 启动排风扇
BEEP_ON();
g_sensorData.fanStatus = 1;
} else {
if(!g_sensorData.flame) { // 火焰报警时蜂鸣器持续, 不关闭
BEEP_OFF();
}
FAN_OFF();
g_sensorData.fanStatus = 0;
}
// 防盗模式
if(g_systemMode == MODE_ANTI_THEFT && g_sensorData.pir) {
BEEP_ON();
g_sensorData.alarmStatus = 1;
}
}
/**
* @brief 检查电力阈值
*/
void Check_Threshold(void)
{
if(g_sensorData.current > CURRENT_THRESHOLD ||
g_sensorData.power > POWER_THRESHOLD) {
RELAY_OFF(); // 切断电源
BEEP_ON();
g_sensorData.alarmStatus = 1;
} else {
RELAY_ON();
}
}
/*---------------------- ESP8266和MQTT函数 ----------------------*/
/**
* @brief 发送AT命令到ESP8266
*/
void ESP8266_SendCommand(const char *cmd)
{
while(*cmd) {
while(!(USART1->SR & (1<<7)));
USART1->DR = *cmd++;
}
}
/**
* @brief MQTT发布传感器数据到华为云
*/
void MQTT_Publish_SensorData(void)
{
char payload[256];
// 构建华为云IoT平台标准JSON格式
sprintf(payload,
"{"services":[{"service_id":"sensor","properties":{"
""smoke":%d,"gas":%d,"flame":%d,"pir":%d,"
""temperature":25.5,"humidity":60.0,"
""voltage":%.1f,"current":%.2f,"power":%.1f,"
""doorlock":%d,"alarm":%d}}]}",
g_sensorData.smoke,
g_sensorData.gas,
g_sensorData.flame,
g_sensorData.pir,
g_sensorData.voltage,
g_sensorData.current,
g_sensorData.power,
g_sensorData.doorLock,
g_sensorData.alarmStatus
);
// 发送MQTT发布命令
char cmd[300];
sprintf(cmd, "AT+MQTTPUB=0,"$oc/devices/your-device-id/sys/properties/report","%s",1,0rn", payload);
ESP8266_SendCommand(cmd);
}
/*---------------------- 中断服务函数 ----------------------*/
/**
* @brief SysTick中断
*/
void SysTick_Handler(void)
{
g_sysTick++;
if(g_sysTick % 1000 == 0) { // 1秒
g_1sFlag = 1;
}
if(g_sysTick % 500 == 0) { // 500ms
g_500msFlag = 1;
g_sysTick = 0;
}
}
/**
* @brief USART1中断 (ESP8266)
*/
void USART1_IRQHandler(void)
{
if(USART1->SR & (1<<5)) {
uint8_t data = USART1->DR;
// 处理ESP8266返回数据
// 实际项目中需要解析响应
}
}
/**
* @brief USART2中断 (电力参数模块)
*/
void USART2_IRQHandler(void)
{
if(USART2->SR & (1<<5)) {
uint8_t data = USART2->DR;
// 接收电力参数数据
// 实际项目中需要解析协议
g_uartRxFlag = 1;
}
}
/*---------------------- 延时函数 ----------------------*/
/**
* @brief 微秒延时
*/
void Delay_us(uint32_t us)
{
uint32_t count = us * 8; // 72MHz下, 约8个周期/us
while(count--) {
__NOP();
}
}
/**
* @brief 毫秒延时
*/
void Delay_ms(uint32_t ms)
{
while(ms--) {
Delay_us(1000);
}
}
/*---------------------- LCD显示函数 (简化) ----------------------*/
void LCD_ShowString(uint8_t x, uint8_t y, const char *str)
{
// 根据实际LCD驱动实现
// 这里提供占位实现
}
void LCD_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{
// 根据实际LCD驱动实现
}
void LCD_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t dec)
{
// 根据实际LCD驱动实现
}
/*---------------------- 硬件错误处理 ----------------------*/
void HardFault_Handler(void)
{
while(1);
}
void MemManage_Handler(void)
{
while(1);
}
void BusFault_Handler(void)
{
while(1);
}
void UsageFault_Handler(void)
{
while(1);
}
【2】 硬件全覆盖
| 功能 | 实现方式 | 对应代码 |
|---|---|---|
| 火焰检测+水泵 | GPIO读取 + PB0控制 | FLAME_READ(), PUMP_ON() |
| 烟雾/燃气+排风扇 | ADC采集 + PA7控制 | ADC1->DR, FAN_ON() |
| 4按键密码开门 | PB12~PB15扫描 | Key_Scan(), Check_Password() |
| 舵机门锁 | TIM3 PWM控制 | Process_Servo(), Control_Door() |
| 人体红外 | GPIO读取 PA1 | PIR_READ() |
| 电力参数 | USART2串口读取 | Read_PowerMeter() |
| LCD显示 | I2C/SPI (预留接口) | LCD_ShowXXX() |
| ESP8266+MQTT | USART1 AT指令 | ESP8266_SendCommand(), MQTT_Publish_SensorData() |
| 蜂鸣器报警 | PA8控制 | BEEP_ON() |
| 过载保护 | PB1继电器 | RELAY_OFF() |
【3】 MQTT上传数据格式
{
"services": [{
"service_id": "sensor",
"properties": {
"smoke": 512,
"gas": 256,
"flame": 0,
"pir": 1,
"temperature": 25.5,
"humidity": 60.0,
"voltage": 220.0,
"current": 0.50,
"power": 110.0,
"doorlock": 0,
"alarm": 0
}
}]
}
【4】 远程开门命令接收
代码中已包含发布功能,如需接收手机APP开门命令,需要在主循环中添加:
// 在USART1中断中处理收到"door_open"命令
if(strstr(g_esp8266_rxBuf, "door_control")) {
Control_Door(1); // 开门
}
【5】使用前需要替换的内容
| 位置 | 替换内容 |
|---|---|
| WiFi配置 | "SSID", "PASSWORD" |
| 华为云配置 | "your-device-id", "your-user", "your-password" |
| 华为云服务器地址 | "your-server.iot-mqtts.cn-north-4.myhuaweicloud.com" |
| 电力参数模块协议 | Read_PowerMeter() 中的解析代码 |
| LCD驱动 | 根据实际LCD型号补充 LCD_ShowXXX() 实现 |
【6】编译环境配置
IDE: Keil MDK5•
芯片: STM32F103RCT6•
启动文件: startup_stm32f10x_md.s•
编译器: ARMCC (AC5)
4.3 程序下载
也有视频教程:
讲解如何编译代码,下载STM32程序: https://www.bilibili.com/video/BV1Cw4m1e7Yc
打STM32的keil工程,编译代码、然后,使用USB线将开发板的左边的USB口(串口1)与电脑的USB连接,打开程序下载软件下载程序。
具体下载过程看下面图:
打开程序下载软件:[软件就在资料包里的软件工具目录下]
4.4 程序正常运行效果
设备运行过程中会通过串口打印调试信息,我们可以通过串口打印了解程序是否正常。
程序下载之后,可以打开串口调试助手查看程序运行的状态信息。[软件就在资料包里的软件工具目录下]
4.5 取模软件的使用
显示屏上会显示中文,字母,数字等数据,可以使用下面的取模软件进行取模设置。
项目开发使用的全部软件工具已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
[软件就在资料包里的软件工具目录下]
打开软件之后:
五、总结
本项目围绕宿舍安全防护与环境监测需求,设计并实现了一套基于STM32F103RCT6的宿舍安全防护与环境监测系统。系统以STM32单片机作为核心控制器,通过MQ2烟雾及可燃气体传感器、火焰检测传感器、红外热释电人体检测传感器以及电力参数采集模块,对宿舍内部的火焰、烟雾、可燃气体、人员状态以及用电参数进行实时监测,并通过LCD显示屏对采集的数据进行本地显示。
在安全防护方面,系统能够在检测到火焰时启动蜂鸣器和水泵,在检测到烟雾或可燃气体时启动蜂鸣器和排风扇,在防盗模式下检测到人员时进行入侵报警;同时通过电力参数采集模块监测电压、电流和功率,当电流或功率超过设定阈值时进行报警并切断负载供电。在门锁控制方面,系统采用4颗数字按键实现本地密码开门,并通过SG90舵机完成门锁的开关控制,同时支持通过手机APP和Windows电脑上位机进行远程开门。
在物联网通信方面,系统利用ESP8266 WiFi模块连接网络,通过MQTT协议将设备采集的数据上传至华为云物联网平台,Android手机APP和Windows上位机通过云平台获取设备数据,并实现相应的远程控制。通过本地监测、自动控制和云端远程管理相结合,使系统能够较为全面地满足宿舍安全监测和用电管理的实际需求。
通过本项目的设计与实现,将STM32嵌入式控制、传感器检测、执行机构控制、串口通信、WiFi联网、MQTT物联网通信以及Qt上位机开发等技术进行了综合应用。项目不仅实现了预期的宿舍安全防护与环境监测功能,也为嵌入式系统与物联网技术在宿舍安全管理场景中的应用提供了一定的实践参考。
1146