一、项目开发背景
随着文化艺术产业的蓬勃发展,珍贵艺术品的收藏与保存日益受到重视。艺术品对环境条件极为敏感,温湿度波动、光照过强、紫外线辐射、物理振动及人为盗窃等因素均可能导致其不可逆的损伤。传统保存方式依赖人工巡检与简易设备,难以实现全天候精准监测,且缺乏实时预警与数据追溯能力,存在明显的管理盲区。
为解决这一问题,基于物联网技术的智能环境监控系统成为重要研究方向。通过集成多类型传感器与云平台,可实现对艺术品保存环境的全方位自动化监测。本系统采用STM32微控制器作为核心,结合温湿度、紫外线、振动及红外防盗传感器,构建高精度数据采集网络,并通过Wi-Fi模块将数据实时上传至华为云物联网平台,确保数据的可靠存储与远程可访问性。
此外,通过定制化开发的QT上位机界面,管理人员可直观查看历史数据趋势与预警信息,极大提升了艺术品保存管理的智能化水平。该系统不仅适用于博物馆、美术馆等专业机构,也可扩展至私人收藏领域,为艺术遗产的长期保存提供关键技术支撑。
二、设计实现的功能
(1) STM32F103C8T6最小系统核心板作为主控制器
(2) SI1145紫外线指数传感器监测紫外线强度
(3) SW-420振动传感器检测环境震动
(4) E18-D80NK红外对射传感器实现防盗监测
(5) ESP8266-01S Wi-Fi模块连接华为云平台
(6) 洞洞板焊接传感器接口电路,杜邦线连接各模块
三、项目硬件模块组成
(1)STM32F103C8T6最小系统核心板作为主控制器
(2)SI1145紫外线指数传感器监测紫外线强度
(3)SW-420振动传感器检测环境震动
(4)E18-D80NK红外对射传感器实现防盗监测
(5)ESP8266-01S Wi-Fi模块连接华为云平台
(6)洞洞板焊接传感器接口电路,杜邦线连接各模块
当前项目使用的相关软件工具、模块源码已经上传到网盘: https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
四、设计意义
该智能艺术品保存环境监控系统通过实时监测温湿度、光照度和紫外线强度等关键环境参数,有效保护艺术品免受环境因素导致的退化,如颜料褪色、材料脆化或霉变,从而延长艺术品的保存寿命并维持其艺术价值。系统集成振动传感器和红外对射传感器,能够及时检测环境震动和非法入侵事件,增强艺术品的物理安全性,防止盗窃或意外损坏,为珍贵艺术品提供全面的防护保障。
借助华为云物联网服务器和QT上位机界面,系统实现了环境数据的远程传输、存储和历史分析,用户可随时查看参数变化趋势和接收预警信息,提高了监控的便捷性和响应速度。硬件设计采用STM32主控制器和常见传感器模块,通过洞洞板焊接和杜邦线连接,确保了系统的稳定性和可扩展性,同时降低了实现成本,使其适用于博物馆、画廊或私人收藏等实际场景。
五、设计思路
该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个监控系统的运行。主控制器通过洞洞板焊接的接口电路和杜邦线连接各传感器模块,实现数据的采集与初步处理。系统通过ESP8266-01S Wi-Fi模块与华为云物联网服务器建立连接,实现数据的远程上传和云端存储,为上位机提供数据源。
温湿度、光照度和紫外线强度的监测由SI1145紫外线指数传感器完成,该传感器能够同时检测紫外线强度和可见光照度,而温湿度监测需借助额外的常见传感器如DHT11实现,以满足功能需求。振动检测使用SW-420振动传感器,实时监控环境震动情况,当检测到异常振动时,系统会触发预警机制。防盗监测通过E18-D80NK红外对射传感器实现,当红外光束被阻断时,系统判断为入侵事件并立即上报。
数据采集后,STM32主控制器对传感器数据进行整合和格式化,包括时间戳和传感器类型信息。通过ESP8266模块,数据以MQTT协议上传至华为云物联网平台,平台负责数据存储和实时转发。QT上位机定期从云平台拉取数据,显示环境参数的历史变化曲线,并在检测到异常值时弹出预警信息,如温度超标、振动异常或防盗触发,帮助用户及时采取应对措施。
整个系统注重实用性和可靠性,硬件连接基于洞洞板和杜邦线简化布线,软件层面优化了数据上传频率以降低功耗,并通过华为云平台实现数据的稳定管理和远程访问。上位机界面设计直观,支持历史数据查询和实时监控,确保艺术品保存环境的安全监测。
六、华为云设计
6.1 框架描述
6.2 部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
6.3 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
6.4 开通物联网服务
地址: 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端口合适
。
6.5 创建产品
链接: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。
接着点击新增属性。
6.6 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
(4)设备创建完成
(5)设备详情
6.7 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}}]}
6.8 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
6.9 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到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}}]}
6.10 创建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
6.11 获取影子数据
帮助文档: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
七、系统功能总结
功能 | 传感器/组件 | 描述 |
---|---|---|
温湿度监测 | 温湿度传感器 | 监测环境温度和湿度 |
光照度监测 | SI1145传感器 | 监测环境光照强度 |
紫外线强度监测 | SI1145传感器 | 监测紫外线指数 |
振动检测 | SW-420振动传感器 | 检测环境震动情况 |
防盗监测 | E18-D80NK红外对射传感器 | 实现防盗监测 |
数据显示与预警 | QT上位机软件 | 显示环境参数变化历史及预警信息 |
云平台通信 | ESP8266-01S Wi-Fi模块 | 连接华为云物联网平台 |
主控制 | STM32F103C8T6核心板 | 系统主控制器 |
硬件接口 | 洞洞板焊接电路 | 传感器和模块连接接口 |
八、设计的各个功能模块描述
STM32F103C8T6最小系统核心板作为整个系统的主控制器,负责协调所有传感器的数据采集、处理逻辑判断以及通过Wi-Fi模块与云平台通信。它接收来自各种传感器的输入信号,执行必要的计算和控制算法,并管理数据的存储和传输。
SI1145紫外线指数传感器用于监测艺术品保存环境中的紫外线强度和光照度。该传感器提供数字输出,能够准确测量紫外线指数和环境光强度,并将数据实时传送给STM32主控制器,以帮助评估光照条件对艺术品的影响。
SW-420振动传感器检测环境中的震动情况,通过其常开或常闭输出模式,当检测到异常震动时,会向STM32发送信号,触发系统的警报机制,从而保护艺术品免受物理损害。
E18-D80NK红外对射传感器实现防盗监测功能,通过发射和接收红外光束来检测入侵。当光束被遮挡时,传感器输出信号变化,STM32据此判断潜在的安全威胁,并立即启动预警程序。
ESP8266-01S Wi-Fi模块负责连接华为云物联网服务器,实现数据的远程传输和云平台交互。它将STM处理后的传感器数据上传到云端,同时接收来自服务器的指令或配置更新,确保系统的实时监控和远程管理。
洞洞板用于焊接所有传感器的接口电路,并通过杜邦线连接各模块到STM32主控制器。这种布局提供了灵活的连接方式,确保电气稳定性和模块之间的可靠通信,同时便于调试和维护。
九、上位机代码设计
9.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
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。
选择编译器: (一定要看清楚了)
9.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
9.3 切换编译器
在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。
目前新建工程的时候选择了2种编译器。 一种是mingw32
这个编译Windows下运行的程序。 一种是Android
编译器,可以生成Android
手机APP。
不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。
windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。
下面我这里就选择的 mingw32
这个编译器,编译Windows下运行的程序。
9.4 编译测试功能
创建完毕之后,编译测试一下功能是否OK。
点击左下角的绿色三角形按钮
。
正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。
9.5 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
9.6 设计代码
【1】获取token
调用华为云的API都需要填token参数,先看帮助文章,了解如何获取token。
帮助文档:https://support.huaweicloud.com/api-iam/iam_30_0001.html
根据帮助文档,写完成下面代码编写:
这段代码的功能是通过华为云IAM服务获取Token,以便后续调用华为云API时使用。以下是代码的详细功能解释:
1. 设置功能标识
function_select = 3;
function_select
是一个标识变量,用于区分当前请求的功能类型。这里设置为3,表示当前请求是获取Token。
2. 构造请求URL
QString requestUrl;
QNetworkRequest request;
// 设置请求地址
QUrl url;
// 获取token请求地址
requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens")
.arg(SERVER_ID);
- 构造获取Token的请求URL,URL格式为:
https://iam.{SERVER_ID}.myhuaweicloud.com/v3/auth/tokens
。 SERVER_ID
是华为云服务器的区域ID(如cn-north-1
),通过QString
的arg
方法动态替换到URL中。
3. 设置请求头
// 设置数据提交格式
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
- 设置HTTP请求头,指定请求体的数据格式为
application/json;charset=UTF-8
,表示发送的数据是JSON格式。
4. 设置请求URL
// 构造请求url.setUrl(requestUrl);request.setUrl(url);
- 将构造好的URL设置到
QUrl
对象中,并将其绑定到QNetworkRequest
对象。
5. 构造请求体
QString text = QString("{"auth":{"identity":{"methods":["password"],"password":"
"{"user":{"domain": {"
""name":"%1"},"name": "%2","password": "%3"}}},"
""scope":{"project":{"name":"%4"}}}}")
.arg(MAIN_USER)
.arg(IAM_USER)
.arg(IAM_PASSWORD)
.arg(SERVER_ID);
- 构造JSON格式的请求体,用于向华为云IAM服务请求Token。请求体包含以下字段:
auth
:认证信息。identity
:身份信息。methods
:认证方法,这里使用密码认证(password
)。password
:密码认证的具体信息。user
:用户信息。domain
:用户所属的域名。name
:域名名称(MAIN_USER
)。
name
:用户名(IAM_USER
)。password
:用户密码(IAM_PASSWORD
)。
scope
:请求的范围。project
:项目信息。name
:项目名称(SERVER_ID
)。
- 使用
QString
的arg
方法动态替换请求体中的变量(如MAIN_USER
、IAM_USER
等)。
6. 发送HTTP POST请求
// 发送请求
manager->post(request, text.toUtf8());
- 使用
QNetworkAccessManager
的post
方法发送HTTP POST请求。 request
是构造好的请求对象,text.toUtf8()
是将请求体转换为UTF-8编码的字节数组。
7. 总结
这段代码的核心功能是:
- 构造获取Token的HTTP请求:包括请求URL、请求头和请求体。
- 发送请求:通过
QNetworkAccessManager
发送POST请求,向华为云IAM服务请求Token。 - Token的作用:获取到的Token将用于后续调用华为云API时的身份验证。
通过这段代码,QT上位机能够获取华为云的Token,为后续的设备数据查询、控制等操作提供身份验证支持。
【2】获取影子数据
前面章节介绍了影子数据获取接口。下面是对应编写的代码:
这段代码的功能是向华为云IoT平台查询设备的属性信息(设备状态)。以下是对代码的详细功能含义解释:
代码功能含义解释:
(1)function_select = 0;
- 这行代码设置
function_select
为0,表示当前操作是查询设备属性。这个变量用于标识不同的操作,可以帮助后续根据不同的操作类型执行不同的处理逻辑。
(2)QString requestUrl; QNetworkRequest request;
requestUrl
:用于存储请求的URL地址,后续将构造一个用于查询设备属性的URL。request
:用来封装HTTP请求的对象,包含请求的所有信息,包括请求头、URL等。
(3)QUrl url;
url
:用于存储并处理请求的URL对象,确保请求使用正确的地址。
(4)构造请求URL:
requestUrl = QString("https://%1:443/v5/iot/%2/devices/%3/shadow")
.arg(IP_ADDR)
.arg(PROJECT_ID)
.arg(device_id);
这行代码构建了一个URL,用于查询设备的状态(属性)。URL包括了:
IP_ADDR:华为云IoT平台的IP地址或域名。
PROJECT_ID:项目的ID,用于区分不同的项目。
device_id:设备的唯一标识符,用于查询指定设备的属性。
:443
指定使用HTTPS协议(端口443)进行安全通信。
最终构造出的URL形如:https://<IP_ADDR>:443/v5/iot/<PROJECT_ID>/devices/<device_id>/shadow
,这是查询设备状态的API接口。
(1)request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
- 设置请求头的内容类型为
application/json
,表明请求体中的数据格式是JSON。
(2)request.setRawHeader("X-Auth-Token", Token);
- 设置请求头中的
X-Auth-Token
字段,传递身份验证令牌(Token
)。这个令牌用于验证请求的合法性,确保只有授权的用户可以查询设备的状态。
(3)url.setUrl(requestUrl);
- 将前面构建好的请求URL赋值给
url
对象,确保后续的请求使用正确的URL。
(4)request.setUrl(url);
- 将
url
对象设置到request
对象中,准备发送请求。
(5)manager->get(request);
- 使用
QNetworkAccessManager
的get
方法发送GET请求,查询设备的属性。request
中包含了URL、请求头以及Token等信息,服务器接收到请求后将返回设备的属性信息(如设备状态、属性值等)。
代码整体功能:
该代码实现了通过华为云IoT平台的API查询设备的属性信息。具体步骤包括:
- 构造查询设备属性的API请求URL。
- 设置请求头,指定数据格式为JSON,并传递Token进行身份验证。
- 使用
QNetworkAccessManager
发送GET请求,向服务器请求设备的状态数据。 - 服务器将返回设备的属性数据,供后续处理。
总结:
这段代码的功能是向华为云IoT平台查询指定设备的属性信息,并通过GET请求将设备的状态返回给客户端。通过Token进行身份验证,确保请求的合法性。
【3】解析数据更新界面
根据接口返回的数据更新界面。
【4】判断设备是否离线
这段代码用于判断设备是否离线。通过获取设备上传到服务器数据的时间与本地的系统时间差进行对比。
这段代码的核心功能是通过比较设备上传数据的时间和本地系统时间来判断设备是否处于离线状态,以下是其详细解释:
(1)功能分析
显示最新更新时间
ui->label_update_time->setText("最新时间:" + update_time);
将设备上传的最新时间 update_time
显示在界面上的 label_update_time
控件中,格式为 最新时间:yyyy-MM-dd HH:mm:ss
。
方便用户了解设备数据的最近更新时间。
获取本地当前时间
QDateTime currentDateTime = QDateTime::currentDateTime();
使用 QDateTime::currentDateTime()
获取系统当前时间,作为对比基准。
计算时间差
qint64 secondsDiff = currentDateTime.secsTo(dateTime);
secsTo
: 计算 currentDateTime
和设备上传时间 dateTime
之间的时间差(单位:秒)。
dateTime
是通过解析 JSON 数据提取到的设备数据上传时间,并已转换为本地时间格式。
判断设备状态
if (qAbs(secondsDiff) >= 5 * 60)
使用 qAbs
获取时间差的绝对值。
如果时间差超过 5 分钟(300秒),表示设备长时间未上传数据,判定为“离线”。
(2)离线处理
更新状态显示
ui->label_dev_state->setText("设备状态:离线");
在界面 label_dev_state
控件中显示设备当前状态为“离线”。
(3)在线处理
状态更新ui->label_dev_state->setText("设备状态:在线");
如果时间差小于 5 分钟,显示“设备状态:在线”。
【5】获取设备最新数据上传时间
这是解析华为云API接口返回的数据,解析出来里面设备数据的时间,进行显示。
这段代码的主要作用是解析华为云 API 返回的 JSON 数据中的设备数据时间字段,转换为本地时间格式,并最终以用户友好的标准格式输出到界面。
(1)详细代码解析
(1)提取时间字段
QString event_time = obj3.take("event_time").toString();
qDebug() << "event_time:" << event_time;
obj3.take("event_time")
:从 JSON 数据中的 reported
对象提取 event_time
字段,值为一个字符串,表示设备上传数据的时间。
toString()
:将提取的字段值转换为 QString
类型,便于后续操作。
调试输出:使用 qDebug()
输出提取的时间值,例如:20231121T120530Z
。
2. 转换为 QDateTime
对象
QDateTime dateTime = QDateTime::fromString(event_time, "yyyyMMddTHHmmssZ");
QDateTime::fromString
:
使用指定格式解析 event_time
字符串为 QDateTime
对象。
格式说明:
yyyyMMdd
: 年、月、日(如20231121
)。T
: 时间部分的分隔符(固定为T
)。HHmmss
: 时、分、秒(如120530
)。Z
: 表示时间是 UTC 时间。- 如果时间字符串格式不匹配,会返回一个无效的
QDateTime
对象。
3. 转换时区到本地时间
dateTime.setTimeSpec(Qt::UTC);dateTime = dateTime.toLocalTime();
setTimeSpec(Qt::UTC)
:
- 明确告知
dateTime
对象,当前时间是 UTC 时间。 - 确保时间转换准确,避免因为默认时区不明确导致的误差。
toLocalTime()
:
- 将时间从 UTC 转换为本地时区时间,例如中国标准时间(CST, UTC+8)。
4. 格式化输出为标准时间字符串
QString update_time = dateTime.toString("yyyy-MM-dd HH:mm:ss");
toString()
:将 QDateTime
转换为指定格式的字符串。
格式说明:
yyyy-MM-dd
: 年-月-日。HH:mm:ss
: 小时:分钟:秒。
示例结果:2023-11-21 20:05:30
。
用户显示友好性:转换后的格式易读,符合国际通用的日期时间表示规范。
(2)代码运行效果
假设 API 返回的时间字段值为 20231121T120530Z
。
转换流程:
- 解析为
QDateTime
对象:2023-11-21 12:05:30 (UTC)
; - 转换为本地时间:
2023-11-21 20:05:30 (CST)
。 - 格式化输出:
"2023-11-21 20:05:30"
。
输出到界面时,显示为:
最新时间: 2023-11-21 20:05:30
9.7 编译Windows上位机
点击软件左下角的绿色三角形按钮进行编译运行。
9.8 代码设计
#include <QApplication>
#include <QMainWindow>
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QChart>
#include <QChartView>
#include <QLineSeries>
#include <QValueAxis>
#include <QDateTimeAxis>
#include <QListWidget>
#include <QLabel>
#include <QStatusBar>
#include <QtMqtt/QMqttClient>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QTimer>
QT_CHARTS_USE_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
setupUI();
setupMQTT();
setupCharts();
}
~MainWindow() {}
private slots:
void onConnected() {
statusBar()->showMessage("Connected to Huawei Cloud");
m_client->subscribe("artwork_monitoring/data"); // Subscribe to data topic
}
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic) {
QJsonDocument doc = QJsonDocument::fromJson(message);
if (doc.isNull()) {
statusBar()->showMessage("Invalid JSON received");
return;
}
QJsonObject obj = doc.object();
double temperature = obj["temperature"].toDouble();
double humidity = obj["humidity"].toDouble();
double light = obj["light"].toDouble();
double uv = obj["uv"].toDouble();
bool vibration = obj["vibration"].toBool();
bool intrusion = obj["intrusion"].toBool();
QDateTime now = QDateTime::currentDateTime();
qint64 timestamp = now.toMSecsSinceEpoch();
// Update data series
temperatureSeries->append(timestamp, temperature);
humiditySeries->append(timestamp, humidity);
lightSeries->append(timestamp, light);
uvSeries->append(timestamp, uv);
// Check for warnings
checkWarnings(temperature, humidity, light, uv, vibration, intrusion, now);
// Update chart ranges if needed
updateChartAxes();
}
void updateChartAxes() {
// Adjust X-axis to show last 24 hours
QDateTime now = QDateTime::currentDateTime();
QDateTime minTime = now.addSecs(-24 * 3600);
axisX->setRange(minTime, now);
// Adjust Y-axes based on data range (simplified)
axisYTemperature->setRange(0, 50);
axisYHumidity->setRange(0, 100);
axisYLight->setRange(0, 1000);
axisYUV->setRange(0, 10);
}
void checkWarnings(double temp, double humidity, double light, double uv, bool vibration, bool intrusion, const QDateTime &time) {
if (temp > 25.0) {
addWarning("High temperature alert: " + QString::number(temp) + "°C", time);
}
if (humidity > 60.0) {
addWarning("High humidity alert: " + QString::number(humidity) + "%", time);
}
if (light > 500.0) {
addWarning("High light intensity alert: " + QString::number(light) + " lux", time);
}
if (uv > 3.0) {
addWarning("High UV alert: " + QString::number(uv) + " index", time);
}
if (vibration) {
addWarning("Vibration detected!", time);
}
if (intrusion) {
addWarning("Intrusion detected!", time);
}
}
void addWarning(const QString &message, const QDateTime &time) {
QString entry = time.toString("yyyy-MM-dd hh:mm:ss") + " - " + message;
warningList->addItem(entry);
statusBar()->showMessage("New warning: " + message, 5000);
}
private:
void setupUI() {
QWidget *centralWidget = new QWidget(this);
QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
// Left side: Charts
QWidget *chartWidget = new QWidget;
QVBoxLayout *chartLayout = new QVBoxLayout(chartWidget);
temperatureChartView = new QChartView;
humidityChartView = new QChartView;
lightChartView = new QChartView;
uvChartView = new QChartView;
chartLayout->addWidget(new QLabel("Temperature"));
chartLayout->addWidget(temperatureChartView);
chartLayout->addWidget(new QLabel("Humidity"));
chartLayout->addWidget(humidityChartView);
chartLayout->addWidget(new QLabel("Light"));
chartLayout->addWidget(lightChartView);
chartLayout->addWidget(new QLabel("UV"));
chartLayout->addWidget(uvChartView);
// Right side: Warnings
QWidget *warningWidget = new QWidget;
QVBoxLayout *warningLayout = new QVBoxLayout(warningWidget);
warningLayout->addWidget(new QLabel("Warning Messages"));
warningList = new QListWidget;
warningLayout->addWidget(warningList);
mainLayout->addWidget(chartWidget, 3);
mainLayout->addWidget(warningWidget, 1);
setCentralWidget(centralWidget);
statusBar()->showMessage("Disconnected");
}
void setupMQTT() {
m_client = new QMqttClient(this);
m_client->setHostname("your-huawei-cloud-mqtt-endpoint"); // Replace with actual endpoint
m_client->setPort(1883); // Use 8883 for SSL
// Set username and password if required, e.g., from Huawei Cloud device credentials
// m_client->setUsername("username");
// m_client->setPassword("password");
connect(m_client, &QMqttClient::connected, this, &MainWindow::onConnected);
connect(m_client, &QMqttClient::messageReceived, this, &MainWindow::onMessageReceived);
m_client->connectToHost();
}
void setupCharts() {
// Initialize series
temperatureSeries = new QLineSeries;
humiditySeries = new QLineSeries;
lightSeries = new QLineSeries;
uvSeries = new QLineSeries;
temperatureSeries->setName("Temperature (°C)");
humiditySeries->setName("Humidity (%)");
lightSeries->setName("Light (lux)");
uvSeries->setName("UV Index");
// Create charts
QChart *tempChart = new QChart;
tempChart->addSeries(temperatureSeries);
tempChart->setTitle("Temperature History");
axisX = new QDateTimeAxis;
axisX->setFormat("hh:mm");
axisX->setTitleText("Time");
tempChart->addAxis(axisX, Qt::AlignBottom);
axisYTemperature = new QValueAxis;
axisYTemperature->setTitleText("°C");
tempChart->addAxis(axisYTemperature, Qt::AlignLeft);
temperatureSeries->attachAxis(axisX);
temperatureSeries->attachAxis(axisYTemperature);
temperatureChartView->setChart(tempChart);
// Similarly for other charts (humidity, light, UV)
QChart *humChart = new QChart;
humChart->addSeries(humiditySeries);
humChart->setTitle("Humidity History");
humChart->addAxis(axisX, Qt::AlignBottom);
axisYHumidity = new QValueAxis;
axisYHumidity->setTitleText("%");
humChart->addAxis(axisYHumidity, Qt::AlignLeft);
humiditySeries->attachAxis(axisX);
humiditySeries->attachAxis(axisYHumidity);
humidityChartView->setChart(humChart);
QChart *lChart = new QChart;
lChart->addSeries(lightSeries);
lChart->setTitle("Light History");
lChart->addAxis(axisX, Qt::AlignBottom);
axisYLight = new QValueAxis;
axisYLight->setTitleText("lux");
lChart->addAxis(axisYLight, Qt::AlignLeft);
lightSeries->attachAxis(axisX);
lightSeries->attachAxis(axisYLight);
lightChartView->setChart(lChart);
QChart *uChart = new QChart;
uChart->addSeries(uvSeries);
uChart->setTitle("UV History");
uChart->addAxis(axisX, Qt::AlignBottom);
axisYUV = new QValueAxis;
axisYUV->setTitleText("Index");
uChart->addAxis(axisYUV, Qt::AlignLeft);
uvSeries->attachAxis(axisX);
uvSeries->attachAxis(axisYUV);
uvChartView->setChart(uChart);
// Set initial axis ranges
updateChartAxes();
}
QMqttClient *m_client;
QChartView *temperatureChartView;
QChartView *humidityChartView;
QChartView *lightChartView;
QChartView *uvChartView;
QLineSeries *temperatureSeries;
QLineSeries *humiditySeries;
QLineSeries *lightSeries;
QLineSeries *uvSeries;
QDateTimeAxis *axisX;
QValueAxis *axisYTemperature;
QValueAxis *axisYHumidity;
QValueAxis *axisYLight;
QValueAxis *axisYUV;
QListWidget *warningList;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"
此代码是一个基本的QT上位机应用程序,用于监控智能艺术品保存环境。它连接到华为云MQTT服务器,订阅数据主题,接收并解析JSON格式的环境数据(温湿度、光照度、紫外线强度、振动和防盗状态),使用图表显示历史数据,并在参数超出阈值时显示预警信息。用户需要替换MQTT端点、端口和认证信息以匹配华为云配置。图表显示最近24小时的数据,预警信息列表在右侧面板。
十、模块代码设计
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
// Register definitions for STM32F103C8T6
#define RCC_BASE 0x40021000
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x18))
#define RCC_APB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x1C))
#define GPIOA_BASE 0x40010800
#define GPIOA_CRL (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_CRH (*(volatile uint32_t *)(GPIOA_BASE + 0x04))
#define GPIOA_IDR (*(volatile uint32_t *)(GPIOA_BASE + 0x08))
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))
#define GPIOB_BASE 0x40010C00
#define GPIOB_CRL (*(volatile uint32_t *)(GPIOB_BASE + 0x00))
#define GPIOB_CRH (*(volatile uint32_t *)(GPIOB_BASE + 0x04))
#define GPIOB_IDR (*(volatile uint32_t *)(GPIOB_BASE + 0x08))
#define GPIOB_ODR (*(volatile uint32_t *)(GPIOB_BASE + 0x0C))
#define I2C1_BASE 0x40005400
#define I2C1_CR1 (*(volatile uint32_t *)(I2C1_BASE + 0x00))
#define I2C1_CR2 (*(volatile uint32_t *)(I2C1_BASE + 0x04))
#define I2C1_DR (*(volatile uint32_t *)(I2C1_BASE + 0x10))
#define I2C1_SR1 (*(volatile uint32_t *)(I2C1_BASE + 0x14))
#define I2C1_SR2 (*(volatile uint32_t *)(I2C1_BASE + 0x18))
#define I2C1_CCR (*(volatile uint32_t *)(I2C1_BASE + 0x1C))
#define I2C1_TRISE (*(volatile uint32_t *)(I2C1_BASE + 0x20))
#define USART1_BASE 0x40013800
#define USART1_SR (*(volatile uint32_t *)(USART1_BASE + 0x00))
#define USART1_DR (*(volatile uint32_t *)(USART1_BASE + 0x04))
#define USART1_BRR (*(volatile uint32_t *)(USART1_BASE + 0x08))
#define USART1_CR1 (*(volatile uint32_t *)(USART1_BASE + 0x0C))
// Sensor pins and addresses
#define SW420_PIN 0 // PA0
#define E18_PIN 1 // PA1
#define SI1145_ADDR_W 0x60
#define SI1145_ADDR_R 0x61
// Function prototypes
void SystemInit(void);
void GPIO_Init(void);
void I2C1_Init(void);
void USART1_Init(void);
void I2C1_Start(void);
void I2C1_Stop(void);
void I2C1_Write(uint8_t data);
uint8_t I2C1_Read(uint8_t ack);
uint16_t Read_SI1145_UV(void);
uint16_t Read_SI1145_Light(void);
void USART1_Send(uint8_t data);
void USART1_SendString(char *str);
void Delay_ms(uint32_t ms);
int main(void) {
SystemInit();
GPIO_Init();
I2C1_Init();
USART1_Init();
// Initialize ESP8266 connection to Huawei Cloud (assume pre-configured)
USART1_SendString("AT+CWMODE=1rn");
Delay_ms(1000);
USART1_SendString("AT+CWJAP="SSID","PASSWORD"rn"); // Replace with actual WiFi credentials
Delay_ms(5000);
USART1_SendString("AT+MQTTUSERCFG=0,1,"NULL","username","password",0,0,""rn"); // Replace with Huawei Cloud device info
USART1_SendString("AT+MQTTCONN=0,"cloud.huawei.com",1883,0rn");
Delay_ms(2000);
while (1) {
// Read sensors
uint16_t uv_index = Read_SI1145_UV();
uint16_t light_intensity = Read_SI1145_Light();
uint8_t vibration = (GPIOA_IDR & (1 << SW420_PIN)) ? 1 : 0;
uint8_t obstacle = (GPIOA_IDR & (1 << E18_PIN)) ? 0 : 1; // E18 outputs low when obstacle detected
// Prepare data string for Huawei Cloud
char data_str[100];
sprintf(data_str, "{"UV":%u,"Light":%u,"Vibration":%u,"Obstacle":%u}", uv_index, light_intensity, vibration, obstacle);
// Send via MQTT publish command
USART1_SendString("AT+MQTTPUB=0,"topic","");
USART1_SendString(data_str);
USART1_SendString("",0,0rn");
Delay_ms(5000); // Send data every 5 seconds
}
}
void SystemInit(void) {
// Enable clocks for GPIOA, GPIOB, USART1, I2C1
RCC_APB2ENR |= (1 << 2) | (1 << 3) | (1 << 14); // GPIOA, GPIOB, USART1
RCC_APB1ENR |= (1 << 21); // I2C1
}
void GPIO_Init(void) {
// Configure PA0 for SW-420 vibration sensor: input with pull-down
GPIOA_CRL &= ~(0xF << (4 * SW420_PIN));
GPIOA_CRL |= (0x8 << (4 * SW420_PIN)); // CNF=10 (pull-up/pull-down), MODE=00 (input)
GPIOA_ODR &= ~(1 << SW420_PIN); // Pull-down
// Configure PA1 for E18-D80NK infrared sensor: input with pull-up
GPIOA_CRL &= ~(0xF << (4 * E18_PIN));
GPIOA_CRL |= (0x8 << (4 * E18_PIN)); // CNF=10, MODE=00
GPIOA_ODR |= (1 << E18_PIN); // Pull-up
// Configure PB6 and PB7 for I2C1: alternate function open drain
GPIOB_CRL &= ~(0xF << 24); // Clear bits for PB6 (bits 24-27)
GPIOB_CRL |= (0xF << 24); // CNF=11 (AF open drain), MODE=11 (50MHz)
GPIOB_CRL &= ~(0xF << 28); // Clear bits for PB7 (bits 28-31)
GPIOB_CRL |= (0xF << 28); // CNF=11, MODE=11
GPIOB_ODR |= (1 << 6) | (1 << 7); // Set ODR high
// Configure PA9 for USART1 TX: alternate function push-pull
GPIOA_CRH &= ~(0xF << 4); // Clear bits for PA9 (bits 4-7 in CRH)
GPIOA_CRH |= (0xB << 4); // CNF=10 (AF push-pull), MODE=11 (50MHz)
// Configure PA10 for USART1 RX: input floating
GPIOA_CRH &= ~(0xF << 8); // Clear bits for PA10 (bits 8-11 in CRH)
GPIOA_CRH |= (0x4 << 8); // CNF=01 (input floating), MODE=00
}
void I2C1_Init(void) {
I2C1_CR1 &= ~(1 << 0); // Disable I2C
I2C1_CR2 = 36; // Set frequency to 36MHz (APB1 clock)
I2C1_CCR = 180; // CCR for 100kHz
I2C1_TRISE = 37; // TRISE for 100kHz
I2C1_CR1 |= (1 << 0); // Enable I2C
}
void USART1_Init(void) {
USART1_CR1 &= ~(1 << 13); // Disable USART
USART1_BRR = 0x271; // 115200 baud with 72MHz PCLK2
USART1_CR1 |= (1 << 2) | (1 << 3); // Enable TE and RE
USART1_CR1 |= (1 << 13); // Enable USART
}
void I2C1_Start(void) {
I2C1_CR1 |= (1 << 8); // Generate start
while (!(I2C1_SR1 & (1 << 0))); // Wait for SB flag
}
void I2C1_Stop(void) {
I2C1_CR1 |= (1 << 9); // Generate stop
while (I2C1_CR1 & (1 << 9)); // Wait for stop condition
}
void I2C1_Write(uint8_t data) {
I2C1_DR = data;
while (!(I2C1_SR1 & (1 << 7))); // Wait for TxE
}
uint8_t I2C1_Read(uint8_t ack) {
if (ack) {
I2C1_CR1 |= (1 << 10); // Set ACK
} else {
I2C1_CR1 &= ~(1 << 10); // Clear ACK
}
while (!(I2C1_SR1 & (1 << 6))); // Wait for RxNE
return I2C1_DR;
}
uint16_t Read_SI1145_UV(void) {
I2C1_Start();
I2C1_Write(SI1145_ADDR_W);
I2C1_Write(0x2A); // Command for UV index
I2C1_Start(); // Repeated start
I2C1_Write(SI1145_ADDR_R);
uint8_t high = I2C1_Read(1); // Read with ACK
uint8_t low = I2C1_Read(0); // Read with NACK
I2C1_Stop();
return (high << 8) | low;
}
uint16_t Read_SI1145_Light(void) {
I2C1_Start();
I2C1_Write(SI1145_ADDR_W);
I2C1_Write(0x22); // Command for visible light
I2C1_Start(); // Repeated start
I2C1_Write(SI1145_ADDR_R);
uint8_t high = I2C1_Read(1);
uint8_t low = I2C1_Read(0);
I2C1_Stop();
return (high << 8) | low;
}
void USART1_Send(uint8_t data) {
while (!(USART1_SR & (1 << 7))); // Wait for TXE
USART1_DR = data;
}
void USART1_SendString(char *str) {
while (*str) {
USART1_Send(*str++);
}
}
void Delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms * 7200; i++) { // Simple delay for 72MHz
__asm__("nop");
}
}
十一、项目核心代码
#define RCC_BASE 0x40021000
#define RCC_CR *(volatile uint32_t*)(RCC_BASE + 0x00)
#define RCC_CFGR *(volatile uint32_t*)(RCC_BASE + 0x04)
#define RCC_APB2ENR *(volatile uint32_t*)(RCC_BASE + 0x18)
#define RCC_APB1ENR *(volatile uint32_t*)(RCC_BASE + 0x1C)
#define FLASH_BASE 0x40022000
#define FLASH_ACR *(volatile uint32_t*)(FLASH_BASE + 0x00)
#define GPIOA_BASE 0x40010800
#define GPIOA_CRL *(volatile uint32_t*)(GPIOA_BASE + 0x00)
#define GPIOA_CRH *(volatile uint32_t*)(GPIOA_BASE + 0x04)
#define GPIOA_IDR *(volatile uint32_t*)(GPIOA_BASE + 0x08)
#define GPIOB_BASE 0x40010C00
#define GPIOB_CRL *(volatile uint32_t*)(GPIOB_BASE + 0x00)
#define GPIOB_IDR *(volatile uint32_t*)(GPIOB_BASE + 0x08)
#define USART1_BASE 0x40013800
#define USART1_SR *(volatile uint32_t*)(USART1_BASE + 0x00)
#define USART1_DR *(volatile uint32_t*)(USART1_BASE + 0x04)
#define USART1_BRR *(volatile uint32_t*)(USART1_BASE + 0x08)
#define USART1_CR1 *(volatile uint32_t*)(USART1_BASE + 0x0C)
#define USART1_CR2 *(volatile uint32_t*)(USART1_BASE + 0x10)
#define USART1_CR3 *(volatile uint32_t*)(USART1_BASE + 0x14)
extern void SI1145_Init(void);
extern uint16_t SI1145_Read_UV(void);
extern uint16_t SI1145_Read_Visible(void);
extern void DHT11_Init(void);
extern int DHT11_Read(int *temp, int *hum);
extern void ESP8266_Init(void);
extern void ESP8266_Send_Data(char *data);
#define VIBRATION_PIN 0
#define IR_PIN 1
void SystemClock_Init(void) {
RCC_CR |= (1 << 16);
while (!(RCC_CR & (1 << 17)));
RCC_CFGR |= (1 << 16);
RCC_CFGR |= (0x9 << 18);
FLASH_ACR |= (2 << 0);
RCC_CR |= (1 << 24);
while (!(RCC_CR & (1 << 25)));
RCC_CFGR |= (2 << 0);
while ((RCC_CFGR & (3 << 2)) != (2 << 2));
}
void GPIO_Init(void) {
RCC_APB2ENR |= (1 << 3);
GPIOB_CRL &= ~(0xF << (4 * VIBRATION_PIN));
GPIOB_CRL |= (0x4 << (4 * VIBRATION_PIN));
GPIOB_CRL &= ~(0xF << (4 * IR_PIN));
GPIOB_CRL |= (0x4 << (4 * IR_PIN));
}
void UART1_Init(void) {
RCC_APB2ENR |= (1 << 2);
RCC_APB2ENR |= (1 << 14);
GPIOA_CRH &= ~(0xF << 4);
GPIOA_CRH |= (0xB << 4);
GPIOA_CRH &= ~(0xF << 8);
GPIOA_CRH |= (0x4 << 8);
USART1_BRR = 0x271;
USART1_CR1 |= (1 << 13);
USART1_CR1 |= (1 << 3);
USART1_CR1 |= (1 << 2);
}
void UART1_SendChar(char c) {
while (!(USART1_SR & (1 << 7)));
USART1_DR = c;
}
void UART1_SendString(char *str) {
while (*str) {
UART1_SendChar(*str++);
}
}
void delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms * 1000; i++);
}
char* itoa(int value, char* str, int base) {
char* rc;
char* ptr;
char* low;
if (base < 2 || base > 36) {
*str = ' ';
return str;
}
rc = ptr = str;
if (value < 0 && base == 10) {
*ptr++ = '-';
value = -value;
}
low = ptr;
do {
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + value % base];
value /= base;
} while (value);
*ptr-- = ' ';
while (low < ptr) {
char tmp = *low;
*low++ = *ptr;
*ptr-- = tmp;
}
return rc;
}
int main(void) {
SystemClock_Init();
GPIO_Init();
UART1_Init();
SI1145_Init();
DHT11_Init();
ESP8266_Init();
int temperature, humidity;
uint16_t uv, visible;
int vibration_state, ir_state;
char data_str[100];
char temp_str[10], hum_str[10], uv_str[10], vis_str[10];
while (1) {
DHT11_Read(&temperature, &humidity);
uv = SI1145_Read_UV();
visible = SI1145_Read_Visible();
vibration_state = (GPIOB_IDR & (1 << VIBRATION_PIN)) ? 1 : 0;
ir_state = (GPIOB_IDR & (1 << IR_PIN)) ? 1 : 0;
if (vibration_state) {
char alert[] = "Vibration alert!n";
ESP8266_Send_Data(alert);
}
if (ir_state) {
char alert[] = "Intrusion alert!n";
ESP8266_Send_Data(alert);
}
itoa(temperature, temp_str, 10);
itoa(humidity, hum_str, 10);
itoa(uv, uv_str, 10);
itoa(visible, vis_str, 10);
data_str[0] = ' ';
UART1_SendString("T:");
UART1_SendString(temp_str);
UART1_SendString(" H:");
UART1_SendString(hum_str);
UART1_SendString(" UV:");
UART1_SendString(uv_str);
UART1_SendString(" L:");
UART1_SendString(vis_str);
UART1_SendString(" V:");
UART1_SendString(itoa(vibration_state, data_str, 10));
UART1_SendString(" I:");
UART1_SendString(itoa(ir_state, data_str, 10));
UART1_SendString("n");
ESP8266_Send_Data(data_str);
delay_ms(10000);
}
}
十二、总结
该系统成功实现了对艺术品保存环境的全面智能监控,通过集成多种传感器实时监测温湿度、光照度、紫外线强度、振动以及防盗情况,确保了艺术品的长期保存安全。STM32F103C8T6主控制器高效协调各模块工作,体现了嵌入式系统在环境监测中的可靠性和灵活性。
硬件设计采用洞洞板焊接和杜邦线连接方式,简化了传感器接口电路,使得SI1145紫外线传感器、SW-420振动传感器和E18-D80NK红外对射传感器能够稳定采集数据,并通过ESP8266-01S Wi-Fi模块将数据上传至华为云物联网服务器,实现了远程监控和数据存储。
软件方面,QT上位机提供了直观的环境参数历史变化显示和预警信息管理,增强了系统的可视化和用户体验。结合华为云平台,系统支持大数据分析和远程告警,为艺术品保存提供了科学依据和及时干预手段。
本设计不仅满足了功能需求,还展示了物联网技术在文化遗产保护中的创新应用,具有较高的实用价值和推广前景。