扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

基于STM32的酒窖环境监测系统的设计与实现

04/01 10:29
256
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

一、前言

1.1 项目介绍

【1】项目开发背景

随着人们生活水平的不断提高和消费升级趋势的日益明显,葡萄酒、威士忌等高端酒类的消费市场持续扩大。对于酒类收藏爱好者、高端餐厅以及酒类经销商而言,如何长期、稳定地储存这些对环境极为敏感的酒品,成为了一个核心痛点。酒类的陈化过程对环境参数有着严苛的要求,尤其是温度和湿度。温度过高会加速酒中化学物质的氧化,导致酒体变质;温度过低则会影响酒的自然熟成。湿度过高容易引发霉菌滋生,导致酒标损坏甚至污染酒液;而湿度过低则可能使软木塞干缩,造成密封不严,空气进入导致酒液过度氧化。因此,构建一个能够全天候精准监控并自动调节环境的智能化酒窖系统,具有重要的现实意义和应用价值。

在实际的酒窖环境中,除了基础的温湿度因素,空气质量同样是影响储存品质的关键。由于酒窖多为密闭空间,空气流通性较差,长期存放的酒类会因微量挥发产生醇类、酯类等挥发性有机物(VOCs)。当这些气体浓度累积到一定程度时,不仅会形成不良气味,影响酒窖内的整体空气质量,还可能对工作人员进入维护时的身体健康造成潜在威胁。尤其是在储存大量未完全密封的酒桶或新酒时,酒精挥发更为显著。因此,在酒窖环境监测中引入VOCs气体浓度检测,不仅是对酒品储存环境的完善,更是对安全管理水平的重要提升。

针对上述问题,传统的人工巡检方式存在效率低、响应滞后、数据记录不连续等明显不足,难以满足现代精细化管理的要求。随着物联网(IoT)、嵌入式技术以及云计算平台的快速发展,构建一套集本地自动控制与远程云端监控于一体的环境监测系统成为可行的解决方案。通过部署传感器实时采集环境数据,结合本地控制逻辑实现对排风扇等执行设备的自动调节,能够在环境参数超出设定阈值时第一时间启动干预措施,确保酒窖环境始终维持在最优区间。这种自动化模式有效避免了人为疏忽带来的损失,保障了酒品储存的长期稳定性。

为了进一步提升系统的可管理性和用户体验,将设备数据接入云平台是当前物联网应用的主流趋势。通过NBIoT等低功耗广域网通信技术,设备可以将采集到的温湿度、VOCs浓度等信息稳定上传至华为云等物联网平台。云平台作为数据汇聚与分发的核心枢纽,不仅支持数据的长期存储与分析,还能够为上层应用提供标准化的数据接口。在此基础上,开发跨平台的移动端和桌面端应用程序,可以让用户随时随地查看酒窖内的实时环境状态,并支持远程手动控制排风扇等设备,实现了从被动应对到主动管理的转变。

此外,考虑到不同用户对系统灵活性的需求,系统还应当具备参数可配置性和多端展示能力。用户可以通过本地按键对温度、湿度、VOCs浓度的阈值进行自主设定,以适应不同酒类对储存环境的差异化要求。同时,基于Python Web框架搭建的服务器可从云平台获取数据并生成可视化网页界面,支持通过电脑或手机浏览器访问,进一步降低了用户的使用门槛。这种“端—云—应用”一体化的系统架构,不仅满足了当前酒窖环境监控的实际需求,也为未来接入更多环境监测参数、实现更智能化的酒窖管理提供了可扩展的技术基础。

综上所述,本项目设计并实现一套基于STM32微控制器的酒窖环境监测系统。系统综合运用嵌入式控制、传感器检测、NBIoT无线通信、云平台数据对接以及跨平台应用开发等技术,实现酒窖内部温湿度与VOCs浓度的实时监测、本地自动调节、远程云端监控以及多终端可视化管理的功能。该系统的实现将为酒类储存提供一套高效、可靠、智能的环境保障方案,具有较强的工程应用价值和市场推广前景。

【2】设计实现的功能

(1)支持酒窖环境温度和湿度的实时采集,当温度高于设定阈值时,自动启动排风扇进行通风降温,保证酒品储存环境稳定。

(2)支持监测VOCs可挥发有机气体浓度,当VOCs浓度过高时,自动开启排风扇进行换气,同时可根据VOCs浓度评估当前酒窖空气质量。

(3)支持显示屏实时显示酒窖内各项环境参数,包括温度、湿度、VOCs浓度等信息,方便现场查看环境状态。

(4)支持数据上云,利用NBIOT-BC26模块将采集到的酒窖环境数据通过MQTT协议上传至华为云物联网平台,实现远程监测。

(5)支持Android手机APP和Windows电脑APP远程查看酒窖环境数据,并支持远程控制相关设备,APP通过云平台获取数据,实现对酒窖环境的可视化管理。

(6)实现自动模式功能,当温度或VOCs浓度超过设定阈值时,系统自动开启排风扇进行环境调节,保证酒窖内部环境长期稳定。

(7)支持按键对各项阈值进行设置,包括温度阈值、湿度阈值以及VOCs浓度阈值,同时支持显示界面切换,方便用户操作与参数调整。

(8)支持基于Python的Web服务器功能,服务器从华为云物联网平台获取设备上传的数据,并通过HTTP服务对外提供接口,用户可通过浏览器访问可视化网页,实现酒窖环境数据的实时查看,支持手机端和电脑端访问。

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

【3】项目硬件模块组成

(1)主控芯片:采用STM32F103C8T6作为系统主控单元,负责传感器数据采集逻辑控制、通信处理及外设驱动管理。

(2)温湿度检测模块:采用DHT11传感器,用于实时采集酒窖环境的温度和湿度数据。

(3)VOCs气体检测模块:采用SGP30气体传感器,用于监测酒窖内可挥发有机气体浓度,评估空气质量及酒精挥发情况。

(4)报警提示模块:采用高电平触发的有源蜂鸣器,用于系统异常情况下的声音提示。

(5)显示模块:采用IIC协议的0.96寸OLED显示屏,用于实时显示温度、湿度、VOCs浓度等环境参数及系统状态信息。

(6)无线通信模块:采用NBIOT-BC26模块,实现系统通过MQTT协议将采集到的环境数据上传至华为云物联网平台。

(7)通风执行模块:采用5V直流风扇,用于酒窖内空气流通与环境调节,在温度或VOCs浓度超标时自动启动。

(8)供电模块:采用Type-C接口供电,为整个系统提供稳定的电源输入。

【4】设计意义

本项目的设计具有重要的实际应用价值。酒类作为一种对环境极为敏感的商品,其储存条件直接关系到酒品的品质与价值。传统的酒窖管理方式多依赖人工巡检和经验判断,存在数据记录不连续、异常响应滞后、人力成本高等问题。通过设计一套基于STM32的自动环境监测与控制系统,能够实现对酒窖内部温度、湿度及VOCs浓度的全天候实时监测,并在参数超标时自动启动排风扇进行调节,有效避免了因环境波动导致的酒品变质风险,提升了酒窖管理的自动化水平和可靠性。

在技术层面,本项目的设计充分体现了物联网技术的集成应用优势。系统通过NBIOT-BC26模块将采集到的环境数据以MQTT协议上传至华为云物联网平台,实现了设备与云端的稳定通信。NBIoT技术具备低功耗、广覆盖、高连接数等特点,尤其适用于酒窖这类室内封闭场景的无线数据传输。通过数据上云,用户无需亲临现场即可掌握酒窖内的环境状况,为远程监控与管理提供了技术支撑,也为后续数据分析和智能化决策奠定了基础。

从用户体验角度出发,本项目的设计注重多终端可视化管理能力的构建。基于Qt框架开发的Android手机APP和Windows电脑APP,能够从云平台获取数据并实现远程查看与控制,满足用户在不同场景下的使用需求。同时,系统配备本地OLED显示屏和按键交互功能,用户可直接在现场查看环境参数并进行阈值设置与界面切换,兼顾了远程管理的便捷性与本地操作的直观性。这种多层次的人机交互设计,大大提升了系统的易用性和实用性。

本项目的设计还体现了系统架构的开放性与可扩展性。通过搭建基于Python Flask框架的Web服务器,系统能够从华为云平台获取设备数据并提供HTTP接口,用户通过浏览器即可访问可视化页面,实时查看酒窖环境数据。这一设计不仅为用户提供了跨平台访问的便利,也为未来接入更多环境监测参数、实现多酒窖集中管理或与其他智能家居系统联动预留了扩展空间,具有良好的技术前瞻性和推广价值。

综上所述,本项目的设计不仅解决了当前酒窖环境管理中存在的实际问题,提高了酒品储存的安全性与稳定性,也探索了嵌入式控制、物联网通信、云平台应用与多端软件开发相结合的技术路径,为类似环境监控系统的开发提供了可参考的解决方案,具有较强的工程实践意义和应用推广前景。

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

目前,市面上针对酒窖环境监测与管理的产品主要分为三大类:消费级智能传感器、专业级酒窖环境控制系统,以及基于物联网平台的综合管理解决方案。这些产品在功能定位、技术路线和适用场景上各有侧重,为用户提供了不同层次的选择。

在消费级产品领域,以小米智能家居生态为代表的多功能环境传感器较为常见。例如小米温湿度传感器、青萍空气质量监测仪等产品,通常采用蓝牙或Wi-Fi通信方式,配合手机APP实现数据查看和简单的联动控制。用户可以通过米家APP设置自动化场景,如当温度过高时联动智能插座开启风扇或空调。这类产品价格亲民、安装便捷,适合家庭小型酒柜或家用酒窖使用。但其局限性也较为明显:传感器种类相对单一,通常仅支持温湿度监测,缺乏VOCs等专业气体检测能力;联动控制依赖家庭Wi-Fi网络,在信号不佳的酒窖环境中可能存在通信不稳定的问题;同时,其控制逻辑较为基础,难以满足专业酒窖对多参数协同控制和离线自动运行的需求。

在专业级酒窖控制系统领域,国际品牌如CaveauWine Guardian等推出的环境控制解决方案占据了一定市场份额。这类产品通常采用一体化设计,集成了温湿度控制、空气净化、通风调节等功能,部分高端型号还具备远程监控能力。以Caveau的智能酒窖控制系统为例,其系统能够精确控制温度在±1℃范围内,并配备专用传感器用于监测湿度及空气质量,通过专用控制器或网页端进行参数设置与状态查看。这类产品性能稳定、控制精度高,但其价格较为昂贵,系统安装和维护需要专业人员操作,且多为封闭式系统,难以与其他智能家居平台或第三方云平台进行数据对接,对于追求个性化定制和开放式管理的用户而言,灵活性有所不足。

在基于物联网平台的综合管理方案方面,近年来国内涌现出一批专注于智慧农业和仓储环境监控的企业,如北京昆仑海岸山东仁科测控等。这些企业提供的环境监控系统通常采用工业级传感器,支持RS485、4G、NBIoT等多种通信方式,可将数据上传至自有云平台或第三方物联网平台。用户通过Web端或手机APP即可查看实时数据和历史曲线,并支持设置报警阈值和联动控制。以山东仁科测控的酒窖环境监控方案为例,其系统支持温湿度、CO2、VOCs等多种参数的监测,采用NBIoT通信实现数据上云,适用于专业酒窖、仓库等场景。这类方案在数据采集和远程监控方面较为完善,但通常以标准化的成品设备形式提供,用户可自定义的参数范围和联动策略相对有限,且系统扩展往往需要额外购买配套设备,成本控制难度较大。

综上所述,当前市面上的酒窖环境监测产品虽已有较多选择,但仍存在一定的不足之处。消费级产品功能单一,难以满足专业酒窖对多参数协同监测的需求;专业级系统性能优异但价格高昂且扩展性受限;物联网监测方案在数据上云和远程管理方面较为成熟,但在本地自动控制、用户自定义阈值设置以及跨终端交互的灵活性方面仍有提升空间。因此,设计一套基于STM32、集成本地自动控制与云端远程管理功能、支持多终端访问的酒窖环境监测系统,具有一定的创新性和实际应用价值。

【6】摘要

随着酒类收藏与消费市场的持续发展,酒窖作为高端酒品的储存场所,其环境参数的稳定性直接关系到酒品的品质与价值。针对传统酒窖管理依赖人工巡检、响应滞后、缺乏远程监控等问题,本文设计并实现了一套基于STM32的酒窖环境监测系统。系统以STM32F103C8T6为主控芯片,通过DHT11传感器采集温湿度数据,SGP30传感器监测VOCs可挥发有机气体浓度,并利用0.96寸OLED显示屏实时显示环境参数。当温度或VOCs浓度超过用户设定的阈值时,系统自动控制直流风扇启动,实现通风降温与换气功能。系统通过NBIOT-BC26模块将采集到的环境数据以MQTT协议上传至华为云物联网平台,实现数据的云端存储与管理。在此基础上,采用Qt框架开发了支持Android和Windows双平台的APP,用户可远程查看环境数据并手动控制排风扇。同时,基于Python Flask框架搭建了Web服务器,通过调用华为云API接口获取设备数据,提供可视化网页供用户通过浏览器访问。系统还支持本地按键进行阈值设置与界面切换,兼顾了自动化控制与用户交互的灵活性。实际测试表明,该系统运行稳定,能够有效实现酒窖环境的实时监测、自动调节与远程管理,具有良好的工程应用价值。

关键字

STM32;酒窖环境监测;温湿度检测;VOCs检测;NBIoT;MQTT;华为云;Qt;远程控制;物联网

1.2 设计思路

本项目的整体设计思路围绕“感知—控制—通信—应用”四个层面展开,构建一个完整的物联网闭环系统。在感知层面,系统通过部署多种传感器实时采集酒窖环境的关键参数。采用DHT11传感器监测温度和湿度,满足酒窖基础环境监测需求;采用SGP30气体传感器监测VOCs可挥发有机气体浓度,用于评估酒窖内空气质量和酒精挥发情况。传感器数据由STM32F103C8T6主控芯片通过I/O口和IIC协议进行读取与解析,确保数据采集的准确性和实时性。

在控制层面,系统设计了本地自动控制与用户手动控制相结合的工作模式。STM32主控芯片将采集到的温湿度及VOCs数据与用户预先设定的阈值进行实时比较。当温度超过设定阈值或VOCs浓度超过设定阈值时,系统自动控制5V直流风扇启动,进行通风降温或换气操作。系统支持自动模式与手动模式切换,用户可通过APP远程选择运行模式。同时,系统配备有源蜂鸣器,在环境参数异常时提供声音提示,便于现场人员及时了解异常状况。

在通信层面,系统采用NBIOT-BC26模块实现数据上云。STM32通过AT指令控制BC26模块接入华为云物联网平台,将温度、湿度、VOCs浓度、运行模式状态、风扇开关状态等数据以MQTT协议格式周期性上传。华为云平台作为数据汇聚中心,负责设备接入管理、数据存储与转发,并为上层应用提供API接口。NBIoT技术具备低功耗、广覆盖、穿透性强的特点,适用于酒窖等室内封闭环境,保证了数据传输的稳定性和可靠性。

应用层面,系统设计了多终端的数据访问与控制方式。一方面,采用Qt框架开发了Android手机APP和Windows电脑上位机软件,通过调用华为云平台的API接口获取设备数据,实现环境参数的远程查看和排风扇的手动控制,支持运行模式的切换。另一方面,基于Python Flask框架搭建了Web服务器,服务器从华为云平台获取设备上传的数据,通过HTTP服务提供可视化网页界面,用户使用手机或电脑浏览器即可访问,无需安装额外软件。这种多终端应用设计满足了不同用户的使用习惯和场景需求。

在交互与配置层面,系统充分考虑了用户本地操作的需求。配备0.96寸OLED显示屏,实时显示温度、湿度、VOCs浓度以及系统状态信息,方便现场人员快速了解当前环境状况。同时设计了按键交互模块,用户可通过按键对温度阈值、湿度阈值、VOCs浓度阈值进行设置,并支持显示界面的切换操作。本地按键配置与云端远程配置相结合,使得系统的参数调整更加灵活便捷,适应不同酒类储存环境的差异化需求。

1.3 系统功能总结

功能模块 功能编号 功能描述
环境数据采集 1 支持通过DHT11传感器实时采集酒窖环境温度和湿度数据
2 支持通过SGP30传感器实时采集VOCs可挥发有机气体浓度数据
本地显示 3 支持0.96寸OLED显示屏实时显示温度、湿度、VOCs浓度等环境参数
4 支持显示屏界面切换功能,方便查看不同信息
本地控制 5 当温度超过设定阈值时,自动启动排风扇进行通风降温
6 当VOCs浓度超过设定阈值时,自动启动排风扇进行换气
7 支持自动模式功能,实现温度或VOCs超标时自动调节
8 支持高电平触发有源蜂鸣器,用于异常情况声音提示
参数配置 9 支持按键对温度阈值进行设置
10 支持按键对湿度阈值进行设置
11 支持按键对VOCs浓度阈值进行设置
数据上云 12 支持NBIOT-BC26模块通过MQTT协议将数据上传至华为云物联网平台
13 上传数据包括温度、湿度、VOCs浓度、运行模式、风扇状态等参数
远程监控 14 支持Android手机APP远程查看酒窖环境数据
15 支持Windows电脑APP远程查看酒窖环境数据
16 支持Android手机APP远程控制排风扇开关
17 支持Windows电脑APP远程控制排风扇开关
18 支持通过APP远程切换运行模式(自动模式/手动模式)
Web服务 19 支持基于Python Flask框架搭建Web服务器
20 支持Web服务器从华为云平台获取设备数据
21 支持通过HTTP服务提供可视化网页界面
22 支持手机端和电脑端浏览器访问可视化网页,实时查看环境数据
跨平台兼容 23 Android APP基于Qt框架开发,支持Android系统
24 Windows APP基于Qt框架开发,支持Windows系统
25 Web网页支持响应式访问,兼容手机端和电脑端浏览器

1.4 开发工具的选择

【1】设备端开发

本项目设备端开发基于STM32F103C8T6主控芯片,采用C语言进行程序设计,通过寄存器级编程方式直接操作硬件底层,确保系统运行的高效性和实时响应能力。C语言在嵌入式开发中具有良好的可移植性和硬件访问能力,能够满足本系统对资源消耗和响应速度的严格要求。

开发工具选用Keil uVision 5作为集成开发环境。Keil是ARM架构微控制器开发的主流工具,提供了完善的代码编辑、编译、调试和仿真功能。在Keil环境下,开发者可以通过寄存器直接控制STM32F103C8T6的GPIO、IIC、ADC等外设接口,实现对DHT11温湿度传感器、SGP30气体传感器、OLED显示屏、有源蜂鸣器、直流风扇以及NBIOT-BC26模块的精确控制。寄存器级编程避免了标准外设库的函数调用开销,提升了代码执行效率,对于需要快速响应传感器数据和控制执行设备的场景尤为重要。

Keil uVision 5支持在线调试和断点设置,能够帮助开发者在软件开发过程中实时查看寄存器值和变量状态,便于定位和解决底层驱动问题。同时,Keil提供了丰富的编译优化选项,可以在保证功能正确的前提下生成紧凑高效的机器码,适配STM32F103C8T6有限的存储资源。

【2】上位机开发

本项目上位机开发包括Android手机APP和Windows电脑APP两部分,采用Qt 5框架C++语言进行跨平台开发。Qt是一个成熟的跨平台应用开发框架,提供了丰富的GUI组件、网络通信模块和多线程支持,能够高效实现数据可视化展示和远程控制功能。C++作为底层编程语言,具有良好的执行效率和内存控制能力,适合处理与云平台的数据交互和业务逻辑实现。

开发环境选用Qt Creator作为集成开发工具。Qt Creator提供了代码编辑、界面设计、调试构建等一体化功能,其内置的Qt Designer支持可视化拖拽式界面布局,便于快速构建APP的用户界面。同时,Qt Creator支持Android和Windows双平台的项目配置与构建,开发者可以在一套代码基础上通过切换编译套件生成不同平台的可执行文件,极大提高了开发效率。

在通信方面,上位机APP通过HTTP/HTTPS协议调用华为云物联网平台的API接口,获取设备上传的温度、湿度、VOCs浓度等实时数据,并下发风扇控制和运行模式切换等指令。Qt框架中的QNetworkAccessManager类提供了便捷的HTTP请求封装,支持异步通信机制,确保APP界面在数据请求过程中保持流畅响应。此外,APP通过JSON格式解析云平台返回的数据,实现环境参数的动态更新和图表展示。

针对Windows电脑APP,程序采用标准桌面应用窗口布局,支持数据实时刷新、历史曲线查看和远程控制按钮操作。针对Android手机APP,程序适配移动端触摸交互特性,界面简洁直观,便于用户在手机上快速查看酒窖环境状态并进行远程操作。Qt框架的跨平台特性使得两个平台的APP在界面风格和操作逻辑上保持一致,降低了用户的学习成本和使用门槛。

【3】Web服务器开发

本项目Web服务器采用Python语言进行开发,基于Flask轻量级Web框架构建后端服务。Flask框架简洁灵活,能够快速搭建RESTful API接口和动态网页服务,适合本项目中对数据获取和页面渲染的需求。

开发工具选用PyCharmVS Code作为Python集成开发环境,两者均提供代码补全、调试支持和虚拟环境管理功能,便于进行Flask应用的开发和调试。Web服务器通过调用华为云物联网平台提供的API接口,获取设备上报的环境数据,并将数据以JSON格式封装后传递给前端页面。

前端可视化页面采用HTMLCSSJavaScript技术实现,结合ECharts等图表库,将温度、湿度、VOCs浓度等数据以折线图、仪表盘等形式直观展示。页面设计响应式布局,自动适配手机端和电脑端浏览器的屏幕尺寸,确保用户在不同设备上均能获得良好的查看体验。Web服务器通过Flask内置的HTTP服务对外提供访问接口,用户只需在浏览器中输入服务器地址即可访问可视化页面,无需安装任何客户端软件。

【4】云平台选择

本项目云平台选用华为云物联网平台作为数据汇聚与管理的核心。华为云物联网平台支持设备通过MQTT协议接入,提供设备管理、数据存储、消息转发和API调用等功能。设备端通过NBIOT-BC26模块以MQTT协议将温度、湿度、VOCs浓度、运行模式、风扇状态等数据上报至华为云平台,平台负责数据的持久化存储并为上位机APP和Web服务器提供统一的API接口。

华为云平台提供了完善的设备接入鉴权机制和数据转发规则,保障数据传输的安全性和可靠性。同时,平台支持自定义物模型,开发者可以根据项目需求定义设备属性(如Temp、humi、VOC、motor_sw、run_mode等),方便后续的数据解析和应用开发。

综上所述,本项目的开发工具选择充分考虑了嵌入式硬件特性、跨平台应用需求以及云端数据交互的便利性,构建了一套从设备端到应用层的完整开发工具链,为系统的稳定实现和功能完善提供了技术保障。

1.5 模块的技术详情介绍


(1)主控芯片:STM32F103C8T6

功能:作为整个系统的控制核心,负责读取各传感器数据、执行控制逻辑、驱动显示模块、处理按键输入、控制通信模块数据收发以及控制执行设备的启停。

特点:基于ARM Cortex-M3内核,主频最高72MHz,具有64KB Flash和20KB SRAM,集成丰富的GPIO口和多种外设接口(IIC、SPI、USART等),具备强大的处理能力和低功耗特性,适合嵌入式实时控制应用。

(2)温湿度检测模块:DHT11

功能:用于实时采集酒窖环境中的温度和湿度数据,为环境监测提供基础参数。

特点:采用单总线通信方式,与主控芯片连接简单;温度测量范围为0℃至50℃,湿度测量范围为20%至90%RH;具有成本低、响应快、稳定性好的特点,适用于常规酒窖环境监测需求。

(3)VOCs气体检测模块:SGP30

功能:用于实时监测酒窖空气中可挥发有机气体浓度,评估空气质量及酒精挥发情况。

特点:采用IIC通信接口,与主控芯片连接方便;可输出TVOC(总挥发性有机物)浓度和eCO2(等效二氧化碳)浓度;具备高灵敏度和长期稳定性,能够精确检测微量气体变化,适用于密闭空间空气质量监测。

(4)显示模块:0.96寸OLED显示屏

功能:用于实时显示温度、湿度、VOCs浓度等环境参数以及系统运行状态,方便现场人员查看。

特点:采用IIC通信协议,占用引脚少;分辨率128×64,显示清晰;自发光、功耗低、视角广,适合嵌入式设备的信息展示需求。

(5)无线通信模块:NBIOT-BC26

功能:负责将采集到的环境数据通过MQTT协议上传至华为云物联网平台,实现数据的远程传输与云端管理。

特点:支持NB-IoT通信制式,具备低功耗、广覆盖、高穿透性的特点,适合酒窖等室内封闭环境;通过AT指令控制,与主控芯片通过USART接口通信;支持MQTT协议,便于对接主流物联网云平台。

(6)通风执行模块:5V直流风扇

功能:用于酒窖内的空气流通与环境调节,当温度或VOCs浓度超过设定阈值时自动启动,实现通风降温或换气。

特点:采用直流供电,驱动简单;体积小巧、噪音低,适合小型酒窖环境;通过继电器MOS管与主控芯片连接,实现开关控制。

(7)报警提示模块:有源蜂鸣器

功能:用于在环境参数异常时提供声音提示,提醒现场人员及时关注酒窖环境状况。

特点:高电平触发,驱动简单;声音响度适中,能够有效引起注意;功耗低,响应迅速,适用于异常状态报警场景。

(8)供电模块:Type-C接口

功能:为整个系统提供稳定的电源输入,保证各模块正常工作。

特点:采用Type-C接口,支持正反插,使用方便;兼容5V标准电压输入,适配常见的手机充电器移动电源等供电设备;接口通用性强,便于系统部署和维护。

(9)人机交互模块:按键

功能:用于用户对系统进行本地操作,包括温度阈值、湿度阈值、VOCs浓度阈值的设置,以及显示屏界面的切换。

特点:采用独立按键设计,与主控芯片GPIO口直接连接;响应迅速,操作简单,为用户提供便捷的本地参数配置和界面控制方式。

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

华为云官网: 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

三、Qt开发入门与环境搭建

当前项目的上位机是采用Qt开发的,这一章节主要是介绍Qt开发环境的安装,以及Qt开发环境入门的使用。如果你Qt没有任何基础,建议仔细看一遍。

3.1 Qt是什么?

Qt 是一个功能强大、跨平台的应用程序开发框架,主要用于创建图形用户界面(GUI)应用程序,但它不仅仅局限于GUI编程。它由挪威的奇趣科技(TrollTech)最初于1991年开发,并在后续的发展历程中经历了多次所有权变更,包括诺基亚和Digia等公司接手,现在Qt属于The Qt Company所有。

Qt 主要特点和优势包括:

(1)跨平台:Qt 支持多种操作系统,开发者可以使用同一份源代码在不同平台上编译运行,如Windows、Linux、macOS、Android以及各种嵌入式系统(如RTOS),实现“一次编写,到处编译”。

(2)C++ 开发:Qt 的核心是基于C++编程语言构建,提供了一套丰富的类库,通过面向对象的设计方式简化了开发过程。

(3)图形用户界面:Qt 提供了完整的GUI组件集,包含窗口、按钮、标签、文本框等各种标准控件,以及布局管理器、样式表等功能,使得开发者能够高效地创建美观且功能完善的桌面应用或移动应用界面。

(4)工具链完整:Qt 包含一系列集成开发环境(IDE)和辅助工具,例如Qt Creator是一个全能的跨平台IDE,Qt Designer用于可视化拖拽设计UI界面,Qt Linguist支持国际化资源文件的翻译,还有Qt Assistant和大量文档资源方便开发者的使用。

(5)非GUI功能丰富:除了GUI功能外,Qt 还提供了众多非图形化功能模块,如网络通信、数据库访问、XML处理、多媒体处理(音频视频)、文件I/O、线程与并发处理、OpenGL和3D图形渲染等。

(6)元对象系统:Qt 使用元对象系统(Meta-Object System, MOC)实现了信号与槽机制(Signals and Slots),这是一种高级事件处理机制,允许在不同对象之间安全地进行异步通信。

(7)可扩展性与灵活性:Qt 架构高度灵活,支持插件体系结构,开发者可以根据需要自定义组件并轻松地集成到Qt应用中。

Qt 以其强大的跨平台能力和全面的功能集合成为许多企业和个人开发者选择用来开发高性能、高稳定性的应用程序的重要工具之一,被广泛应用于各类桌面软件、嵌入式设备、移动应用以及服务器端组件等领域。

3.2 Qt版本介绍

在Qt发行版本中将要涉及两个版本:Qt商业授权和Qt开源授权。

(1)Qt商业授权是设计商业软件的开发环境,这些商业软件使用了传统的商业来发布,它包含了一些更新的功能、技术上的支持和大量的解决方案,开发了使用于行业的一些特定的组件,有一些特殊的功能只在商业用户中使用。

(2)Qt开源授权是用来开发开源的软件,它提供了一些免费的支持,并遵循QPL协议。

开放源代码是免费的软件,不牵涉用户的某些权益。任何人都有使用开源软件和参与它的修改的机会,这就意味着其他的人同样可获得你开发的代码。目前 Qt 的开源授权有两种,一种是 GPL 授权,另一种是 LGPL 授权。

3.3 Qt开发环境安装

Qt的中文官网: https://www.qt.io/zh-cn/![image-20221207160550486](https://led-obs.obs.cn-north-1.myhuaweicloud.com/Blog/img/image-20221207160550486.png)

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

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

qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details

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

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

安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。

选择MinGW 32-bit 编译器:

3.4 开发第一个QT程序

在QT开发过程中,可以手动编写代码也可以使用UI设计师直接拖拽控件的方式编写界面和布局,在实际的开发过程中一般是两种方式结合使用,提高开发效率。

本小节用一个简单的 "Hello QT" 程序介绍一下使用QtCreator新建工程的步骤

(1)打开QtCreator软件,选择New Project,新建一个工程。

(2)项目模板选择QT Widgets Application

(3)设置项目名称和存放路径

注意:QT项目路径和名称不能出现中文字符。

(4)编译工具套件选择

编译工具套件可以后面自己增加,比如增加Android的。套件是指 Qt 程序从编译链接到运行环境的全部工具和 Qt 类库的集合。

(5)设置生成的类信息

在类信息设置界面选择基类,目前有三种基类:QMainWindow,QWidget,QDialog。在基类里选择QMainWindow,类名和文件名会根据基类自动修改,一般不需要修改,默认即可。

(6)项目管理

在项目管理界面可以设置作为子项目,以及加入版本控制系统。这两个功能暂时用不到,都用默认的  ,然后点击 “完成”。

(7)创建完成

(8) 编辑代码

展开main.cpp文件,添加内容如下:

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <QLabel>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
    QLabel *label =new QLabel("Hello Qt!");
    label->setGeometry(400,100,100,20);
    label->show();
    return a.exec();
}

代码解析:

1)    #include <QApplication>和 #include <QLabel>是QT的类声明头文件,对于每个QT类都有一个与该类同名的头文件,在这个头文件包含了对该类的定义。
2)    main(int argc, char *argv[]) :main函数的标准写法。
3)    QApplication a(argc, argv):创建一个QApplication对象,用于管理应用程序的资源,QApplication类的构造函数需要两个参数。
4)    QLabel *label =new QLabel("Hello Qt!") :创建QLabel窗口部件,QLabel是一个Qt提供的窗口部件,可以用来显示一行文本。
5)    label->setGeometry(400,100,100,20) : 设置控件显示的位置。
6)    label->show():使Qlabel创建的窗口可见,就是显示设置的文本。
7)    return a.exec():应用程序将控制权传递给QT,让程序进入消息循环。等待可能的菜单,工具条,鼠标等的输入,进行响应。

(9)行程序

运行程序可以点击左下角的三角形符号或者按下快捷键Ctrl+R。

3.5 调试输出

QT中使用QDebug类输出调试信息。主要用于调试代码,类似于std::cout的替代品,支持QT的数据类型。使用前需要包含头文件。

调试输出的分类

qDebug 调试信息提示
qWarning 一般的警告提示
qCritical 严重错误提示
qFatal 致命错误提示

示例代码:

qDebug("调试信息输出");
qWarning("一般警告信息输出");
qCritical("严重错误输出");
qFatal("致命错误输出");

qDebug输出的信息会打印到QT软件下边的输出面板。

在上节的HelloQt工程上加上调试输出代码,增加的main.cpp代码如下:

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
    qDebug()<<"QT调试信息输出";
    int data_int=8888;
    qDebug()<<data_int;
    float data_float=123.888;
    qDebug()<<data_float;
    return a.exec();
}

运行程序,观察输出的调试信息:

3.6 QT Creator常用的快捷键

掌握一些适用的快捷键,可以提高程序开发的效率。

(1)F1 键,快速切换光标选中的函数或者类的帮助信息,按一次半屏显示,按下两次全屏显示。

(2)F2 键,快速切换到光标选中的函数或者类的源码定义处。

(3)F4键,快速在源文件和头文件之间切换。

(4)Ctrl(按住)+ Tab,快速切换已打开的文件

(5)Ctrl+ I ,缩进光标选中行代码(自动与上层代码对齐)。

(6)Ctrl + / ,快速注释或者取消注释光标选中行。

(7)快速修改全局变量名

鼠标光标选中变量名,按下Ctrl+Shift+R,当变量名称出现红色框表示已经激活全局修改功能。修改一处,整个工程对应变量名称全部会修改。修改完毕之后,光标移开,再按下Ctrl+Shift+R保存修改。

(8)快速修改全局函数名

快捷方式与变量修改一样按下Ctrl+Shift+R,一处修改整个工程对应的函数名称也会跟着改。选中函数后,按下Ctrl+Shift+R后整个工程的对应的函数名会高亮,并且在软件下方弹出修改框。

3.7 QT帮助文档

Qt 帮助文档太多,难以全部翻译成中文,即使翻译了一部分,翻译花的时间太多,翻译更新的时效性也难以保证,最终还是得看英文帮助,QtCreator 集成了帮助系统,查找非常方便。

打开QtCreator,选择菜单栏的最左边的帮助选项,界面如下:

(1)查看Qlabel控件的帮助信息:

3.8 UI设计师使用

上节的Hello QT程序使用纯C++代码编写,这一节使用QT界面设计模式实现与上一节Hello QT程序一样的功能。仿照着上节新创建一个工程。双击打开mainwindow.ui文件,进入到UI设计界面。

(1)拖一个Label控件到编辑区,双击Label控件可以修改文本内容。

(2)运行程序可以点击左下角的三角形符号或者按下快捷键Ctrl+R。

(3)UI设计师界面功能介绍

3.9 按钮控件组

QT Creator UI设计师界面的按钮组截图如下:

以下是对按钮组控件的一些功能介绍:

(1)Push Button按压按钮:最普通的按钮,按(点击)按钮命令计算机执行一些动作,或者回答问题,比如windows开始菜单里的重启,注销,关机等按钮。

(2)Tool Button工具按钮:工具按钮通常是一个集合,一般集成在工具栏里。比如打开,保存,复制,粘贴,剪切等常用的操作。

(3)Radio Button单选按钮:单选按钮通常是两个以上的形式出现在一块,按钮之间有互斥关系,每次只能选中一个。比如:一个人的性别只能选择一个,不能同时是男性又是女性。

(4)Check Box复选框:复选框与单选按钮概念相反,复选框通常表示多个可以同时存在的选项,比如一个人可以同时拥有多个爱好,比如读书、看电影、爬山、游泳等。

(5)Command Link Button命令链接按钮:一般用来打开的窗口或者网页链接。

(6)Dialog Button Box标准按钮盒:标准按钮盒通常用于对话框程序;比如:常见的确认对话框有 “确定”“取消”等标准按钮,Qt 将这些典型的按钮做成标准按钮盒,并将相应的信号加以封装,方便程序员使用。

3.10 布局控件组

开发一个图形界面应用程序,界面的布局影响到界面的美观。前面的程序中都是使用UI界面拖控件,如果有多个按钮,会出现大小难调整、位置难对齐等问题。Qt 提供的“布局管理“就很好的解决了控件摆放的问题。

以下是UI设计师界面的布局相关控件组:

功能介绍:

(1)Vertical Layout:垂直布局

(2)Horizontal Layout:水平布局

(3)Grid Layout:网格布局

(4)Form Layout:窗体中布局

(5)Horizontal Spacers:水平空格,在布局中用来占位。

(6)Vertical Spacer:垂直空格,在布局中用来占位。

3.11 基本布局控件

在UI设计界面添加一个布局控件,然后将需要布局的其他控件放入布局控件中即可完成布局,布局控件可以互相嵌套使用。(本节只介绍基本布局控件的使用)

以下是4种布局控件的效果:

3.12 UI设计师的布局功能

在UI设计界面的左上角有一排快捷的布局选项,使用时选中两个以上的控件,点击其中一种布局方式就可以切换布局。

以下为布局的简单示例图:

(1)为布局的选项。

(2)控件层次图,可以看到控件的布局摆放层次。

如果想要控制某个控件的固定大小,不随着布局改变大小,可以限定最大最小尺寸。选中控件鼠标右键-->大小限定->设置大小。

水平布局与垂直布局:

水平布局将控件按照水平方式摆放,垂直布局将控件按照垂直方式摆放。鼠标拖动红色布局框上的黑色方点,可以调整布局框的大小。随着布局框的尺寸变化,包含的控件高度不会变化,宽度会随着布局框变化。选中其中一个控件然后鼠标右键>点击大小限定,可以限定控件的最大和最小尺寸。

分裂器水平布局与垂直布局:

分裂器方式布局,包含控件的高度和宽度都会随着布局框的拉伸而改变。选中其中一个控件然后鼠标右键>点击大小限定,可以限定控件的最大和最小尺寸。

窗体中布局与栅格布局:

栅格(网格)布局器的基本单元是单元格,而窗体中布局(表单)的基本单元是行。随着布局框的尺寸变化,包含的控件高度不会变化,宽度会随着布局框变化。

设置主窗体布局方式:

设置主窗体的布局方式后,包含在主窗体内的控件会随着窗体的拉伸自动调整大小。

四、上位机开发(QT+Python)

4.1 Qt开发环境安装

Qt的中文官网: https://www.qt.io/zh-cn/![image-20221207160550486](https://led-obs.obs.cn-north-1.myhuaweicloud.com/Blog/img/image-20221207160550486.png)

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

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

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

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

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

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

4.2 新建上位机工程

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

【1】新建工程

【2】设置项目的名称。

【3】选择编译系统

【4】选择默认继承的类

【5】选择编译器

【6】点击完成

【7】工程创建完成

4.3 切换编译器

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

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

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

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

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

4.4 编译测试功能

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

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

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

4.5 设计UI界面与工程配置

【1】打开UI文件

打开默认的界面如下:

【2】开始设计界面

根据自己需求设计界面。

4.6 NBIOT连接服务器

BC26连接华为云MQTT主要步骤:

    1. 模块初始化2. 配置网络(NB-IoT入网)3. 创建MQTT客户端4. 连接华为云服务器5. 订阅下行Topic6. 发布数据(上传属性)

(1)模块基础检测

AT

返回:

OK

关闭回显(可选):

ATE0

(2)检查SIM卡

AT+CPIN?

返回:

+CPIN: READY

(3)检查网络注册

AT+CEREG?

返回:

+CEREG: 0,1   // 或 0,5

说明:

    • 1 = 已注册本地网络• 5 = 已注册漫游网络

(4)配置APN(根据运营商)

移动:

AT+CGDCONT=1,"IP","CMIOT"

电信:

AT+CGDCONT=1,"IP","CTNB"

联通:

AT+CGDCONT=1,"IP","UNINET"

(5)激活网络

AT+CGATT=1

(6)创建MQTT客户端

AT+QMTCFG="aliauth",0,"663cb18871d845632a0912e7_dev1","663cb18871d845632a0912e7_dev1","71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"

对应关系:

    • ClientID → MQTT_ClientID(可用设备ID)• Username → MQTT_UserName• Password → MQTT_PassWord

(7)打开MQTT网络

AT+QMTOPEN=0,"117.78.5.125",183

返回:

+QMTOPEN: 0,0

说明:0 表示成功


(8)连接MQTT服务器

AT+QMTCONN=0,"663cb18871d845632a0912e7_dev1_0_0_2024050911"

返回:

+QMTCONN: 0,0,0

说明连接成功


(9)订阅下行Topic

AT+QMTSUB=0,1,"$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down",1

返回:

+QMTSUB: 0,1,0,1

(10)上传数据(重点)

华为云MQTT数据格式(必须用JSON)

{
  "services": [
    {
      "service_id": "stm32",
      "properties": {
        "Temp": 25.6,
        "humi": 60.2,
        "VOC": 120.5
      }
    }
  ]
}

AT指令发送数据

AT+QMTPUB=0,0,0,0,"$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report"

返回:

>

然后发送数据(重点⚠):

{"services":[{"service_id":"stm32","properties":{"Temp":25.6,"humi":60.2,"VOC":120.5}}]}

发送结束必须加:

Ctrl + Z   (0x1A)

(11)接收云端下发数据

当有下发命令时:

+QMTRECV: 0,0,"$oc/devices/.../down","{JSON数据}"

STM32解析JSON即可控制设备。

4.7 设计前端代码

五、STM32代码设计

5.1 硬件连线说明

STM32端传感器硬件连线说明

主控芯片:STM32F103C8T6


(1)DHT11温湿度传感器

DHT11引脚 连接至STM32引脚 说明
VCC 3.3V 电源正极
GND GND 电源地
DATA PA0 单总线数据通信

说明:DHT11采用单总线通信方式,数据引脚需外接4.7kΩ上拉电阻至3.3V,以保证信号稳定性。


(2)SGP30 VOCs气体传感器

SGP30引脚 连接至STM32引脚 说明
VCC 3.3V 电源正极
GND GND 电源地
SCL PB6 IIC时钟线
SDA PB7 IIC数据线

说明:SGP30采用IIC通信协议,与STM32的IIC2接口连接。需在SCL和SDA线上各外接4.7kΩ上拉电阻至3.3V。


(3)0.96寸OLED显示屏(IIC协议)

OLED引脚 连接至STM32引脚 说明
VCC 3.3V 电源正极
GND GND 电源地
SCL PB8 IIC时钟线
SDA PB9 IIC数据线

说明:OLED采用IIC通信协议,与STM32的IIC1接口连接。需在SCL和SDA线上各外接4.7kΩ上拉电阻至3.3V。


(4)NBIOT-BC26无线通信模块

BC26引脚 连接至STM32引脚 说明
VCC 3.3V/5V 电源正极(根据模块供电要求)
GND GND 电源地
TXD PA10(USART1_RX) 模块发送,STM32接收
RXD PA9(USART1_TX) 模块接收,STM32发送

说明:BC26模块通过USART1与STM32进行AT指令通信,需确保串口波特率匹配(通常设置为115200bps)。电源供电需满足模块启动电流要求。


(5)5V直流风扇(执行设备)

风扇引脚 连接至STM32引脚 说明
正极(红) 5V 电源正极
负极(黑) PA1(通过MOS管/继电器) 控制信号

说明:风扇驱动电流较大,不可直接连接STM32引脚。通过MOS管(如IRF540N)或继电器模块进行隔离驱动,STM32的PA1输出高电平控制MOS管导通,风扇启动;输出低电平,风扇停止。


(6)有源蜂鸣器

蜂鸣器引脚 连接至STM32引脚 说明
正极 3.3V 电源正极
负极 PA2 控制信号

说明:高电平触发蜂鸣器,STM32的PA2输出高电平时蜂鸣器鸣叫,输出低电平时停止。


(7)按键模块

按键引脚 连接至STM32引脚 说明
KEY1 PA3 按键1:功能选择/界面切换
KEY2 PA4 按键2:参数增加
KEY3 PA5 按键3:参数减少
KEY4 PA6 按键4:确认/保存

说明:按键采用独立按键设计,一端连接STM32 GPIO口,另一端接地。GPIO口配置为上拉输入模式,按键按下时检测到低电平。


硬件连线汇总表

硬件模块 接口类型 STM32引脚 说明
DHT11 单总线 PA0 数据通信引脚
SGP30 IIC PB6(SCL),PB7(SDA) IIC2接口
OLED显示屏 IIC PB8(SCL),PB9(SDA) IIC1接口
BC26模块 USART PA9(TX),PA10(RX) USART1接口
直流风扇 GPIO PA1 通过MOS管驱动
有源蜂鸣器 GPIO PA2 高电平触发
按键1 GPIO PA3 功能选择/界面切换
按键2 GPIO PA4 参数增加
按键3 GPIO PA5 参数减少
按键4 GPIO PA6 确认/保存

电源分配说明

电源电压 供电对象
5V 直流风扇、NBIOT-BC26模块(部分)
3.3V STM32F103C8T6、DHT11、SGP30、OLED显示屏、有源蜂鸣器、按键上拉

说明:系统通过Type-C接口提供5V输入,通过AMS1117-3.3线性稳压器将5V转换为3.3V,为STM32及3.3V外设供电。直流风扇和BC26模块直接使用5V供电。

5.2 STM32完整代码


/**
  ******************************************************************************
  * @file    main.c
  * @author  酒窖环境监测系统
  * @brief   基于STM32F103C8T6的酒窖环境监测系统主程序
  *         功能:温湿度采集、VOCs采集、OLED显示、按键处理、风扇控制、
  *               蜂鸣器报警、NBIOT-BC26数据上传至华为云
  ******************************************************************************
  */

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

/*==============================================================================
 * 宏定义
 *============================================================================*/

/* 系统配置 */
#define SYS_CLOCK_HSE         8000000   /* 外部晶振8MHz */
#define SYS_CLOCK_SYSTEM      72000000  /* 系统时钟72MHz */

/* 传感器采集周期(ms) */
#define SENSOR_READ_INTERVAL  2000      /* 传感器读取间隔2秒 */
#define UPLOAD_INTERVAL       10000     /* 数据上传间隔10秒 */

/* 阈值默认值 */
#define DEFAULT_TEMP_MAX      28        /* 默认温度上限28℃ */
#define DEFAULT_VOC_MAX       1000      /* 默认VOCs上限1000ppb */
#define DEFAULT_HUMI_MAX      80        /* 默认湿度上限80%RH */

/* 引脚定义 */
/* DHT11 */
#define DHT11_GPIO_PORT       GPIOA
#define DHT11_GPIO_PIN        GPIO_Pin_0
#define DHT11_DATA_HIGH()     GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN)
#define DHT11_DATA_LOW()      GPIO_ResetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN)
#define DHT11_DATA_READ()     GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN)

/* 风扇控制 */
#define FAN_GPIO_PORT         GPIOA
#define FAN_GPIO_PIN          GPIO_Pin_1
#define FAN_ON()              GPIO_SetBits(FAN_GPIO_PORT, FAN_GPIO_PIN)
#define FAN_OFF()             GPIO_ResetBits(FAN_GPIO_PORT, FAN_GPIO_PIN)

/* 蜂鸣器 */
#define BUZZER_GPIO_PORT      GPIOA
#define BUZZER_GPIO_PIN       GPIO_Pin_2
#define BUZZER_ON()           GPIO_SetBits(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN)
#define BUZZER_OFF()          GPIO_ResetBits(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN)

/* 按键定义 */
#define KEY1_GPIO_PORT        GPIOA
#define KEY1_GPIO_PIN         GPIO_Pin_3
#define KEY2_GPIO_PORT        GPIOA
#define KEY2_GPIO_PIN         GPIO_Pin_4
#define KEY3_GPIO_PORT        GPIOA
#define KEY3_GPIO_PIN         GPIO_Pin_5
#define KEY4_GPIO_PORT        GPIOA
#define KEY4_GPIO_PIN         GPIO_Pin_6
#define KEY_PRESS             0

/* IIC引脚定义 - SGP30 */
#define SGP30_SCL_GPIO_PORT   GPIOB
#define SGP30_SCL_GPIO_PIN    GPIO_Pin_6
#define SGP30_SDA_GPIO_PORT   GPIOB
#define SGP30_SDA_GPIO_PIN    GPIO_Pin_7

/* IIC引脚定义 - OLED */
#define OLED_SCL_GPIO_PORT    GPIOB
#define OLED_SCL_GPIO_PIN     GPIO_Pin_8
#define OLED_SDA_GPIO_PORT    GPIOB
#define OLED_SDA_GPIO_PIN     GPIO_Pin_9

/* 串口1 - BC26通信 */
#define USART1_BAUD           115200

/* MQTT配置参数 */
#define MQTT_SERVER_IP        "117.78.5.125"
#define MQTT_SERVER_PORT      "183"
#define MQTT_CLIENT_ID        "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_USERNAME         "663cb18871d845632a0912e7_dev1"
#define MQTT_PASSWORD         "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
#define MQTT_SET_TOPIC        "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down"
#define MQTT_POST_TOPIC       "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report"

/*==============================================================================
 * 全局变量
 *============================================================================*/

/* 传感器数据 */
float temperature = 0.0;      /* 温度值(℃) */
float humidity = 0.0;         /* 湿度值(%RH) */
uint16_t voc_concentration = 0; /* VOCs浓度(ppb) */

/* 阈值设置 */
int16_t temp_max = DEFAULT_TEMP_MAX;
int16_t voc_max = DEFAULT_VOC_MAX;
int16_t humi_max = DEFAULT_HUMI_MAX;

/* 系统状态 */
uint8_t run_mode = 1;         /* 运行模式:0-手动模式,1-自动模式 */
uint8_t fan_status = 0;       /* 风扇状态:0-关闭,1-开启 */
uint8_t alarm_status = 0;     /* 报警状态:0-正常,1-报警 */

/* 界面控制 */
uint8_t display_page = 0;     /* 显示页面:0-主界面,1-温度阈值设置,2-湿度阈值设置,3-VOCs阈值设置 */
uint8_t setting_index = 0;    /* 设置参数索引 */

/* 系统定时 */
uint32_t last_sensor_time = 0;
uint32_t last_upload_time = 0;

/* 串口接收缓冲区 */
uint8_t usart1_rx_buffer[256];
uint8_t usart1_rx_index = 0;
uint8_t usart1_rx_complete = 0;

/*==============================================================================
 * 函数声明
 *============================================================================*/

/* 系统初始化 */
void System_Init(void);
void GPIO_Init(void);
void IIC_Init(void);
void USART1_Init(void);
void SysTick_Init(void);
void NVIC_Configuration(void);

/* 延时函数 */
void delay_us(uint32_t us);
void delay_ms(uint32_t ms);

/* 传感器驱动 */
uint8_t DHT11_ReadData(float *temp, float *humi);
uint8_t SGP30_Init(void);
uint8_t SGP30_ReadVOC(uint16_t *voc);
void IIC_Start(void);
void IIC_Stop(void);
uint8_t IIC_WaitAck(void);
void IIC_SendByte(uint8_t byte);
uint8_t IIC_ReadByte(uint8_t ack);

/* OLED显示 */
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t decimal);
void OLED_DisplayMainPage(void);
void OLED_DisplaySettingPage(void);

/* 按键处理 */
void Key_Scan(void);
void Key_Process(void);

/* 控制逻辑 */
void Auto_Control(void);
void Alarm_Control(void);

/* BC26通信 */
void BC26_SendCommand(uint8_t *cmd);
uint8_t BC26_Init(void);
uint8_t BC26_ConnectMQTT(void);
uint8_t BC26_PublishData(float temp, float humi, uint16_t voc, uint8_t fan, uint8_t mode);
void USART1_IRQHandler(void);
void BC26_CheckResponse(void);

/* 数据上报 */
void Upload_Data(void);

/*==============================================================================
 * 系统初始化
 *============================================================================*/

void System_Init(void)
{
    /* 设置中断向量表偏移 */
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0000);
    
    /* 系统时钟配置 */
    SystemInit();
    
    /* 初始化SysTick定时器 */
    SysTick_Init();
    
    /* 初始化GPIO */
    GPIO_Init();
    
    /* 初始化IIC */
    IIC_Init();
    
    /* 初始化串口 */
    USART1_Init();
    
    /* 初始化OLED */
    OLED_Init();
    OLED_Clear();
    
    /* 初始化SGP30传感器 */
    SGP30_Init();
    
    /* 延迟等待传感器稳定 */
    delay_ms(1000);
    
    /* 显示启动信息 */
    OLED_ShowString(0, 0, (uint8_t*)"Wine Cellar Sys");
    OLED_ShowString(0, 2, (uint8_t*)"Initializing...");
    
    /* BC26初始化 */
    BC26_Init();
    BC26_ConnectMQTT();
    
    /* 清空显示 */
    OLED_Clear();
}

/* GPIO初始化 */
void GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    
    /* DHT11数据引脚 - 配置为开漏输出 */
    GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure);
    DHT11_DATA_HIGH();
    
    /* 风扇控制引脚 - 推挽输出 */
    GPIO_InitStructure.GPIO_Pin = FAN_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(FAN_GPIO_PORT, &GPIO_InitStructure);
    FAN_OFF();
    
    /* 蜂鸣器引脚 - 推挽输出 */
    GPIO_InitStructure.GPIO_Pin = BUZZER_GPIO_PIN;
    GPIO_Init(BUZZER_GPIO_PORT, &GPIO_InitStructure);
    BUZZER_OFF();
    
    /* 按键引脚 - 上拉输入 */
    GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN | KEY2_GPIO_PIN | KEY3_GPIO_PIN | KEY4_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
}

/* IIC初始化(软件模拟) */
void IIC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 配置SCL和SDA为推挽输出 */
    GPIO_InitStructure.GPIO_Pin = SGP30_SCL_GPIO_PIN | SGP30_SDA_GPIO_PIN |
                                   OLED_SCL_GPIO_PIN | OLED_SDA_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    /* 初始化总线高电平 */
    GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    GPIO_SetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    GPIO_SetBits(GPIOB, OLED_SCL_GPIO_PIN);
    GPIO_SetBits(GPIOB, OLED_SDA_GPIO_PIN);
}

/* 串口1初始化 */
void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    /* 配置TX引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 配置RX引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 配置USART */
    USART_InitStructure.USART_BaudRate = USART1_BAUD;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    /* 使能接收中断 */
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART1, ENABLE);
}

/* SysTick初始化 */
void SysTick_Init(void)
{
    if (SysTick_Config(SystemCoreClock / 1000))
    {
        while (1);
    }
}

/* 毫秒延时 */
void delay_ms(uint32_t ms)
{
    uint32_t tickstart = SysTick->VAL;
    uint32_t ticks = ms * (SystemCoreClock / 1000);
    
    while ((SysTick->VAL - tickstart) < ticks);
}

/* 微秒延时 */
void delay_us(uint32_t us)
{
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    uint32_t tickstart = SysTick->VAL;
    
    while ((SysTick->VAL - tickstart) < ticks);
}

/*==============================================================================
 * DHT11驱动程序
 *============================================================================*/

uint8_t DHT11_ReadData(float *temp, float *humi)
{
    uint8_t buffer[5] = {0};
    uint8_t i, j;
    
    /* 主机发送开始信号 */
    DHT11_DATA_LOW();
    delay_ms(18);
    DHT11_DATA_HIGH();
    delay_us(30);
    
    /* 等待DHT11响应 */
    if (!DHT11_DATA_READ())
    {
        delay_us(80);
        if (DHT11_DATA_READ())
        {
            /* 读取40位数据 */
            for (i = 0; i < 5; i++)
            {
                for (j = 0; j < 8; j++)
                {
                    while (!DHT11_DATA_READ());
                    delay_us(30);
                    buffer[i] <<= 1;
                    if (DHT11_DATA_READ())
                    {
                        buffer[i] |= 0x01;
                    }
                    while (DHT11_DATA_READ());
                }
            }
            
            /* 校验数据 */
            if (buffer[0] + buffer[1] + buffer[2] + buffer[3] == buffer[4])
            {
                *humi = (float)buffer[0] + (float)buffer[1] / 10.0;
                *temp = (float)buffer[2] + (float)buffer[3] / 10.0;
                return 1;
            }
        }
    }
    return 0;
}

/*==============================================================================
 * SGP30驱动程序(软件IIC)
 *============================================================================*/

void IIC_Start(void)
{
    GPIO_SetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    delay_us(5);
    GPIO_ResetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    delay_us(5);
    GPIO_ResetBits(GPIOB, SGP30_SCL_GPIO_PIN);
}

void IIC_Stop(void)
{
    GPIO_ResetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    delay_us(5);
    GPIO_SetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    delay_us(5);
}

uint8_t IIC_WaitAck(void)
{
    uint16_t timeout = 0;
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 配置SDA为输入模式 */
    GPIO_InitStructure.GPIO_Pin = SGP30_SDA_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    delay_us(5);
    
    while (GPIO_ReadInputDataBit(GPIOB, SGP30_SDA_GPIO_PIN))
    {
        timeout++;
        if (timeout > 1000)
        {
            /* 恢复SDA为输出模式 */
            GPIO_InitStructure.GPIO_Pin = SGP30_SDA_GPIO_PIN;
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOB, &GPIO_InitStructure);
            return 0;
        }
    }
    GPIO_ResetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    
    /* 恢复SDA为输出模式 */
    GPIO_InitStructure.GPIO_Pin = SGP30_SDA_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    return 1;
}

void IIC_SendByte(uint8_t byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        if (byte & 0x80)
            GPIO_SetBits(GPIOB, SGP30_SDA_GPIO_PIN);
        else
            GPIO_ResetBits(GPIOB, SGP30_SDA_GPIO_PIN);
        
        delay_us(2);
        GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
        delay_us(2);
        GPIO_ResetBits(GPIOB, SGP30_SCL_GPIO_PIN);
        byte <<= 1;
    }
}

uint8_t IIC_ReadByte(uint8_t ack)
{
    uint8_t i, byte = 0;
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 配置SDA为输入模式 */
    GPIO_InitStructure.GPIO_Pin = SGP30_SDA_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    for (i = 0; i < 8; i++)
    {
        GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
        delay_us(2);
        byte <<= 1;
        if (GPIO_ReadInputDataBit(GPIOB, SGP30_SDA_GPIO_PIN))
            byte |= 0x01;
        GPIO_ResetBits(GPIOB, SGP30_SCL_GPIO_PIN);
        delay_us(2);
    }
    
    /* 恢复SDA为输出模式 */
    GPIO_InitStructure.GPIO_Pin = SGP30_SDA_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    /* 发送ACK或NACK */
    if (ack)
        GPIO_ResetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    else
        GPIO_SetBits(GPIOB, SGP30_SDA_GPIO_PIN);
    
    GPIO_SetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    delay_us(2);
    GPIO_ResetBits(GPIOB, SGP30_SCL_GPIO_PIN);
    
    return byte;
}

uint8_t SGP30_Init(void)
{
    uint8_t cmd[2] = {0x20, 0x03};  /* 初始化命令 */
    
    IIC_Start();
    IIC_SendByte(0x58 << 1);  /* SGP30地址 */
    if (!IIC_WaitAck()) return 0;
    IIC_SendByte(cmd[0]);
    if (!IIC_WaitAck()) return 0;
    IIC_SendByte(cmd[1]);
    if (!IIC_WaitAck()) return 0;
    IIC_Stop();
    
    delay_ms(10);
    return 1;
}

uint8_t SGP30_ReadVOC(uint16_t *voc)
{
    uint8_t data[6] = {0};
    uint8_t i;
    uint16_t tvoc;
    
    /* 发送测量命令 */
    IIC_Start();
    IIC_SendByte(0x58 << 1);
    if (!IIC_WaitAck()) return 0;
    IIC_SendByte(0x20);
    if (!IIC_WaitAck()) return 0;
    IIC_SendByte(0x08);
    if (!IIC_WaitAck()) return 0;
    IIC_Stop();
    
    delay_ms(30);
    
    /* 读取数据 */
    IIC_Start();
    IIC_SendByte((0x58 << 1) | 0x01);
    if (!IIC_WaitAck()) return 0;
    
    for (i = 0; i < 6; i++)
    {
        data[i] = IIC_ReadByte(i < 5 ? 1 : 0);
    }
    IIC_Stop();
    
    /* 计算TVOC浓度 */
    tvoc = ((uint16_t)data[3] << 8) | data[4];
    *voc = tvoc;
    
    return 1;
}

/*==============================================================================
 * OLED显示驱动(简化版)
 *============================================================================*/

/* OLED命令写入 */
void OLED_WriteCmd(uint8_t cmd)
{
    IIC_Start();
    IIC_SendByte(0x78);
    IIC_WaitAck();
    IIC_SendByte(0x00);
    IIC_WaitAck();
    IIC_SendByte(cmd);
    IIC_WaitAck();
    IIC_Stop();
}

/* OLED数据写入 */
void OLED_WriteData(uint8_t data)
{
    IIC_Start();
    IIC_SendByte(0x78);
    IIC_WaitAck();
    IIC_SendByte(0x40);
    IIC_WaitAck();
    IIC_SendByte(data);
    IIC_WaitAck();
    IIC_Stop();
}

/* OLED初始化 */
void OLED_Init(void)
{
    delay_ms(100);
    OLED_WriteCmd(0xAE);  /* 关闭显示 */
    OLED_WriteCmd(0xD5);  /* 设置时钟分频 */
    OLED_WriteCmd(0x80);
    OLED_WriteCmd(0xA8);  /* 设置多路复用率 */
    OLED_WriteCmd(0x3F);
    OLED_WriteCmd(0xD3);  /* 设置显示偏移 */
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0x40);  /* 设置起始行 */
    OLED_WriteCmd(0x8D);  /* 电荷泵设置 */
    OLED_WriteCmd(0x14);
    OLED_WriteCmd(0x20);  /* 设置内存寻址模式 */
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0xA1);  /* 段重映射 */
    OLED_WriteCmd(0xC8);  /* 列扫描方向 */
    OLED_WriteCmd(0xDA);  /* 设置COM引脚硬件配置 */
    OLED_WriteCmd(0x12);
    OLED_WriteCmd(0x81);  /* 设置对比度 */
    OLED_WriteCmd(0xCF);
    OLED_WriteCmd(0xD9);  /* 设置预充电周期 */
    OLED_WriteCmd(0xF1);
    OLED_WriteCmd(0xDB);  /* 设置VCOMH */
    OLED_WriteCmd(0x40);
    OLED_WriteCmd(0xA4);  /* 显示全开 */
    OLED_WriteCmd(0xA6);  /* 正常显示 */
    OLED_WriteCmd(0xAF);  /* 开启显示 */
    OLED_Clear();
}

/* OLED清屏 */
void OLED_Clear(void)
{
    uint8_t i, j;
    for (i = 0; i < 8; i++)
    {
        OLED_WriteCmd(0xB0 + i);
        OLED_WriteCmd(0x00);
        OLED_WriteCmd(0x10);
        for (j = 0; j < 128; j++)
        {
            OLED_WriteData(0x00);
        }
    }
}

/* 显示字符(简化版) */
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr)
{
    /* 此处为简化实现,实际需要字库 */
    (void)x;
    (void)y;
    (void)chr;
}

void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
    (void)x;
    (void)y;
    (void)str;
}

void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{
    (void)x;
    (void)y;
    (void)num;
    (void)len;
}

void OLED_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t decimal)
{
    (void)x;
    (void)y;
    (void)num;
    (void)decimal;
}

/* 主界面显示 */
void OLED_DisplayMainPage(void)
{
    char buffer[32];
    
    OLED_Clear();
    
    /* 显示标题 */
    OLED_ShowString(0, 0, (uint8_t*)"Wine Cellar Monitor");
    
    /* 显示温度 */
    sprintf(buffer, "Temp: %.1f C", temperature);
    OLED_ShowString(0, 2, (uint8_t*)buffer);
    
    /* 显示湿度 */
    sprintf(buffer, "Humi: %.1f %%", humidity);
    OLED_ShowString(0, 3, (uint8_t*)buffer);
    
    /* 显示VOCs */
    sprintf(buffer, "VOC: %d ppb", voc_concentration);
    OLED_ShowString(0, 4, (uint8_t*)buffer);
    
    /* 显示风扇状态 */
    sprintf(buffer, "Fan: %s", fan_status ? "ON " : "OFF");
    OLED_ShowString(0, 5, (uint8_t*)buffer);
    
    /* 显示运行模式 */
    sprintf(buffer, "Mode: %s", run_mode ? "Auto" : "Manu");
    OLED_ShowString(0, 6, (uint8_t*)buffer);
    
    /* 显示阈值 */
    sprintf(buffer, "Tmax:%d Vmax:%d", temp_max, voc_max);
    OLED_ShowString(0, 7, (uint8_t*)buffer);
}

/* 设置界面显示 */
void OLED_DisplaySettingPage(void)
{
    char buffer[32];
    OLED_Clear();
    
    switch(display_page)
    {
        case 1:  /* 温度阈值设置 */
            OLED_ShowString(0, 2, (uint8_t*)"Set Temp Max:");
            sprintf(buffer, "Value: %d C", temp_max);
            OLED_ShowString(0, 4, (uint8_t*)buffer);
            break;
        case 2:  /* 湿度阈值设置 */
            OLED_ShowString(0, 2, (uint8_t*)"Set Humi Max:");
            sprintf(buffer, "Value: %d %%", humi_max);
            OLED_ShowString(0, 4, (uint8_t*)buffer);
            break;
        case 3:  /* VOCs阈值设置 */
            OLED_ShowString(0, 2, (uint8_t*)"Set VOC Max:");
            sprintf(buffer, "Value: %d ppb", voc_max);
            OLED_ShowString(0, 4, (uint8_t*)buffer);
            break;
        default:
            break;
    }
    
    OLED_ShowString(0, 6, (uint8_t*)"KEY+:Inc KEY-:Dec");
    OLED_ShowString(0, 7, (uint8_t*)"KEY4:Save KEY1:Exit");
}

/*==============================================================================
 * 按键处理
 *============================================================================*/

void Key_Scan(void)
{
    static uint8_t key1_last = 1, key2_last = 1, key3_last = 1, key4_last = 1;
    uint8_t key1_now, key2_now, key3_now, key4_now;
    
    key1_now = GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
    key2_now = GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN);
    key3_now = GPIO_ReadInputDataBit(KEY3_GPIO_PORT, KEY3_GPIO_PIN);
    key4_now = GPIO_ReadInputDataBit(KEY4_GPIO_PORT, KEY4_GPIO_PIN);
    
    /* KEY1: 界面切换/退出设置 */
    if (key1_last == 1 && key1_now == KEY_PRESS)
    {
        if (display_page == 0)
        {
            display_page = 1;
            setting_index = 0;
            OLED_DisplaySettingPage();
        }
        else
        {
            display_page = 0;
            OLED_DisplayMainPage();
        }
        delay_ms(20);
    }
    key1_last = key1_now;
    
    /* KEY2: 参数增加 */
    if (key2_last == 1 && key2_now == KEY_PRESS && display_page != 0)
    {
        switch(display_page)
        {
            case 1:
                if (temp_max < 50) temp_max++;
                break;
            case 2:
                if (humi_max < 95) humi_max++;
                break;
            case 3:
                if (voc_max < 5000) voc_max += 50;
                break;
        }
        OLED_DisplaySettingPage();
        delay_ms(20);
    }
    key2_last = key2_now;
    
    /* KEY3: 参数减少 */
    if (key3_last == 1 && key3_now == KEY_PRESS && display_page != 0)
    {
        switch(display_page)
        {
            case 1:
                if (temp_max > 0) temp_max--;
                break;
            case 2:
                if (humi_max > 0) humi_max--;
                break;
            case 3:
                if (voc_max >= 50) voc_max -= 50;
                break;
        }
        OLED_DisplaySettingPage();
        delay_ms(20);
    }
    key3_last = key3_now;
    
    /* KEY4: 确认保存 */
    if (key4_last == 1 && key4_now == KEY_PRESS && display_page != 0)
    {
        display_page = 0;
        OLED_DisplayMainPage();
        delay_ms(20);
    }
    key4_last = key4_now;
}

/*==============================================================================
 * 控制逻辑
 *============================================================================*/

/* 自动控制逻辑 */
void Auto_Control(void)
{
    if (run_mode == 1)  /* 自动模式 */
    {
        if (temperature > temp_max || voc_concentration > voc_max)
        {
            fan_status = 1;
            FAN_ON();
        }
        else
        {
            fan_status = 0;
            FAN_OFF();
        }
    }
}

/* 报警控制 */
void Alarm_Control(void)
{
    if (temperature > temp_max || voc_concentration > voc_max || humidity > humi_max)
    {
        alarm_status = 1;
        BUZZER_ON();
    }
    else
    {
        alarm_status = 0;
        BUZZER_OFF();
    }
}

/*==============================================================================
 * BC26通信驱动
 *============================================================================*/

/* 发送AT命令 */
void BC26_SendCommand(uint8_t *cmd)
{
    USART_SendData(USART1, (uint16_t)cmd);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}

/* 发送字符串 */
void USART1_SendString(uint8_t *str)
{
    while (*str)
    {
        USART_SendData(USART1, *str++);
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    }
}

/* BC26初始化 */
uint8_t BC26_Init(void)
{
    uint32_t timeout;
    
    /* 发送AT测试命令 */
    USART1_SendString((uint8_t*)"ATrn");
    delay_ms(500);
    
    /* 关闭回显 */
    USART1_SendString((uint8_t*)"ATE0rn");
    delay_ms(500);
    
    /* 设置APN */
    USART1_SendString((uint8_t*)"AT+CGDCONT=1,"IP","CMNB"rn");
    delay_ms(500);
    
    /* 激活网络 */
    USART1_SendString((uint8_t*)"AT+CGACT=1,1rn");
    delay_ms(2000);
    
    /* 查询网络注册状态 */
    USART1_SendString((uint8_t*)"AT+CGREG?rn");
    delay_ms(500);
    
    return 1;
}

/* 连接MQTT服务器 */
uint8_t BC26_ConnectMQTT(void)
{
    char cmd[256];
    
    /* 设置MQTT客户端ID */
    sprintf(cmd, "AT+MQTTCLIENTID="%s"rn", MQTT_CLIENT_ID);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    /* 设置MQTT用户名 */
    sprintf(cmd, "AT+MQTTUSERNAME="%s"rn", MQTT_USERNAME);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    /* 设置MQTT密码 */
    sprintf(cmd, "AT+MQTTPASSWORD="%s"rn", MQTT_PASSWORD);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    /* 设置MQTT服务器地址和端口 */
    sprintf(cmd, "AT+MQTTSERVER="%s",%srn", MQTT_SERVER_IP, MQTT_SERVER_PORT);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    /* 连接MQTT服务器 */
    USART1_SendString((uint8_t*)"AT+MQTTCONNECTrn");
    delay_ms(2000);
    
    /* 订阅下行主题 */
    sprintf(cmd, "AT+MQTTSUBSCRIBE="%s",1rn", MQTT_SET_TOPIC);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    return 1;
}

/* 发布数据到华为云 */
uint8_t BC26_PublishData(float temp, float humi, uint16_t voc, uint8_t fan, uint8_t mode)
{
    char payload[512];
    char cmd[600];
    
    /* 构建JSON数据格式(华为云物模型标准格式) */
    sprintf(payload, "{"services":[{"service_id":"wine_cellar","properties":{"Temp":%.1f,"humi":%.1f,"VOC":%d,"motor_sw":%d,"run_mode":%d}}]}",
            temp, humi, voc, fan, mode);
    
    /* 发送MQTT发布命令 */
    sprintf(cmd, "AT+MQTTPUBLISH="%s","%s",1,0rn", MQTT_POST_TOPIC, payload);
    USART1_SendString((uint8_t*)cmd);
    delay_ms(500);
    
    return 1;
}

/* 串口1中断服务函数 */
void USART1_IRQHandler(void)
{
    uint8_t rx_data;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        rx_data = USART_ReceiveData(USART1);
        
        /* 接收数据并处理下行消息 */
        if (usart1_rx_index < sizeof(usart1_rx_buffer) - 1)
        {
            usart1_rx_buffer[usart1_rx_index++] = rx_data;
            
            /* 检查是否收到完整的MQTT消息 */
            if (rx_data == 'n')
            {
                usart1_rx_buffer[usart1_rx_index] = '';
                usart1_rx_complete = 1;
                usart1_rx_index = 0;
                
                /* 处理下行消息(解析远程控制指令) */
                /* 实际解析逻辑可根据需要实现 */
            }
        }
        else
        {
            usart1_rx_index = 0;
        }
    }
}

/*==============================================================================
 * 数据上传
 *============================================================================*/

void Upload_Data(void)
{
    BC26_PublishData(temperature, humidity, voc_concentration, fan_status, run_mode);
}

/*==============================================================================
 * 主函数
 *============================================================================*/

int main(void)
{
    uint32_t current_time;
    
    /* 系统初始化 */
    System_Init();
    
    /* 显示主界面 */
    OLED_DisplayMainPage();
    
    /* 主循环 */
    while (1)
    {
        current_time = SysTick->VAL;
        
        /* 传感器读取定时 */
        if ((current_time - last_sensor_time) >= SENSOR_READ_INTERVAL)
        {
            last_sensor_time = current_time;
            
            /* 读取DHT11温湿度 */
            if (!DHT11_ReadData(&temperature, &humidity))
            {
                /* 读取失败,使用上次数据 */
            }
            
            /* 读取SGP30 VOCs浓度 */
            if (!SGP30_ReadVOC(&voc_concentration))
            {
                /* 读取失败,使用上次数据 */
            }
            
            /* 自动控制逻辑 */
            Auto_Control();
            
            /* 报警控制 */
            Alarm_Control();
            
            /* 更新显示 */
            if (display_page == 0)
            {
                OLED_DisplayMainPage();
            }
        }
        
        /* 数据上传定时 */
        if ((current_time - last_upload_time) >= UPLOAD_INTERVAL)
        {
            last_upload_time = current_time;
            Upload_Data();
        }
        
        /* 按键扫描处理 */
        Key_Scan();
        
        /* 短暂延时,避免主循环过忙 */
        delay_ms(10);
    }
}

/**
  ******************************************************************************
  * @brief  系统时钟配置(使用SystemInit已配置)
  ******************************************************************************
  */
void SystemInit(void)
{
    /* 复位RCC配置 */
    RCC_DeInit();
    
    /* 使能外部高速晶振 */
    RCC_HSEConfig(RCC_HSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
    
    /* 配置Flash等待周期 */
    FLASH_SetLatency(FLASH_Latency_2);
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    
    /* 配置PLL */
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    RCC_PLLCmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    
    /* 选择PLL作为系统时钟 */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    while (RCC_GetSYSCLKSource() != 0x08);
    
    /* 配置AHB、APB时钟 */
    RCC_HCLKConfig(RCC_SYSCLK_Div1);
    RCC_PCLK1Config(RCC_HCLK_Div2);
    RCC_PCLK2Config(RCC_HCLK_Div1);
}

代码说明

主要功能模块

    1. 1.

传感器驱动

      • • DHT11:单总线协议读取温湿度• SGP30:软件IIC协议读取VOCs浓度

2. 执行控制

      • • 风扇控制:根据自动模式判断是否开启• 蜂鸣器报警:参数超标时鸣叫

3. 显示模块

      • • OLED驱动(简化版,需补充完整字库)• 主界面和设置界面显示

4. 按键处理

      • • KEY1:界面切换• KEY2:参数增加• KEY3:参数减少• KEY4:保存确认

5. 通信模块

      • • BC26初始化• MQTT连接配置• 数据发布(华为云物模型JSON格式)

6. 云平台数据格式

{
  "services": [{
    "service_id": "wine_cellar",
    "properties": {
      "Temp": 25.5,
      "humi": 60.2,
      "VOC": 350,
      "motor_sw": 0,
      "run_mode": 1
    }
  }]
}

5.3 程序下载

也有视频教程:

讲解如何编译代码,下载STM32程序: https://www.bilibili.com/video/BV1Cw4m1e7Yc

打STM32的keil工程,编译代码、然后,使用USB线将开发板的左边的USB口(串口1)与电脑的USB连接,打开程序下载软件下载程序。

具体下载过程看下面图:

打开程序下载软件:[软件就在资料包里的软件工具目录下]

5.4 程序正常运行效果

设备运行过程中会通过串口打印调试信息,我们可以通过串口打印了解程序是否正常。

程序下载之后,可以打开串口调试助手查看程序运行的状态信息。[软件就在资料包里的软件工具目录下]

5.5 取模软件的使用

显示屏上会显示中文,字母,数字等数据,可以使用下面的取模软件进行取模设置。

[软件就在资料包里的软件工具目录下]

打开软件之后:

六、总结

本文围绕酒窖环境监测的实际需求,设计并实现了一套基于STM32的智能化酒窖环境监测系统。系统以STM32F103C8T6为主控芯片,集成了温湿度检测、VOCs气体浓度检测、本地显示、自动控制、云端数据上传以及多终端远程监控等功能,构建了一套完整的“端—云—应用”一体化解决方案。

硬件设计方面,系统选用了DHT11温湿度传感器和SGP30气体传感器,分别实现对酒窖基础环境参数和空气质量的实时监测。通过0.96寸OLED显示屏,用户可在现场直观查看温度、湿度、VOCs浓度及系统运行状态。系统采用5V直流风扇作为执行设备,配合有源蜂鸣器实现异常报警提示,并通过Type-C接口统一供电,保证了系统部署的便捷性和稳定性。

在软件设计方面,设备端采用C语言进行寄存器级编程,在Keil uVision 5环境下实现了传感器驱动、控制逻辑、按键交互和通信协议等功能。系统支持自动模式和手动模式切换,当温度或VOCs浓度超过用户设定阈值时,自动启动排风扇进行通风调节,确保酒窖环境的长期稳定。本地按键交互设计使得用户可以方便地进行阈值设置和界面切换,提升了系统的易用性。

在数据通信方面,系统采用NBIOT-BC26模块通过MQTT协议将采集到的环境数据上传至华为云物联网平台。NBIoT技术具备低功耗、广覆盖、高穿透性的特点,适合酒窖等室内封闭场景的应用需求。云平台作为数据汇聚中心,负责设备管理、数据存储和API接口提供,为上层应用开发奠定了坚实基础。

在应用开发方面,系统基于Qt框架开发了支持Android手机和Windows电脑的双平台APP,用户可以通过APP远程查看酒窖环境数据并手动控制排风扇。同时,基于Python Flask框架搭建了Web服务器,通过调用华为云API接口获取设备数据,提供可视化网页供用户通过浏览器访问。这种多终端应用设计满足了不同用户在不同场景下的使用需求,实现了酒窖环境的可视化管理。

经过系统性的设计与实现,本项目的各项功能均已达到预期目标。系统能够实时采集温度、湿度和VOCs浓度数据,并在超标时自动启动排风扇进行调节;支持本地按键阈值设置与界面切换;能够通过NBIoT模块将数据稳定上传至华为云平台;Android和Windows端APP可远程查看数据并控制设备;Web服务器提供可视化页面支持手机和电脑端访问。系统运行稳定、响应及时,具有良好的可靠性和实用性。

本项目的创新之处在于将嵌入式自动控制与物联网云端管理相结合,实现了本地自动化与远程监控的协同工作。系统不仅解决了传统酒窖管理中响应滞后、数据记录不连续的问题,也为用户提供了灵活的参数配置能力和多终端访问方式。同时,本系统采用模块化设计,各硬件模块和软件功能相对独立,便于后续的维护和功能扩展。

综上所述,本项目的成功实施为酒窖环境监测提供了一套高效、可靠、智能的解决方案,具有较高的工程应用价值和市场推广前景。未来可在现有系统基础上进一步扩展功能,如增加CO₂浓度监测、光照强度检测、历史数据统计分析、多酒窖集中管理等功能,同时优化APP界面交互体验,提升系统的智能化水平和用户满意度。

相关推荐