• 方案介绍
  • 附件下载
  • 相关推荐
申请入驻 产业图谱

基于STM32的智能康养木屋监测系统

07/28 11:54
1852
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

更多详细资料请联系.docx

共1个文件

一、前言

1.1 项目介绍

【1】项目开发背景

随着人们生活水平的提升与健康意识的增强,康养旅游逐渐成为一种重要的休闲方式,尤其是在自然资源丰富的山区景区,越来越多游客选择入住木质结构的康养露营木屋,体验贴近自然、舒适安静的生活环境。为了进一步提升游客的入住体验与安全保障,构建一套智能化、自动化的环境监测与调控系统已成为康养木屋升级的重要方向。

当前传统的木屋多依赖人工管理,环境数据无法实时获取,也无法及时响应屋内空气质量变化、温湿度异常等问题,存在舒适度不足与健康风险隐患。此外,游客对智能化生活的需求日益提升,期望木屋能具备更高的环境自适应调控能力和便捷的远程控制手段。因此,设计一套基于STM32的智能康养木屋监测系统,融合环境感知、智能联动控制、远程交互等功能,具有重要的实际应用价值。

该系统以STM32为核心控制单元,融合多种环境传感器、智能电气设备与通信模块,不仅可对木屋内温湿度、CO?浓度、PM2.5颗粒物等关键参数进行实时监测,还具备人员在屋识别、自动窗帘/灯光/风扇控制、OLED本地显示、WIFI远程数据上传与手机APP交互等功能。结合华为云物联网平台,实现环境数据的可视化分析与远程集中管理,进一步增强木屋的智能性、安全性与能效管理水平。

该项目不仅能有效提高游客在木屋中的居住舒适度,也可作为景区集群智能管理的技术支撑平台,具有良好的推广前景和社会效益。

image-20250724120520715

【2】设计实现的功能

(1)环境参数采集与显示功能
● 实时采集木屋内的温度、湿度、CO?浓度、PM2.5浓度,并计算空气质量综合评分。
● 每隔固定时间(如5秒)采集一次数据并记录,用于后续环境趋势分析。

(2)人员存在检测与活动统计功能
● 通过人体红外传感器检测屋内是否有人员活动。
● 统计每日人员活动频率,如进出次数或持续活动时长,用于健康行为评估。

(3)智能风扇/窗帘/灯光自动控制功能
● 当检测到CO?浓度过高或温湿度异常时,自动开启风扇或通风设备。
● 日出时自动拉开窗帘,日落时自动关闭窗帘,同时支持手动控制。
● 灯光根据人员状态和光照时间段自动点亮或关闭。

(4)本地OLED显示功能
● 使用OLED显示屏滚动展示当前温湿度、空气质量评分、人员状态、风扇和灯光运行状态等信息。

(5)WIFI无线通信与数据上传功能
● 通过ESP8266模块将环境数据以MQTT协议定时上传至华为云IoT平台。
● 云端可实现数据的长期记录与可视化分析。

(6)手机APP远程控制与环境监控功能
● 用户可通过手机APP查看木屋环境参数信息,并控制风扇、灯光、窗帘等设备。
● 支持设置个性化环境控制阈值(如温度>26℃自动开风扇)。

(7)异常状态报警功能
● 当CO?浓度>1200ppm、PM2.5>150、温度>35℃或<0℃、或无人长时间滞留等异常情况发生时,
● 启动本地蜂鸣器报警,并通过云端推送报警信息至手机APP。

(8)节能与离线模式支持功能
● 若屋内持续检测无人状态超过2小时,自动关闭非必要电气设备进入节能模式。
● 当网络断开时,系统自动切换为本地控制模式,保障基本功能正常运行。

(9)多屋并联管理扩展功能
● 预留扩展接口,支持多个木屋并联接入同一个后台服务器或手机APP,实现集中统一管理。

【3】项目硬件模块组成

(1)STM32F103RCT6主控芯片
● 作为整个系统的核心控制单元,负责各类传感器数据采集、逻辑判断、设备控制及通信处理。

(2)温湿度传感器(SHT30)
● 用于实时检测屋内环境的温度和湿度。

(3)CO?浓度传感器(MH-Z19B)
● 用于检测屋内二氧化碳浓度,支持串口通信输出数据。

(4)PM2.5传感器(如PMS7003)
● 用于监测空气中的PM2.5颗粒物浓度,评估空气质量。

(5)人体红外传感器(如HC-SR501)
● 检测屋内是否存在人员活动,用于判断居住状态与行为分析。

(6)光照传感器(如BH1750)
● 用于检测光照强度,为自动窗帘和灯光控制提供依据。

(7)风扇控制模块(继电器+直流风扇)
● 用于通风降温,继电器模块控制风扇启停。

(8)电动窗帘控制模块(继电器或舵机控制)
● 控制窗帘的自动开合,实现日出拉开、日落关闭功能。

(9)LED灯光及控制模块(继电器或MOS管驱动)
● 根据光照和人员状态智能控制屋内照明设备。

(10)OLED显示模块(0.96寸SPI OLED)
● 实时显示环境数据、人员状态和设备运行状态。

(11)蜂鸣器报警模块(高电平触发有源蜂鸣器
● 异常情况下本地发出报警提示。

(12)ESP8266 WIFI模块
● 用于无线通信,将数据通过MQTT协议上传至华为云IoT平台,同时实现与手机APP的数据交互。

(13)电源模块(5V稳压模块/锂电池+升压)
● 为各类模块提供稳定的电源输出,支持USB供电或电池供电。

(14)扩展接口模块(排针/串口/I?C预留口)
● 为后续多屋并联控制系统预留扩展能力,便于统一后台接入与管理。

【4】设计意义

随着健康中国战略的持续推进与旅游休闲方式的多样化发展,山地康养露营木屋作为一种新型生态旅游载体,越来越受到人们的青睐。然而,这类木屋多建于偏远山区,自然环境变化大,管理难度高,传统木屋缺乏智能化环境调控与远程管理手段,无法满足现代游客对舒适性、安全性和便捷性的多重需求。因此,设计一套智能化、自动化的木屋环境监测与控制系统具有重要的现实意义。

本项目基于STM32单片机平台,融合温湿度、CO?浓度、PM2.5等多种传感器模块,能够实时采集木屋内的环境参数,并通过OLED进行本地显示,实现对木屋内空气质量的全面感知,为游客提供安全健康的居住环境。同时,系统引入人体存在检测技术,能够识别人员活动状态,为行为评估、节能控制与自动灯光联动提供基础支撑,提升系统智能化水平。

此外,通过ESP8266模块与华为云物联网平台的对接,系统能够实现远程数据上传与可视化分析,为景区管理者提供数据支持,便于整体调度与健康环境评估;结合手机APP实现风扇、窗帘、灯光等终端的远程控制,极大提升用户体验。系统还支持本地报警与离线控制机制,确保在异常情况下系统仍能独立稳定运行,保障用户人身安全。

项目还预留了多屋并联管理接口,具备良好的拓展性,适用于民宿集群化管理与智慧景区平台接入,具有广阔的应用前景和推广价值。因此,本系统不仅提升了康养木屋的居住品质,也为智能民宿、绿色建筑及智慧旅游的发展提供了可行的技术支撑。

【5】市面上同类产品研究现状


5.1 同类产品市场现状
1. 消费级智能家居系统(如米家、涂鸦智能)
  • 案例:米家全屋智能解决方案
    • 功能:温湿度/PM2.5监测、人体感应、窗帘灯光联动、APP控制。
    • 局限性
      • 依赖稳定WiFi,山区网络中断时功能瘫痪;
      • 缺乏CO?深度监测及健康行为分析;
      • 无法支持多设备集群管理(景区级扩展弱)。
    • 适配性:适合家庭,不满足景区木屋的离线节能工业级环境监测需求。
2. 工业环境监测设备(如Aranet4、Senseware)
  • 案例:Aranet4 CO?监测仪
    • 优势
      • 高精度CO?/温湿度检测,电池续航长达3年;
      • 支持MQTT协议上传云端。
    • 短板
      • 仅限数据采集,无执行器控制(如风扇/窗帘);
      • 无人员活动统计与节能策略,单价高($200+/台)。
    • 场景:办公室/工厂监测,无法覆盖木屋全场景自动化
3. 高端智能酒店系统(如华为智慧酒店方案)
  • 案例:华为云IoT+智慧酒店
    • 功能亮点
      • 云端统一管理多房间设备;
      • 支持自定义阈值报警(温度/安防);
      • 能源管理(无人时关闭空调)。
    • 景区应用缺陷
      • 依赖强电力及网络基建,山区部署成本高;
      • 未针对露营木屋的太阳能供电极端环境优化。
4. 专业康养监测设备(如Withings、欧姆龙)
  • 案例:Withings Healthy Home Coach
    • 功能:空气质量评分、噪音/CO?监测、APP健康建议。
    • 局限
      • 无执行控制能力(如自动开窗);
      • 无本地离线存储,断网数据丢失;
      • 单房间适用,无法扩展多木屋并联

5.2 市场缺口与用户项目优势
需求维度 现有产品短板 本系统解决方案
山区网络适应性 断网即瘫痪 本地离线控制+数据缓存
健康行为统计 仅存在检测,无活动频率分析 基于红外传感器的进出次数/时长统计
多木屋集群管理 多为单房间方案 预留华为云IoT多节点接入接口
极端环境耐用性 消费级设备温湿度范围窄 工业级传感器(-40℃~85℃)
节能模式 简单定时开关 无人2小时深度节能+负载分级管理

5.3 可借鉴的成熟技术方案
  1. 传感器融合方案
    • 案例:Bosch BME688+CCS811
      • 四合一气体/温湿度检测芯片,用于空气质量综合评分,降低多传感器布线复杂度。
  2. 边缘计算节能策略
    • 案例:施耐德EcoStruxure微电网
      • 基于人员活动的负载动态调度,支持本系统“无人2小时断电”逻辑。
  3. 低功耗广域网传输
    • 替代方案:NB-IoT/LoRa(若WiFi不稳定)
      • 华为云IoT支持多协议接入,山区可切换低功耗网络。

【6】摘要

本项目设计并实现了一种基于STM32的智能康养木屋监测系统,旨在提升山地景区康养露营木屋的居住舒适性与智能化水平。系统以STM32F103RCT6为核心控制器,集成温湿度、CO?浓度、PM2.5颗粒物等环境传感器,能够实现对屋内环境参数的实时采集与滚动显示;通过人体红外传感器识别人员活动状态,实现智能灯光、窗帘、风扇的联动控制。系统支持WIFI无线通信,利用ESP8266模块将环境数据通过MQTT协议上传至华为云IoT平台,用户可通过手机APP远程查看木屋环境状态并控制各类电器设备。同时,系统具备异常状态报警、节能模式、离线运行机制及多屋并联管理扩展能力,适应景区远程集中管控的需求。测试结果表明,该系统功能稳定,响应及时,具有良好的实用性和推广前景。

关键词:
STM32;智能康养木屋;环境监测;MQTT;华为云IoT;人体感应;远程控制

1.2 设计思路

本项目的设计以“数据驱动的智能控制”为核心思路,围绕康养木屋的舒适性、安全性和节能性展开,通过多种环境传感器对屋内空气质量和居住状态进行全面监测,并结合自动化控制与远程通信,实现智能化管理。

系统整体架构分为三大层次:感知层、控制层与通信展示层。感知层包括温湿度传感器、CO?浓度传感器、PM2.5传感器和人体红外传感器等,负责实时采集屋内环境数据和人员活动状态。所有传感器数据由STM32主控芯片进行统一管理和处理,并结合预设的逻辑条件,决定是否触发相应控制指令。

控制层由继电器、舵机、电动窗帘驱动模块、风扇和灯光电路组成,根据环境数据判断结果进行自动联动。例如,当检测到CO?浓度升高或温度湿度异常时,系统自动启动风扇或通风装置;光照与人员状态也用于调节灯光与窗帘,提升居住舒适性与节能效果。

OLED显示模块用于本地展示温湿度、空气质量评分、设备状态及人员检测结果,增强用户直观感知。ESP8266模块实现与云端的MQTT协议通信,定时将环境数据上传至华为云物联网平台,实现远程数据可视化存储与分析。

同时,手机APP通过与云端交互,使用户可以随时远程查看木屋环境信息,并主动控制风扇、灯光与窗帘开关。APP还支持个性化阈值设置,进一步增强系统的智能性与用户体验。

为保障系统稳定性与安全性,设计中引入了异常检测与报警机制,如CO?浓度过高、PM2.5超标或温度异常时,通过蜂鸣器进行本地报警并同步通知APP用户。此外,系统支持节能模式与离线运行,当检测到屋内长时间无人,自动关闭非必要电器;在网络中断时,自动切换为本地控制,保证系统核心功能不受影响。

整体设计充分考虑了康养木屋的实际使用场景,既提升了环境舒适度,也增强了设备智能联动与远程管理能力,具有较强的实用性与拓展性。

1.3 系统功能总结

功能模块 功能描述 实现方式或技术组件
环境数据采集 实时采集温度、湿度、CO?浓度、PM2.5浓度,计算空气质量评分 温湿度传感器(如SHT30)、MH-Z19B、PMS7003
数据记录与分析 每5秒采集一次并记录数据,用于分析环境变化趋势 STM32定时采样、内部缓存
人员检测与统计 检测屋内是否有人,统计进出频率与活动时长 红外人体感应模块(HC-SR501)
智能风扇控制 根据CO?、温湿度条件,自动开启风扇或通风设备 继电器+直流风扇
智能窗帘控制 日出自动拉开,日落自动关闭,支持手动控制 舵机或窗帘电机+时间逻辑判断
智能灯光控制 结合光照与人员状态自动控制照明 光照传感器+人体红外+继电器/MOS驱动
本地数据可视化显示 OLED屏滚动显示环境状态、人员检测、风扇/灯光状态 0.96寸SPI OLED显示模块
数据上传功能 将采集数据通过WIFI模块上传至华为云IoT平台 ESP8266模块,MQTT协议
手机APP远程控制 用户远程查看环境信息,控制风扇、灯光、窗帘并设置环境阈值 华为云IoT平台+APP+MQTT交互
异常状态报警 环境参数异常时蜂鸣器报警+APP推送 高电平有源蜂鸣器+MQTT消息推送
节能控制机制 无人状态持续2小时关闭电器,进入节能模式 STM32定时+状态逻辑控制
离线控制支持 网络断开时,自动切换至本地控制,保障基本功能 STM32本地逻辑优先控制
多屋扩展管理 支持多个木屋统一后台或APP集中控制管理 串口/I?C扩展接口+云端多节点接入

1.4 模块的技术详情介绍


(1)STM32F103RCT6 主控芯片
功能:作为系统核心控制器,负责传感器数据采集处理、设备控制逻辑运算、通信协议实现等。
特点:32位ARM Cortex-M3内核,主频72MHz,内置多个USART、ADC、I?C和SPI接口,适合多模块集成控制,运行稳定,功耗低,性价比高。


(2)温湿度传感器(SHT30 或 DHT11
功能:采集木屋内的温度和相对湿度信息,用于环境舒适度评估与控制逻辑判断。
特点:体积小、响应快、集成度高;SHT30采用I?C接口,精度更高;DHT11价格低,适合初级监测场景。


(3)CO?浓度传感器(MH-Z19B)
功能:检测屋内空气中的二氧化碳浓度,作为空气质量与通风控制的重要依据。
特点:支持UART输出,精度高,稳定性强,响应时间短,可长期连续运行。


(4)PM2.5颗粒物传感器(PMS7003)
功能:监测空气中的PM2.5颗粒物浓度,评估屋内空气污染程度。
特点:高灵敏度、可检测0.3μm以上颗粒物,串口通信,适合用于空气质量评估系统。


(5)人体红外传感器(HC-SR501)
功能:检测屋内是否有人活动,用于灯光联动控制、活动频率统计与节能模式判断。
特点:被动红外感应,感应距离远,延迟可调,成本低,抗干扰能力较强。


(6)光照传感器(BH1750)
功能:采集屋内或窗外光照强度信息,结合时间逻辑控制窗帘开合与照明灯启停。
特点:数字光照传感器,I?C通信,测量精度高,线性度好,适合自然光监测场景。


(7)OLED显示模块(0.96寸SPI OLED)
功能:本地滚动显示环境数据、人员状态、风扇与灯光工作状态等信息。
特点:分辨率128×64,亮度高、视角广、功耗低,支持SPI通信,适用于嵌入式显示场景。


(8)风扇控制模块(继电器+直流风扇)
功能:当温湿度或CO?浓度异常时,自动开启风扇加强通风。
特点:采用继电器控制,可驱动大功率直流风扇,电路简单,控制可靠。


(9)电动窗帘控制模块(舵机或继电器驱动电机)
功能:根据光照时间段和用户控制,实现窗帘的自动开合。
特点:响应迅速,支持PWM控制(舵机)或继电器控制(电机),适用于智能家居场景。


(10)LED灯光控制模块(MOS管或继电器控制)
功能:依据光照强度与人员状态,实现灯光的智能开关控制。
特点:低功耗控制,支持高电流LED负载,响应速度快,便于接入自动控制逻辑。


(11)蜂鸣器报警模块(高电平触发有源蜂鸣器)
功能:当发生CO?超标、PM2.5异常或温度过高/过低时发出本地声音报警。
特点:高电平触发、响声清晰、布线简单,适合小型环境警报。


(12)ESP8266 WIFI模块
功能:将环境数据通过MQTT协议无线上传至华为云物联网平台,同时支持APP远程控制功能。
特点:支持STA/AP模式,功耗低,集成TCP/IP协议栈,适合嵌入式物联网通信。


(13)电源模块(5V稳压模块或锂电池+升压)
功能:为系统提供稳定电源,支持USB或电池供电,保障断电情况下的短期运行。
特点:输出稳定,适配范围广,可接5V风扇与逻辑电路,便于户外部署。


(14)扩展接口模块(I?C/UART/SPI接口排针)
功能:用于多木屋并联、后续功能扩展与统一平台集中管理。
特点:标准接口形式,便于后续设备串联与系统功能升级。


1.5 华为云物联网平台属性

本项目采用 ESP8266 模块 作为无线通信单元,通过 MQTT协议 将采集到的环境数据上传至 华为云物联网平台(Huawei IoTDA),实现远程环境监控与智能控制。下面详细说明上传流程与平台属性配置:


【1】上传流程概述

  1. 设备端采集数据
    STM32定时从温湿度、CO?、PM2.5 等传感器采集数据,结合人员状态、灯光/风扇工作状态等构建数据包。
  2. ESP8266建立MQTT连接
    ESP8266通过WIFI接入互联网后,与华为云IoT平台建立 MQTT 长连接,使用设备证书(deviceId、secret、productId)完成认证登录。
  3. 构造属性上传JSON包
    将环境数据与设备状态封装成标准JSON格式,按照华为云物模型要求,通过MQTT发布到 $oc/devices/{device_id}/sys/properties/report 主题。
  4. 云端接收并解析属性数据
    平台解析接收到的JSON属性数据,自动映射到设备物模型的属性字段,进行可视化展示、数据记录、规则触发等操作。

【2】上传的属性内容(物模型定义)

系统上传至华为云平台设备属性主要包括以下字段,每个字段在物联网平台中定义为“属性(property)”:

属性标识符 数据类型 单位 描述
temperature float 室内温度
humidity float %RH 相对湿度
co2 int ppm 二氧化碳浓度
pm25 int μg/m? PM2.5颗粒物浓度
airScore int / 空气质量评分(综合计算)
humanStatus bool / 是否有人(1:有人,0:无人)
fanStatus bool / 风扇开关状态
lightStatus bool / 光开关状态
curtainStatus int / 窗帘状态(0=关闭,1=半开,2=全开)
activityCount int 次/天 今日累计人员活动次数
alarmStatus string / 当前告警类型(如 “CO2超标”)
timestamp string / 数据采集时间戳(可选)

这些属性需要在华为云 IoT平台创建产品物模型时一一配置,确保平台正确接收和展示对应数据。


【3】MQTT消息上报示例

上传数据时构建如下JSON格式的属性消息,通过MQTT发布:

{
  "services": [
    {
      "service_id": "Environment",
      "properties": {
        "temperature": 25.6,
        "humidity": 48.2,
        "co2": 900,
        "pm25": 35,
        "airScore": 87
      },
      "event_time": "2025-07-24T08:00:00Z"
    },
    {
      "service_id": "DeviceStatus",
      "properties": {
        "humanStatus": true,
        "fanStatus": true,
        "lightStatus": false,
        "curtainStatus": 2,
        "activityCount": 12,
        "alarmStatus": "none"
      },
      "event_time": "2025-07-24T08:00:00Z"
    }
  ]
}

【4】在华为云平台的操作简述

  1. 创建产品
    登录华为云 IoTDA 控制台 → 创建新产品 → 添加设备模型 → 定义属性字段(如上表)。
  2. 添加设备
    基于创建的产品实例化设备,获取设备ID、密钥、MQTT接入信息。
  3. 配置接入信息
    将设备三元组信息(deviceId、productId、secret)烧录到ESP8266代码中。
  4. 在线监控数据
    上传成功后,可在“设备详情”页面查看属性变化,或使用“时序数据”可视化分析。

二、部署华为云物联网平台

华为云官网: https://www.huaweicloud.com/

打开官网,搜索物联网,就能快速找到 设备接入IoTDA

image-20221204193824815

2.1 物联网平台介绍

华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。

使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。

物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。

设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。

业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。

img

2.2 开通物联网服务

地址: https://www.huaweicloud.com/product/iothub.html

image-20241028135834377

开通免费单元。

image-20241028135935457

点击立即创建

image-20240117134653452

正在创建标准版实例,需要等待片刻。

image-20241028140048811

创建完成之后,点击详情。 可以看到标准版实例的设备接入端口和地址。

image-20241028140129102

下面框起来的就是端口号域名

image-20241028140229696

点击实例名称,可以查看当前免费单元的配置情况。

image-20241028140331523

image-20241028140428663

开通之后,点击接入信息,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。

image-20241028140511105

总结:

端口号:   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)创建产品

image-20241028141601305

(2)填写产品信息

根据自己产品名字填写,下面的设备类型选择自定义类型。

image-20240612094809689

(3)产品创建成功

image-20240612095148945

创建完成之后点击查看详情。

image-20240612095134263

(4)添加自定义模型

产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。

模型简单来说: 就是存放设备上传到云平台的数据。

你可以根据自己的产品进行创建。

比如:

烟雾可以叫  MQ2
温度可以叫  Temperature
湿度可以叫  humidity
火焰可以叫  flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。

先点击自定义模型。

image-20240612095517900

再创建一个服务ID。

image-20240612095542749

接着点击新增属性。

image-20240612095648815

image-20240612095711898

2.4 添加设备

产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。

(1)注册设备

image-20240425181935561

(2)根据自己的设备填写

image-20240612100115167

(3)保存设备信息

创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。

image-20240612100128061

(4)设备创建完成

image-20240612100147232

(5)设备详情

image-20240612100202960

image-20240612100217236

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

img

业务流程:

img

(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

image-20221207153310037

对于设备而言,一般会订阅平台下发消息给设备 这个主题。

设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。

如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。

以当前设备为例,最终订阅主题的格式如下:
$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

image-20221207153637391

根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:

发布的主题格式:
$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

image-20240509193207359

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

image-20240425182610048

(2)生成MQTT三元组

华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。

下面是打开的页面:

image-20240425183025893

填入设备的信息: (上面两行就是设备创建完成之后保存得到的)

直接得到三元组信息。

image-20240509193310020

得到三元组之后,设备端通过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客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。

image-20240509193457358

(2)打开网页查看

完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。

image-20240612100508790

点击详情页面,可以看到上传的数据:

image-20240612100529581

到此,云平台的部署已经完成,设备已经可以正常上传数据了。

(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】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证

image-20240509193646253

image-20240509193701262

项目凭证:

28add376c01e4a61ac8b621c714bf459

【2】创建IAM用户

鼠标放在左上角头像上,在下拉菜单里选择统一身份认证

image-20240509193729078

点击左上角创建用户

image-20240509193744287

image-20240314153208692

image-20240314153228359

image-20240314153258229

创建成功:

image-20240314153315444

【3】创建完成

image-20240509193828289

用户信息如下:

主用户名  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

在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。

调试完成看右下角的响应体,就是返回的影子数据。

image-20240509194152229

设备影子接口返回的数据如下:

{
 "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写代码访问此链接,获取影子数据,完成上位机开发。

image-20240509194214716

链接如下:

https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow

三、STM32代码设计

当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

3.1 硬件连线说明

  1. 温湿度传感器SHT30
  2. CO?传感器MH-Z19B
    • 接口:UART
    • 连接:PA9(TX), PA10(RX)
    • 备注:需5V电平转换
  3. PM2.5传感器PMS7003
    • 接口:UART
    • 连接:PA2(TX), PA3(RX)
    • 备注:3.3V直接连接
  4. 人体红外传感器HC-SR501
    • 接口:数字输入
    • 连接:PB0
    • 备注:高电平有效
  5. 窗帘控制模块(步进电机
    • 接口:GPIO控制
    • 连接:
      PB12(IN1), PB13(IN2), PB14(IN3), PB15(IN4)
    • 备注:需ULN2003驱动芯片
  6. 风扇控制模块(继电器)
    • 接口:GPIO输出
    • 连接:PB5
    • 备注:低电平触发
  7. OLED显示模块
    • 接口:SPI
    • 连接:
      PA4(CS), PA5(SCK), PA6(MISO), PA7(MOSI), PB1(DC), PB2(RES)
    • 备注:硬件SPI1
  8. 蜂鸣器模块
    • 接口:GPIO输出
    • 连接:PB8
    • 备注:高电平触发
  9. ESP8266-WIFI模块
    • 接口:UART
    • 连接:PA11(TX), PA12(RX)
    • 备注:需AT指令控制
  10. 系统状态LED
    • 接口:GPIO输出
    • 连接:PC13
    • 备注:低电平点亮

IO分配说明:

  • 复用功能优先使用硬件外设(如硬件SPI/I2C/UART)
  • 保留PA0(WAKEUP)、PA13(SWDIO)、PA14(SWCLK)用于调试
  • PB3/PB4默认用于JTAG,如需使用需重映射
  • 模拟输入通道保留未用,便于扩展

3.2 传感器代码

1. 温湿度传感器SHT30 (I2C)

#include "stm32f10x.h"
#include "delay.h"

#define SHT30_ADDR 0x44<<1

void SHT30_Init(void) {
    I2C_InitTypeDef I2C_InitStruct;
    GPIO_InitTypeDef GPIO_InitStruct;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // PB6-SCL, PB7-SDA
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x00;
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStruct.I2C_ClockSpeed = 100000;
    I2C_Init(I2C1, &I2C_InitStruct);
    I2C_Cmd(I2C1, ENABLE);
}

uint8_t SHT30_ReadData(float *temp, float *humi) {
    uint8_t buf[6];
    uint8_t cmd[2] = {0x2C, 0x06}; // High repeatability measurement
    
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    
    I2C_Send7bitAddress(I2C1, SHT30_ADDR, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    
    I2C_SendData(I2C1, cmd[0]);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_SendData(I2C1, cmd[1]);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    
    I2C_Send7bitAddress(I2C1, SHT30_ADDR, I2C_Direction_Receiver);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    
    for(int i=0; i<5; i++) {
        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
        buf[i] = I2C_ReceiveData(I2C1);
        if(i<4) I2C_AcknowledgeConfig(I2C1, ENABLE);
        else I2C_AcknowledgeConfig(I2C1, DISABLE);
    }
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    buf[5] = I2C_ReceiveData(I2C1);
    
    I2C_GenerateSTOP(I2C1, ENABLE);
    
    uint16_t temp_raw = (buf[0] << 8) | buf[1];
    uint16_t humi_raw = (buf[3] << 8) | buf[4];
    
    *temp = -45 + 175 * (float)temp_raw / 65535.0f;
    *humi = 100 * (float)humi_raw / 65535.0f;
    
    return 1;
}

2. CO?传感器MH-Z19B (UART)

#include "stm32f10x.h"
#include "stdio.h"

void MHZ19B_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    // PA9-TX, PA10-RX
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);
}

uint16_t MHZ19B_ReadCO2(void) {
    uint8_t cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
    uint8_t resp[9];
    
    for(int i=0; i<9; i++) {
        USART_SendData(USART1, cmd[i]);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    }
    
    Delay_ms(100);
    
    if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)) {
        for(int i=0; i<9; i++) {
            while(!USART_GetFlagStatus(USART1, USART_FLAG_RXNE));
            resp[i] = USART_ReceiveData(USART1);
        }
        
        if(resp[0] == 0xFF && resp[1] == 0x86) {
            uint16_t co2 = (resp[2] << 8) | resp[3];
            uint8_t checksum = 0;
            for(int i=1; i<8; i++) checksum += resp[i];
            checksum = 0xFF - checksum + 1;
            
            if(checksum == resp[8]) return co2;
        }
    }
    return 0;
}

3. PM2.5传感器PMS7003 (UART)

#include "stm32f10x.h"

typedef struct {
    uint16_t pm1_0;
    uint16_t pm2_5;
    uint16_t pm10;
} PMS7003_Data;

void PMS7003_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // PA2-TX, PA3-RX
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStruct);
    USART_Cmd(USART2, ENABLE);
}

uint8_t PMS7003_ReadData(PMS7003_Data *data) {
    uint8_t buf[32];
    uint16_t checksum = 0;
    
    while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
    if(USART_ReceiveData(USART2) != 0x42) return 0;
    
    for(int i=0; i<31; i++) {
        while(!USART_GetFlagStatus(USART2, USART_FLAG_RXNE));
        buf[i] = USART_ReceiveData(USART2);
        if(i < 30) checksum += buf[i];
    }
    
    if(((checksum >> 8) == buf[30]) && ((checksum & 0xFF) == buf[31])) {
        data->pm1_0 = (buf[4] << 8) | buf[5];
        data->pm2_5 = (buf[6] << 8) | buf[7];
        data->pm10 = (buf[8] << 8) | buf[9];
        return 1;
    }
    return 0;
}

4. 人体红外传感器HC-SR501

#include "stm32f10x.h"

void HC_SR501_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // PB0 input
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    // Enable EXTI interrupt
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
    EXTI_InitTypeDef EXTI_InitStruct;
    EXTI_InitStruct.EXTI_Line = EXTI_Line0;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);
    
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x0F;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x0F;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

uint8_t HC_SR501_Detect(void) {
    return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0);
}

// EXTI0中断服务函数
void EXTI0_IRQHandler(void) {
    if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 人体检测到触发处理
        // 可以添加计数或状态标记
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

5. OLED显示模块 (SPI)

#include "stm32f10x.h"
#include "oled_font.h"

#define OLED_CS_PIN  GPIO_Pin_4
#define OLED_DC_PIN  GPIO_Pin_1
#define OLED_RST_PIN GPIO_Pin_2

void OLED_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_SPI1, ENABLE);
    
    // SPI1: PA5-SCK, PA6-MISO, PA7-MOSI
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // CS:PA4, DC:PB1, RST:PB2
    GPIO_InitStruct.GPIO_Pin = OLED_CS_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    GPIO_InitStruct.GPIO_Pin = OLED_DC_PIN | OLED_RST_PIN;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI1, &SPI_InitStruct);
    SPI_Cmd(SPI1, ENABLE);
}

void OLED_WriteCmd(uint8_t cmd) {
    GPIO_ResetBits(GPIOA, OLED_CS_PIN);
    GPIO_ResetBits(GPIOB, OLED_DC_PIN);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, cmd);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
    GPIO_SetBits(GPIOA, OLED_CS_PIN);
}

void OLED_DisplayOn(void) {
    OLED_WriteCmd(0x8D); // Charge pump
    OLED_WriteCmd(0x14);
    OLED_WriteCmd(0xAF); // Display on
}

void OLED_ShowString(uint8_t x, uint8_t y, char *str) {
    uint8_t i=0;
    while(str[i]) {
        OLED_ShowChar(x+i*8, y, str[i]);
        i++;
    }
}

6. ESP8266-WIFI模块 (UART)

#include "stm32f10x.h"
#include "string.h"

#define WIFI_RECV_BUF_LEN 256

uint8_t wifi_recv_buf[WIFI_RECV_BUF_LEN];
uint16_t wifi_recv_len = 0;

void ESP8266_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // PA11-TX, PA12-RX
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART3, &USART_InitStruct);
    USART_Cmd(USART3, ENABLE);
    
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
    NVIC_EnableIRQ(USART3_IRQn);
}

void ESP8266_SendCmd(char *cmd) {
    while(*cmd) {
        USART_SendData(USART3, *cmd++);
        while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
    }
    USART_SendData(USART3, 'r');
    while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
    USART_SendData(USART3, 'n');
    while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
}

uint8_t ESP8266_ConnectCloud(void) {
    ESP8266_SendCmd("AT+CWMODE=1");
    Delay_ms(1000);
    ESP8266_SendCmd("AT+CWJAP="SSID","PASSWORD"");
    Delay_ms(5000);
    ESP8266_SendCmd("AT+CIPSTART="TCP","iotcloud.com",1883");
    Delay_ms(2000);
    
    if(strstr((char*)wifi_recv_buf, "CONNECT")) return 1;
    return 0;
}

// USART3中断服务函数
void USART3_IRQHandler(void) {
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
        uint8_t ch = USART_ReceiveData(USART3);
        if(wifi_recv_len < WIFI_RECV_BUF_LEN-1) {
            wifi_recv_buf[wifi_recv_len++] = ch;
        }
        USART_ClearITPendingBit(USART3, USART_IT_RXNE);
    }
}

3.3 项目核心代码

int main(void) {
    SystemInit();
    Delay_Init();
    
    // 初始化所有模块
    SHT30_Init();
    MHZ19B_Init();
    PMS7003_Init();
    HC_SR501_Init();
    OLED_Init();
    ESP8266_Init();
    
    float temp, humi;
    uint16_t co2;
    PMS7003_Data pm;
    
    while(1) {
        SHT30_ReadData(&temp, &humi);
        co2 = MHZ19B_ReadCO2();
        PMS7003_ReadData(&pm);
        
        // OLED显示
        char str[20];
        sprintf(str, "Temp:%.1fC", temp);
        OLED_ShowString(0, 0, str);
        sprintf(str, "Humi:%.1f%%", humi);
        OLED_ShowString(0, 2, str);
        sprintf(str, "CO2:%dppm", co2);
        OLED_ShowString(0, 4, str);
        
        // 上传云平台
        if(ESP8266_ConnectCloud()) {
            char json[100];
            sprintf(json, "{"temp":%.1f,"humi":%.1f,"co2":%d,"pm25":%d}",
                   temp, humi, co2, pm.pm2_5);
            ESP8266_SendData(json);
        }
    }
}

3.4 寄存器版本

#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

// 硬件引脚定义
#define SHT30_SCL_PIN   6  // PB6
#define SHT30_SDA_PIN   7  // PB7
#define HCSR501_PIN     0  // PB0
#define FAN_RELAY_PIN   5  // PB5
#define BUZZER_PIN      8  // PB8
#define LED_PIN         13 // PC13

// 步进电机控制引脚
#define STEPPER_IN1_PIN 12 // PB12
#define STEPPER_IN2_PIN 13 // PB13
#define STEPPER_IN3_PIN 14 // PB14
#define STEPPER_IN4_PIN 15 // PB15

// 传感器数据结构体
typedef struct {
    float temperature;
    float humidity;
    uint16_t co2_ppm;
    uint16_t pm25;
    uint8_t air_score;
    bool human_detected;
} SensorData;

// 全局变量
volatile SensorData current_data = {0};
volatile uint32_t system_tick = 0;
volatile bool wifi_connected = false;
volatile bool alarm_triggered = false;

// 函数声明
void SystemClock_Config(void);
void GPIO_Init(void);
void I2C_Init(void);
void USART_Init(USART_TypeDef* USARTx, uint32_t baudrate);
void SPI_Init(void);
void TIM2_Init(void);
void SHT30_ReadData(void);
void MHZ19B_ReadCO2(void);
void PMS7003_ReadPM25(void);
void HC_SR501_ReadHuman(void);
void OLED_DisplayUpdate(void);
void ESP8266_SendData(void);
void Control_FanCurtain(void);
void Buzzer_Alert(bool state);

// 延时函数(ms)
void Delay_ms(uint32_t ms) {
    uint32_t start_tick = system_tick;
    while((system_tick - start_tick) < ms);
}

int main(void) {
    // 系统初始化
    SystemClock_Config();
    GPIO_Init();
    I2C_Init();
    USART_Init(USART1, 9600);  // MH-Z19B
    USART_Init(USART2, 9600);  // PMS7003
    USART_Init(USART3, 115200); // ESP8266
    SPI_Init();
    TIM2_Init();  // 系统滴答定时器
    
    // 传感器初始化序列
    Delay_ms(1000);  // 等待传感器上电稳定
    Buzzer_Alert(true);  // 开机提示音
    Delay_ms(200);
    Buzzer_Alert(false);
    
    // 主循环
    while(1) {
        // 每5秒采集一次数据
        if(system_tick % 5000 == 0) {
            SHT30_ReadData();
            MHZ19B_ReadCO2();
            PMS7003_ReadPM25();
            HC_SR501_ReadHuman();
            
            // 空气质量评分算法 (示例)
            current_data.air_score = 100 - (current_data.co2_ppm / 50) - (current_data.pm25 / 10);
            if(current_data.air_score < 0) current_data.air_score = 0;
            
            // 控制执行器
            Control_FanCurtain();
            
            // OLED显示更新
            OLED_DisplayUpdate();
            
            // 上传云端
            if(wifi_connected) {
                ESP8266_SendData();
            }
        }
        
        // 报警检测
        if(current_data.co2_ppm > 1200 || current_data.pm25 > 150 || 
           current_data.temperature > 35.0 || current_data.temperature < 0.0) {
            alarm_triggered = true;
            Buzzer_Alert(true);
        } else {
            alarm_triggered = false;
            Buzzer_Alert(false);
        }
        
        // 系统状态LED闪烁
        if(system_tick % 1000 == 0) {
            GPIOC->ODR ^= (1 << LED_PIN);
        }
    }
}

// 系统时钟配置 (72MHz HSE)
void SystemClock_Config(void) {
    RCC->CR |= RCC_CR_HSEON;  // 开启HSE
    while(!(RCC->CR & RCC_CR_HSERDY));  // 等待HSE就绪
    
    // 配置PLL 8MHz * 9 = 72MHz
    RCC->CFGR |= RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC;
    RCC->CR |= RCC_CR_PLLON;
    while(!(RCC->CR & RCC_CR_PLLRDY));
    
    // 切换系统时钟
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
    
    // 使能外设时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN |
                   RCC_APB2ENR_USART1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_AFIOEN;
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN | RCC_APB1ENR_TIM2EN;
}

// GPIO初始化
void GPIO_Init(void) {
    // PB6(SCL), PB7(SDA) - I2C1
    GPIOB->CRL &= ~(0xFF << (4*6));  // Clear bits
    GPIOB->CRL |= (0xB << (4*6)) | (0xB << (4*7));  // Alternate open-drain
    
    // PB0 - HCSR501输入
    GPIOB->CRL &= ~(0xF << (4*0));
    GPIOB->CRL |= (0x8 << (4*0));  // Input with pull-up
    GPIOB->ODR |= (1 << 0);  // 使能上拉
    
    // PB5 - 风扇继电器
    GPIOB->CRL &= ~(0xF << (4*5));
    GPIOB->CRL |= (0x3 << (4*5));  // Output 50MHz
    GPIOB->ODR |= (1 << 5);  // 初始关闭(低电平触发)
    
    // 步进电机控制引脚
    GPIOB->CRH &= ~(0xFFFF << 0);
    GPIOB->CRH |= (0x3333 << 0);  // PB12-15输出
    
    // PC13 - 系统LED
    GPIOC->CRH &= ~(0xF << (4*13));
    GPIOC->CRH |= (0x3 << (4*13));
    GPIOC->ODR |= (1 << 13);  // 初始关闭(低电平点亮)
    
    // PB8 - 蜂鸣器
    GPIOB->CRH &= ~(0xF << 0);
    GPIOB->CRH |= (0x3 << 0);
    GPIOB->ODR &= ~(1 << 8);  // 初始关闭
}

// I2C1初始化 (SHT30)
void I2C_Init(void) {
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    
    I2C1->CR1 &= ~I2C_CR1_PE;  // 禁用I2C
    
    // 标准模式(100kHz), 72MHz PCLK1
    I2C1->CR2 = 36;  // APB1时钟=36MHz
    I2C1->CCR = 180; // CCR = 36M / (2*100k) = 180
    I2C1->TRISE = 37; // TRISE = 36M/1000ns +1 = 37
    
    I2C1->CR1 |= I2C_CR1_PE;  // 使能I2C
}

// USART初始化
void USART_Init(USART_TypeDef* USARTx, uint32_t baudrate) {
    if(USARTx == USART1) {
        // PA9(TX), PA10(RX)
        GPIOA->CRH &= ~(0xFF << 4);
        GPIOA->CRH |= (0xB << 4) | (0x4 << 8);  // PA9:AF_PP, PA10:IN_FLOAT
    } 
    else if(USARTx == USART2) {
        // PA2(TX), PA3(RX)
        GPIOA->CRL &= ~(0xFF << 8);
        GPIOA->CRL |= (0xB << 8) | (0x4 << 12);
    }
    else if(USARTx == USART3) {
        // PA11(TX), PA12(RX)
        GPIOA->CRH &= ~(0xFF << 12);
        GPIOA->CRH |= (0xB << 12) | (0x4 << 16);
    }
    
    USARTx->BRR = SystemCoreClock / baudrate;
    USARTx->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}

// SPI1初始化 (OLED)
void SPI_Init(void) {
    // PA4(CS), PA5(SCK), PA6(MISO), PA7(MOSI)
    GPIOA->CRL &= ~(0xFFFF << 16);
    GPIOA->CRL |= (0xB << 16) | (0xB << 20) | (0x4 << 24) | (0xB << 28);
    
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SPE;
    SPI1->CR2 = SPI_CR2_SSOE;
}

// TIM2初始化 (系统滴答定时器)
void TIM2_Init(void) {
    TIM2->PSC = 7200 - 1;  // 72MHz/7200 = 10kHz
    TIM2->ARR = 10 - 1;    // 10kHz/10 = 1kHz (1ms)
    TIM2->DIER = TIM_DIER_UIE;
    TIM2->CR1 = TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM2_IRQn);
}

// SHT30温湿度读取
void SHT30_ReadData(void) {
    uint8_t cmd[2] = {0x2C, 0x06};  // 高精度测量命令
    uint8_t data[6];
    
    // I2C写命令
    while(I2C1->SR2 & I2C_SR2_BUSY);
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));
    I2C1->DR = 0x44 << 1;  // SHT30地址
    
    while(!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2;  // 清除ADDR
    
    I2C1->DR = cmd[0];
    while(!(I2C1->SR1 & I2C_SR1_TXE));
    
    I2C1->DR = cmd[1];
    while(!(I2C1->SR1 & I2C_SR1_TXE));
    
    I2C1->CR1 |= I2C_CR1_STOP;
    
    // 等待测量完成
    Delay_ms(20);
    
    // 读取数据
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));
    I2C1->DR = (0x44 << 1) | 0x01;
    
    while(!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2;
    
    for(int i=0; i<6; i++) {
        if(i==5) I2C1->CR1 &= ~I2C_CR1_ACK;
        while(!(I2C1->SR1 & I2C_SR1_RXNE));
        data[i] = I2C1->DR;
    }
    I2C1->CR1 |= I2C_CR1_STOP;
    
    // 数据转换
    current_data.temperature = -45 + 175 * ((data[0]<<8)|data[1]) / 65535.0;
    current_data.humidity = 100 * ((data[3]<<8)|data[4]) / 65535.0;
}

// MH-Z19B CO2读取
void MHZ19B_ReadCO2(void) {
    uint8_t cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
    uint8_t resp[9];
    
    // 发送命令
    for(int i=0; i<9; i++) {
        USART1->DR = cmd[i];
        while(!(USART1->SR & USART_SR_TXE));
    }
    while(!(USART1->SR & USART_SR_TC));
    
    // 接收响应
    for(int i=0; i<9; i++) {
        while(!(USART1->SR & USART_SR_RXNE));
        resp[i] = USART1->DR;
    }
    
    // 校验和验证
    uint8_t checksum = 0;
    for(int i=1; i<8; i++) checksum += resp[i];
    checksum = 0xFF - checksum + 1;
    
    if(resp[8] == checksum) {
        current_data.co2_ppm = (resp[2] << 8) | resp[3];
    }
}

// PMS7003 PM2.5读取
void PMS7003_ReadPM25(void) {
    uint8_t cmd[7] = {0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71};
    uint8_t resp[32];
    
    // 发送命令
    for(int i=0; i<7; i++) {
        USART2->DR = cmd[i];
        while(!(USART2->SR & USART_SR_TXE));
    }
    while(!(USART2->SR & USART_SR_TC));
    
    // 接收响应
    for(int i=0; i<32; i++) {
        while(!(USART2->SR & USART_SR_RXNE));
        resp[i] = USART2->DR;
    }
    
    // 数据解析
    if(resp[0]==0x42 && resp[1]==0x4D) {
        current_data.pm25 = (resp[12] << 8) | resp[13];
    }
}

// HC-SR501人体检测
void HC_SR501_ReadHuman(void) {
    current_data.human_detected = (GPIOB->IDR & (1 << HCSR501_PIN)) ? true : false;
}

// 风扇和窗帘控制
void Control_FanCurtain(void) {
    // 风扇控制 (CO2或温度过高)
    if(current_data.co2_ppm > 1000 || current_data.temperature > 28.0) {
        GPIOB->ODR &= ~(1 << FAN_RELAY_PIN);  // 开启风扇
    } else {
        GPIOB->ODR |= (1 << FAN_RELAY_PIN);   // 关闭风扇
    }
    
    // 窗帘控制 (简单示例: 根据时间控制)
    // 实际应接入RTC或光敏传感器
    static bool curtain_open = false;
    if((system_tick % 86400000) < 43200000) { // 模拟白天
        if(!curtain_open) {
            // 步进电机正转(开窗帘)
            for(int i=0; i<512; i++) {
                GPIOB->ODR = (GPIOB->ODR & ~0xF000) | (0x1 << (i%4 + 12));
                Delay_ms(2);
            }
            curtain_open = true;
        }
    } else { // 夜晚
        if(curtain_open) {
            // 步进电机反转(关窗帘)
            for(int i=0; i<512; i++) {
                GPIOB->ODR = (GPIOB->ODR & ~0xF000) | (0x1 << (3 - i%4 + 12));
                Delay_ms(2);
            }
            curtain_open = false;
        }
    }
}

// 蜂鸣器控制
void Buzzer_Alert(bool state) {
    if(state) GPIOB->ODR |= (1 << BUZZER_PIN);
    else GPIOB->ODR &= ~(1 << BUZZER_PIN);
}

// OLED显示更新 (简化版)
void OLED_DisplayUpdate(void) {
    char buffer[32];
    
    // 模拟SPI传输
    GPIOA->ODR &= ~(1 << 4);  // CS拉低
    
    sprintf(buffer, "T:%.1fC H:%.1f%%", current_data.temperature, current_data.humidity);
    // 实际需要发送OLED初始化序列和显示数据
    
    GPIOA->ODR |= (1 << 4);   // CS拉高
}

// ESP8266数据上传
void ESP8266_SendData(void) {
    char mqtt_msg[128];
    sprintf(mqtt_msg, 
        "{"temp":%.1f,"humi":%.1f,"co2":%d,"pm25":%d,"score":%d,"human":%d}",
        current_data.temperature, current_data.humidity, 
        current_data.co2_ppm, current_data.pm25,
        current_data.air_score, current_data.human_detected);
    
    // 发送AT指令 (简化版)
    USART3->DR = 'A'; // 实际需要完整MQTT协议封装
    while(!(USART3->SR & USART_SR_TXE));
}

// TIM2中断服务函数 (1ms定时)
void TIM2_IRQHandler(void) {
    if(TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;
        system_tick++;
    }
}

四、手机APP上位机开发

4.1 Qt开发环境安装

Qt的中文官网: https://www.qt.io/zh-cn/image-20221207160550486

image-20221207160606892

QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6

打开下载链接后选择下面的版本进行下载:

如果下载不了,可以在网盘里找到安装包下载: 飞书文档记录的网盘地址:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

软件安装时断网安装,否则会提示输入账户。

安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。

image-20221203151742653

选择编译器: (一定要看清楚了)

image-20241028152725134

4.2 新建上位机工程

前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。

【1】新建工程

image-20240117144052547

【2】设置项目的名称。

image-20250420200347498

【3】选择编译系统

image-20240117144239681

【4】选择默认继承的类

image-20240117144302275

【5】选择编译器

image-20241028153603487

【6】点击完成

image-20240117144354252

【7】工程创建完成

image-20250420200411303

4.3 切换编译器

在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。

目前新建工程的时候选择了2种编译器。 一种是mingw32这个编译Windows下运行的程序。 一种是Android编译器,可以生成Android手机APP。

不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。

windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。

下面我这里就选择的 mingw32这个编译器,编译Windows下运行的程序。

image-20250420200424965

4.4 编译测试功能

创建完毕之后,编译测试一下功能是否OK。

点击左下角的绿色三角形按钮

image-20250420200442769

正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。

image-20250420200457319

4.5 设计UI界面与工程配置

image-20250420200514220

打开默认的界面如下:

image-20250420200526194

4.6 总体架构设计

APP整体采用模块化设计,主要分为界面层、逻辑处理层和通信层三大部分:

  • 界面层(UI)
    利用Qt的QML或Widgets技术,设计简洁直观的用户界面,展示环境监测数据、设备状态及控制面板。支持多页面切换,如实时数据页、历史数据分析页、设备控制页和报警通知页。
  • 逻辑处理层
    负责界面交互响应、数据解析与处理、状态管理和用户命令生成。将云平台传回的JSON格式数据解析为本地对象,支持阈值设置和用户个性化配置。
  • 通信层
    采用MQTT协议实现与华为云IoT平台的消息订阅与发布,负责数据同步和远程控制命令的下发。利用Qt的网络模块(如QMqttClient)稳定维护连接,处理消息收发。

4.7 功能模块设计

  • 实时数据展示模块
    显示温湿度、CO?浓度、PM2.5、空气质量评分等环境参数的实时数值和图表趋势,支持刷新和历史数据回溯。
  • 设备控制模块
    允许用户通过界面控制风扇、灯光和窗帘的开关状态,支持手动与自动模式切换,并可设置环境阈值(如温度阈值),触发自动控制。
  • 人员活动统计模块
    展示当天人员进出次数及活动持续时长,为健康行为评估提供数据支持。
  • 异常报警模块
    实时接收并推送环境异常报警,提供声音提示和弹窗提醒,支持报警记录查询。
  • 用户设置模块
    包括登录认证、阈值配置、设备管理(多屋联控)等,保证系统个性化和安全性。

4.8 技术实现细节

  • MQTT通信
    使用Qt的QMqttClient库实现MQTT客户端,连接华为云IoT平台的MQTT Broker,订阅设备属性主题,接收设备上报数据;同时发布控制命令主题,完成远程控制指令的下发。
  • 数据解析与显示
    接收到的JSON格式数据使用Qt JSON模块解析,提取关键属性更新UI显示。采用QChart或第三方绘图库实现实时曲线图展示。
  • 异步事件处理
    利用Qt信号与槽机制实现异步数据处理与界面更新,保证UI响应流畅,避免阻塞。
  • 多线程设计(可选)
    若数据处理和网络通信复杂,可使用Qt的线程机制将网络通信和数据解析放入后台线程,避免影响界面主线程。

4.9 用户体验与交互设计

  • 采用简洁明快的配色和布局,突出环境数据与报警状态,方便用户快速判断木屋状态。
  • 支持手势操作和快捷按钮,实现便捷的设备控制。
  • 异常报警采用视觉+声音双重提醒,保障用户及时响应。
  • 多木屋管理界面支持切换不同木屋设备,方便景区统一管理。

4.10 APP架构设计

当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

4.11 核心功能模块实现方案

1. 通信模块(华为云IoT接入)
// 使用QtMqtt官方库实现
void MqttClient::connectToCloud() {
    QMqttClient* client = new QMqttClient(this);
    client->setHostname("iotda.cn-north-4.myhuaweicloud.com");
    client->setPort(1883);
    client->setUsername("设备ID");
    client->setPassword("加密Token");
    
    // 订阅主题
    client->subscribe("cabin/+/sensor/data"); // +为木屋ID通配符
    client->subscribe("cabin/+/alarm");
    
    // 消息处理
    connect(client, &QMqttClient::messageReceived, [](const QByteArray &msg) {
        QJsonDocument doc = QJsonDocument::fromJson(msg);
        processSensorData(doc.object()); // 数据解析
    });
}
2. 多木屋管理模块
// 木屋设备树形结构
class CabinDevice : public QObject {
public:
    QString cabinID;
    QVector<SensorData> realtimeData;
    DeviceStatus deviceStatus; // 包含风扇/灯光/窗帘状态
    QTimer* dataRefreshTimer;
    
    // 云端控制命令发送
    void sendControlCommand(QString deviceType, int value) {
        QJsonObject cmd {
            {"cmd", deviceType + "_set"},
            {"value", value},
            {"timestamp", QDateTime::currentSecsSinceEpoch()}
        };
        mqttPublish("cabin/"+cabinID+"/control", cmd);
    }
};

4.12 UI界面设计(QML实现)

1. 主界面布局
// main.qml
SwipeView {
    id: view
    RealTimePage { cabin: selectedCabin }   // 实时数据页
    HistoryChartPage {}                     // 历史趋势页
    ControlPanelPage {}                     // 设备控制页
    SettingsPage {}                        // 阈值设置页
}

// 实时数据卡片组件
SensorCard {
    title: "室内环境"
    valueDisplay: Row {
        ValueLabel { label: "温度"; value: cabin.temperature + "℃" }
        ValueLabel { label: "CO?"; value: cabin.co2 + "ppm" }
        ProgressRing { value: cabin.airScore/100 } // 空气质量环形图
    }
}
2. 历史数据分析界面
// 使用QtCharts实现
ChartView {
    LineSeries {
        name: "温度变化"
        axisX: DateTimeAxis { format: "hh:mm" }
        axisY: ValueAxis { min: 0; max: 40 }
        // 从SQLite加载数据
        Component.onCompleted: loadDayData("temperature")
    }
    // 支持手势缩放/平移
}

4.13 关键技术实现点

  1. 双通道通信保障
    • 主通道:华为云MQTT(QTMQTT库)
    • 备用通道:蓝牙BLE(QtBluetooth模块)
    • 自动切换逻辑:
      if (mqttStatus != Connected) {
          if (bleController->connectNearestCabin()) {
              showToast("已切换到蓝牙直连模式");
          }
      }
      
  2. 智能告警系统
    // 基于规则引擎的告警判断
    void checkAlarms(SensorData data) {
        if (data.co2 > userSettings.co2Threshold) {
            triggerAlarm("CO?超标", QString("当前值: %1ppm").arg(data.co2));
        }
        // 无人滞留检测 (结合人体传感器+门磁记录)
        if (lastMotionTime.addSecs(7200) < QDateTime::currentDateTime() 
           && cabinStatus == Occupied) {
            triggerAlarm("异常滞留", "检测到无人状态超过2小时");
        }
    }
    
  3. 节能模式控制逻辑
    void enterPowerSavingMode() {
        sendControlCommand("main_light", 0);
        sendControlCommand("curtain", 0); // 关闭状态
        setDataUploadInterval(300); // 上传间隔改为5分钟
        showNotification("进入节能模式");
    }
    

4.14 数据存储方案

数据类型 存储方式 保留策略
实时监测数据 SQLite内存表 滚动存储24小时
历史趋势数据 SQLite本地数据库 压缩存储30天
报警事件记录 SQLite+云端备份 永久保留
用户设置 QSettings 本地持久化

4.15 跨平台适配策略

  1. 移动端优化
    • Android/iOS使用QT Quick Controls 2
    • 手势支持:左滑切换木屋/右滑调出菜单
    • 推送服务:集成Firebase Cloud Messaging(iOS)和华为Push(Android)
  2. 桌面端增强
    • 多窗口视图:支持同时监控多个木屋
    • WebSocket直连:景区管理后台直连本地服务器
    • 大屏仪表盘模式

五、总结

本智能康养木屋监测系统基于STM32嵌入式平台,集成多种环境传感器和智能控制模块,实现了对木屋内温湿度、CO?浓度、PM2.5等关键环境参数的实时监测与分析。系统支持人员活动检测、智能风扇、窗帘和灯光联动控制,配备本地OLED显示和蜂鸣器报警,保证了居住环境的舒适性与安全性。通过ESP8266模块实现与华为云物联网平台的无线连接,结合MQTT协议,实现了数据的远程上传与手机APP的远程监控与控制。系统还具备异常报警、节能模式及离线运行机制,增强了实用性和可靠性。整体设计合理、功能完善,满足康养木屋智能化管理需求,具有良好的应用前景和推广价值。未来可进一步拓展多屋并联管理和更多智能化功能,推动康养木屋向更高水平发展。

  • 更多详细资料请联系.docx
    下载

相关推荐