基于STM32设计的水资源监测系统

一、前言

1.1 项目开发背景

水资源作为人类赖以生存和发展的基础性自然资源,其质量直接关系到生态环境的可持续发展和人民群众的生命健康。随着工业化与城镇化的快速推进,水体污染问题日益严重,水资源的保护与科学管理已成为全球关注的重要议题。为了实现对水环境的高效监测与管理,亟需构建一套集成化、智能化的水资源环境监测系统,实现水质数据的实时采集、分析、传输与展示,为环境治理提供数据支撑与决策依据。

传统的水质监测方法多以人工采样与实验室分析为主,周期长、效率低,难以及时反映动态变化的水质状况,无法满足当前水环境实时监控的需求。借助嵌入式技术、物联网通信技术及传感器技术的发展,构建基于STM32嵌入式平台的智能水环境监测系统成为一种可行而有效的方案。该系统通过多种水质传感器实现对水温、pH值、溶解氧、电导率、水位等参数的连续自动监测,并通过无线通信方式将数据实时上传至物联网平台,实现远程监控与报警。

本项目基于STM32F103C8T6单片机作为主控核心,结合DS18B20、pH传感器、溶解氧传感器、TDS电导率传感器、水位传感器等多种模块构建多参数水质检测系统,通过IIC接口实现本地OLED显示,结合Air780E 4G通信模块实现远程数据上传。项目采用MQTT协议接入华为云物联网平台,同时配套开发基于Qt(C++)的Android手机APP和Windows上位机,用于数据可视化展示和历史数据分析,提升系统的可用性与智能化水平。

系统还集成了高电平触发蜂鸣器,在监测数据出现异常时可实现现场声光报警,有效提升应急响应能力。该系统具有部署灵活、运行稳定、实时性强等优点,广泛适用于河流、湖泊、水库、水产养殖、污水处理等多种场景,对于推动水环境保护信息化、智能化具有重要现实意义和应用价值。

1.2 设计实现的功能

(1)水温检测功能:通过DS18B20防水温度传感器对水体温度进行实时监测,并将数据传送至主控芯片处理。

(2)pH值检测功能:采用模拟量输出的pH传感器对水体酸碱度进行检测,经ADC采集并通过算法转换为实际pH值。

(3)溶解氧检测功能:采用485接口输出的溶解氧传感器,通过485转串口模块与STM32通信,实时获取水中溶解氧浓度。

(4)水位检测功能:使用电阻式模拟量输出的水位传感器,实现水体液位的实时监测,并通过ADC接口采集。

(5)TDS电导率检测功能:通过TDS传感器采集水体的电导率信息,反映水中溶解固体物的含量。

(6)OLED本地显示功能:通过0.96寸IIC接口OLED屏,实时显示采集的水温、pH值、溶解氧、水位、电导率等关键参数,便于现场查看。

(7)数据上云功能:利用Air780E 4G模块,通过MQTT协议将采集的所有水质参数上传至华为云IoT物联网平台,实现远程数据可视化管理。

(8)远程APP与上位机显示功能:基于Qt(C++)开发Android手机APP与Windows上位机,接收云端数据并以图表方式进行可视化展示,支持实时查看与历史数据查询。

(9)历史数据记录与折线图查看功能:Android APP端支持每日历史数据的存储和查询功能,用户可通过折线图形式查看各项水质指标的变化趋势。

(10)异常报警功能:当系统监测到水质参数异常(如超出设定阈值)时,触发高电平蜂鸣器进行现场声响报警,提示相关人员及时处理。

1.3 项目硬件模块组成

(1)STM32F103C8T6主控模块:作为系统的核心控制单元,负责采集传感器数据、数据处理、本地显示和数据上传等任务。

(2)DS18B20水温传感器模块:用于实时采集水体温度数据,具有防水封装,通信方式为单总线接口。

(3)pH值检测传感器模块:输出模拟电压信号,通过STM32的ADC采样接口读取,结合公式算法换算出实际pH值。

(4)溶解氧传感器模块:采用RS485接口输出,通过485转TTL串口模块连接STM32,实现对水体溶解氧浓度的读取。

(5)水位检测模块:使用电阻式模拟量输出的水位传感器,通过STM32的ADC接口获取水位高度信息。

(6)TDS电导率传感器模块:模拟量输出,检测水中溶解固体的电导率值,通过ADC采集处理。

(7)OLED显示模块:采用0.96寸IIC通信接口,用于实时显示采集到的水温、pH值、溶解氧、水位、电导率等数据。

(8)Air780E 4G通信模块:负责将处理后的水质数据通过MQTT协议上传至华为云IoT物联网平台,实现远程数据通信。

(9)蜂鸣器模块:采用高电平触发方式,在检测数据异常时发出声响警报,用于本地故障提示。

(10)485转串口通信模块:用于将溶解氧传感器的RS485信号转换为STM32可识别的TTL串口信号,实现串口数据通信。

1.4 设计思路

本项目设计一套基于STM32的水资源环境监测系统,实现多项水质参数的实时监测、本地显示、远程上传和异常报警等功能。系统整体设计思路遵循"模块化、集成化、智能化"的原则,围绕数据采集、数据处理、数据传输与数据展示四个核心环节进行系统开发。

在数据采集部分,系统选用多种水质传感器模块,分别用于采集水温、pH值、溶解氧、水位和电导率等核心水质指标。其中,温度传感器DS18B20通过单总线通信方式获取数据;pH值和TDS电导率采用模拟量输出方式,STM32通过ADC模块进行电压采样并结合算法转换为实际值;水位传感器同样输出模拟信号,方便读取液位变化;溶解氧传感器则通过RS485通信输出,经过485转串口模块后接入STM32的串口读取。通过合理搭配通信协议和接口资源,实现了多参数、多通道的水质数据采集。

在数据处理与显示方面,STM32F103C8T6主控芯片作为系统核心,负责各项传感器数据的采集调度与数值处理。处理后的数据通过IIC接口发送至0.96寸OLED显示屏,便于现场查看当前水质状态。系统设计时充分考虑资源优化,确保多个模块协同工作,避免通信冲突和资源浪费。

在数据传输部分,系统集成了Air780E 4G模块,实现远程无线通信。选用轻量级的MQTT协议,将处理后的数据通过4G网络上传至华为云物联网平台,确保数据上传的稳定性和实时性。MQTT协议具有发布/订阅机制,能够降低网络带宽消耗,提高通信效率,特别适合物联网设备的低功耗、低带宽应用场景。

为实现远程数据展示与分析,系统配套开发Android手机APP与Windows上位机软件,采用Qt(C++)设计,支持从华为云物联网平台接收数据并进行可视化展示。APP端还支持历史数据存储与查看功能,用户可按日期查询历史记录,并以折线图形式分析水质变化趋势。通过跨平台设计,实现了移动端与PC端的无缝对接。

系统具备异常报警功能,当监测到某项参数超过预设阈值时,STM32将自动驱动蜂鸣器报警,实现现场预警,增强系统的安全性和响应能力。整体系统硬件采用模块化结构设计,便于后期扩展与维护,同时提升了项目的稳定性和实用性。

1.5 系统功能总结

功能模块 实现内容 硬件支持 通信方式
水温检测 实时监测水体温度 DS18B20温度传感器 单总线
pH值检测 检测水体酸碱度,转换为实际pH值显示 模拟pH传感器 模拟量 → ADC
溶解氧检测 检测水中溶解氧浓度 溶解氧传感器 + 485转串口模块 RS485 → TTL串口
水位检测 实时监测水位高度 电阻式水位传感器 模拟量 → ADC
TDS电导率检测 检测水体中溶解固体含量的电导率 TDS电导率传感器 模拟量 → ADC
本地显示 实时显示各项水质数据(温度、pH、溶解氧、水位、电导率) 0.96寸OLED显示屏(IIC接口) IIC通信
数据上传 将采集数据上传至华为云IoT平台 Air780E 4G模块 MQTT协议(4G网络)
远程数据显示 在Android APP和Windows上位机端查看实时及历史数据,支持折线图显示 Qt开发APP与上位机 MQTT订阅(云端同步)
历史数据管理 APP端支持每日数据存储与查询,图形化展示水质变化 Android手机APP 本地+云端数据同步
异常报警 当数据异常超阈值,蜂鸣器报警提醒 高电平触发蜂鸣器 IO控制

1.6 开发工具的选择

【1】设备端开发

STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。

开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。

【2】上位机开发

上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。

1.7 数据交互字段

在将本项目采集的数据上传至 华为云 IoT 物联网平台 时,需要为设备模型(即产品模型)定义一组字段属性(属性服务),用于描述设备采集到的各种数据。这些属性应能完整表达你系统的功能。

在华为云 IoT 平台定义以下字段属性:

字段名称(标识符) 数据类型 单位 描述 示例值
waterTemp float 水温 22.5
phValue float PH值 7.21
dissolvedOxygen float mg/L 溶解氧浓度 6.85
waterLevel float cm 当前水位高度 13.2
tdsValue float ppm TDS电导率(溶解性总固体) 580
uploadTime string 时间戳 数据上传时间 2025-06-05T12:30:00
alarmStatus int 异常报警状态(0正常,1异常) 0

说明:

  • 数据类型需与华为云平台支持的类型一致,如 intfloatstringboolean
  • alarmStatus 可用于报警提示功能,如当PH超标、TDS过高等时上传 1
  • uploadTime 可以作为历史数据存储和查询依据。

MQTT 上传格式(JSON 结构):

json 复制代码
{
  "waterTemp": 22.5,
  "phValue": 7.21,
  "dissolvedOxygen": 6.85,
  "waterLevel": 13.2,
  "tdsValue": 580,
  "alarmStatus": 0,
  "uploadTime": "2025-06-05T12:30:00"
}

二、硬件介绍

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

2.1 STM32F102C8T6

STM32F103C8T6 是 STMicroelectronics 公司推出的一款基于 ARM Cortex-M3 内核的 32 位高性能低功耗微控制器,属于 STM32F1 系列中的主流型产品。该芯片具有较高的性价比和稳定的性能,被广泛应用于嵌入式控制系统、物联网设备、传感器采集、工业自动化等领域。

STM32F103C8T6 运行主频为 72MHz,具有 64KB 的 Flash 程序存储器和 20KB 的 SRAM,满足中小型嵌入式项目的代码和数据存储需求。它集成丰富的外设接口,包括多达 37 个通用 I/O 端口、3 个通用定时器、1 个高级定时器、多个串口(USART)、SPI 接口、I²C 接口以及 12 位 ADC 模块(多达 10 个通道),能够支持多种模拟和数字信号输入输出。

该芯片封装为 LQFP-48(48 引脚封装),方便用户在 PCB 设计和元件焊接过程中操作。它支持 2.0V~3.6V 的供电电压,具有较强的抗干扰能力和低功耗特性,适合在复杂或要求较高的工作环境中运行。

在开发方面,STM32F103C8T6 支持多种开发工具,如 Keil、IAR、STM32CubeIDE 等,开发者既可以使用标准外设库(SPL)或硬件抽象库(HAL)开发,也可以直接基于寄存器编程方式进行底层控制,便于系统的精细调试与资源优化。芯片支持通过 ISP 方式(串口)下载程序,用户可以方便地进行程序更新和调试。

STM32F103C8T6 是一款性能稳定、资源丰富、接口齐全的通用型微控制器,非常适合应用在多传感器数据采集、通信控制和实时处理等嵌入式系统设计中,如本项目中的水资源环境监测系统。

2.2 Air780e

Air780E 是移远通信(Quectel)推出的一款高性能、低功耗、功能丰富的 4G 全网通通信模块,适用于物联网领域中对数据传输速率、可靠性和远程接入能力有较高要求的应用场景。该模块支持 LTE CAT1 通信标准,可实现较快的数据传输速度与稳定的网络连接能力,广泛应用于智能终端、远程监控、工业物联、智慧农业等系统中。

Air780E 支持中国三大运营商(移动、联通、电信)的 4G 网络,具备良好的网络兼容性和通信覆盖能力。模块内部集成了 TCP/IP 协议栈,并支持常用的通信协议如 MQTT、HTTP、FTP、UDP、TCP 等,用户可以通过串口 AT 指令方式快速配置模块,实现云平台的数据上传与下发控制。对于本项目,Air780E 通过 MQTT 协议将采集的水质数据上传到华为云物联网平台,起到了关键的数据传输桥梁作用。

硬件接口方面,Air780E 模块提供标准的 UART 接口,方便与主控芯片(如 STM32)通信,支持常见的波特率配置,并且具有多路 GPIO、SIM 卡接口、天线接口、电源管理等功能。模块支持主控通过串口发送 AT 指令来进行拨号上网、参数设置、数据发送、断线重连等操作,操作、上手快速,能够显著降低开发难度。

在功耗控制方面,Air780E 具备多种省电模式,如飞行模式、睡眠模式和掉电模式,特别适合电池供电类的嵌入式应用场景。模块体积小巧,集成度高,封装方式适合贴片安装,可方便集成在各种尺寸受限的设备中。

Air780E 是一款集高可靠性、易用性和网络兼容性于一体的 4G 通信模块,特别适合用于需要远程数据传输、接入云平台的嵌入式系统。在本水资源环境监测项目中,它有效实现了传感器数据从本地终端到云端的远距离传输,是系统远程监控功能实现的关键组成部分。

2.3 TDS传感器

TDS电导率检测传感器是一种用于检测水中溶解性总固体(Total Dissolved Solids)含量的传感器,广泛应用于水质检测、饮用水处理、水产养殖、农业灌溉等场景。TDS 的单位通常为 ppm(每百万分之一),代表每升水中含有多少毫克的可溶性物质,如矿物质、盐类、金属离子等。

该传感器的工作原理基于电导率的测量,即通过检测水体对电流的导通能力,间接判断水中溶解固体的浓度。水中含有的离子越多,导电能力越强,对应的TDS值也越高。传感器内部通常包含两个电极,当电极浸入水中后,电解质会使电流形成通路,控制模块通过测量电导值并结合算法,将其转换为实际的TDS数值。

TDS传感器通常输出为模拟电压信号,便于与单片机的ADC模块(如STM32的ADC)进行对接。传感器输出的电压与TDS值成正比,经过采样与算法换算后即可获得相应的ppm浓度值。在应用中,为了获得更准确的测量结果,常配合温度传感器(如DS18B20)进行温度补偿,以消除不同水温对电导率造成的影响。

TDS传感器一般采用防水探头,外壳材质耐腐蚀,适合长期浸入水中使用。控制模块则具备电压调节、滤波放大、信号校准等功能,提高输出信号的稳定性和抗干扰能力。

在本水资源环境监测系统中,TDS电导率传感器用于实时监测水体中溶解物的浓度,反映水质纯净程度,为判断水质优劣提供关键依据。通过STM32的ADC接口读取其输出的模拟信号,并结合补偿算法计算出当前水体的TDS值,再通过OLED本地显示及4G模块上传至物联网平台,为远程水质管理与预警提供数据支持。

2.4 溶解氧传感器

溶解氧检测传感器是一种用于测量水体中溶解氧(Dissolved Oxygen, DO)浓度的传感器,广泛应用于水质监测、水产养殖、环保工程、污水处理等领域。溶解氧是水中生物呼吸和水体自净能力的重要指标,过高或过低都会影响水体生态系统的稳定性。

常见的溶解氧传感器类型主要包括电化学式(极谱型、盖尔文型)和光学式。电化学式传感器工作原理基于氧分子通过膜扩散进入电极系统发生还原反应,产生电流信号,该信号与溶解氧浓度成正比。光学式则通过荧光猝灭原理测量氧气浓度,具有更高的稳定性和更长的使用寿命,但成本相对较高。

在本系统中使用的溶解氧传感器为 RS-485 接口输出型,具有抗干扰能力强、传输距离远、通信稳定等优点。通过与485转串口模块配合,STM32单片机可以通过串口协议(如Modbus-RTU)与传感器进行通信,实时读取水中溶解氧的数值。该传感器通常支持标准指令查询,返回的数据格式规范,便于主控程序解析。

传感器通常采用防水封装,能够长期浸入水中运行,且部分型号支持自动温度补偿,进一步提高测量的准确性。部分高端型号还具备校准功能,用户可以根据实际水质环境定期校准以保持精度。

在本水资源环境监测系统中,溶解氧传感器的作用是监控水体中氧含量水平,从而判断是否存在水质恶化、生物缺氧等异常情况。通过 STM32 读取溶解氧值后,不仅可在 OLED 显示屏上实时查看,还可以通过 4G 模块上传至云平台,便于远程监测和历史数据分析,为水质保护和应急响应提供依据。

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

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

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

3.1 物联网平台介绍

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

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

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

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

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

3.2 开通物联网服务

地址: www.huaweicloud.com/product/iot...

开通免费单元。

点击立即创建

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

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

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

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

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

总结:

scss 复制代码
端口号:   MQTT (1883)| MQTTS (8883)    
接入地址: dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com

根据域名地址得到IP地址信息:

打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。

ini 复制代码
Microsoft Windows [版本 10.0.19045.5011]
(c) Microsoft Corporation。保留所有权利。
​
C:\Users\Lenovo>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:\Users\Lenovo>
​

MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口合适

3.3 创建产品

链接:console.huaweicloud.com/iotdm/?regi...

(1)创建产品

(2)填写产品信息

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

(3)产品创建成功

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

(4)添加自定义模型

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

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

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

比如:

在将本项目采集的数据上传至 华为云 IoT 物联网平台 时,需要为设备模型(即产品模型)定义一组字段属性(属性服务),用于描述设备采集到的各种数据。这些属性应能完整表达你系统的功能。

根据项目功能,在华为云 IoT 平台定义以下字段属性:

字段名称(标识符) 数据类型 单位 描述 示例值
waterTemp float 水温 22.5
phValue float PH值 7.21
dissolvedOxygen float mg/L 溶解氧浓度 6.85
waterLevel float cm 当前水位高度 13.2
tdsValue float ppm TDS电导率(溶解性总固体) 580
uploadTime string 时间戳 数据上传时间 2025-06-05T12:30:00
alarmStatus int 异常报警状态(0正常,1异常) 0

说明:

  • 数据类型需与华为云平台支持的类型一致,如 intfloatstringboolean
  • alarmStatus 可用于报警提示功能,如当PH超标、TDS过高等时上传 1
  • uploadTime 可以作为历史数据存储和查询依据。

MQTT 上传格式(JSON 结构):

json 复制代码
{
  "waterTemp": 22.5,
  "phValue": 7.21,
  "dissolvedOxygen": 6.85,
  "waterLevel": 13.2,
  "tdsValue": 580,
  "alarmStatus": 0,
  "uploadTime": "2025-06-05T12:30:00"
}

在华为云 IoT 平台创建产品后,可以在 产品模型 > 服务 > 属性 中添加上述字段,并配置设备上传的 Topic。STM32 端通过 AT 指令控制 4G 模块(Air780E)连接 MQTT Broker,将这些字段打包为 JSON 并上报即可。

先点击自定义模型。

再创建一个服务ID。

接着点击新增属性。

3.4 添加设备

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

(1)注册设备

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

(3)保存设备信息

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

(4)设备创建完成

(5)设备详情

3.5 MQTT协议主题订阅与发布

(1)MQTT协议介绍

当前的设备是采用MQTT协议与华为云平台进行通信。

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。

MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。

华为云的MQTT协议接入帮助文档在这里: support.huaweicloud.com/devg-iothub...

业务流程:

(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)主题订阅格式

帮助文档地址:support.huaweicloud.com/devg-iothub...

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

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

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

bash 复制代码
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
    
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down

(4)主题发布格式

对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。

这个操作称为:属性上报。

帮助文档地址:support.huaweicloud.com/usermanual-...

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

bash 复制代码
发布的主题格式:
$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}}]}

3.6 MQTT三元组

MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。

接下来介绍,华为云平台的MQTT三元组参数如何得到。

(1)MQTT服务器地址

要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。

帮助文档地址:console.huaweicloud.com/iotdm/?regi...

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鉴权三元组: iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

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

下面是打开的页面:

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

直接得到三元组信息。

得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。

复制代码
ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237

3.7 模拟设备登录测试

经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。

MQTT软件下载地址【免费】: download.csdn.net/download/xi...

(1)填入登录信息

打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。

(2)打开网页查看

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

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

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

(3)MQTT登录测试参数总结

arduino 复制代码
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}}]}

3.8 创建IAM账户

创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。

地址: console.huaweicloud.com/iam/?region...

【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证

项目凭证:

复制代码
28add376c01e4a61ac8b621c714bf459

【2】创建IAM用户

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

点击左上角创建用户

创建成功:

【3】创建完成

用户信息如下:

复制代码
主用户名  l19504562721
IAM用户  ds_abc
密码     DS12345678

3.9 获取影子数据

帮助文档:support.huaweicloud.com/api-iothub/...

设备影子介绍:

javascript 复制代码
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性

来说:设备影子就是保存,设备最新上传的一次数据。

我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。

如果对接口不熟悉,可以先进行在线调试:apiexplorer.developer.huaweicloud.com/apiexplorer...

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

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

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

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

链接如下:

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

3.10 访问接口的代码实现

(1)配置 Qt 项目

在 Qt 项目的 .pro 文件中,加入对 libcurl 的支持:

makefile 复制代码
QT += core
CONFIG += console
CONFIG -= app_bundle
​
INCLUDEPATH += /usr/include/curl  # 根据你的系统设置 libcurl 的路径
LIBS += -lcurl  # 链接 libcurl 库
​
SOURCES += main.cpp

(2)代码实现

main.cpp 文件中实现代码如下:

arduino 复制代码
#include <QCoreApplication>
#include <curl/curl.h>
#include <QDebug>
#include <QString>
#include <QByteArray>
​
// 回调函数,处理libcurl下载数据
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t totalSize = size * nmemb;
    QByteArray *response = static_cast<QByteArray *>(userp);
    response->append(static_cast<char *>(contents), totalSize);
    return totalSize;
}
​
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
​
    // 初始化libcurl
    CURL *curl;
    CURLcode res;
    QByteArray responseData;  // 用于存储响应数据
​
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    if (curl) {
        // 设置访问URL
        const QString url = "https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow";
​
        // 设置HTTP请求头
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Authorization: Bearer <Your_Access_Token>"); // 这里需要替换为你的实际 token
​
        curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
​
        // 发起GET请求
        res = curl_easy_perform(curl);
​
        if (res != CURLE_OK) {
            qDebug() << "Curl request failed:" << curl_easy_strerror(res);
        } else {
            qDebug() << "Response data:" << responseData;
        }
​
        // 清理
        curl_easy_cleanup(curl);
        curl_slist_free_all(headers);
    }
​
    curl_global_cleanup();
​
    return a.exec();
}

3.11 数据解析代码

在 Qt 中使用 CJSON (一个用于解析 JSON 数据的轻量级 C 库) 来解析返回的 JSON 数据。

(1)配置 Qt 项目

在 Qt 项目的 .pro 文件中,确保包括了 CJSON 的头文件,并链接 CJSON 的源代码。

makefile 复制代码
QT += core
CONFIG += console
CONFIG -= app_bundle
​
SOURCES += main.cpp \
           cJSON.c  # 将 cJSON.c 文件添加到你的项目中
​
INCLUDEPATH += path/to/cjson/  # 添加 CJSON 头文件的路径
​
LIBS += -lcurl  # 链接 libcurl 库

(2)解析 JSON 数据的完整代码

main.cpp 中,以下代码展示了如何解析你提供的 JSON 数据。

ini 复制代码
#include <QCoreApplication>
#include <curl/curl.h>
#include <QDebug>
#include <QString>
#include <QByteArray>
#include "cJSON.h"
​
// 回调函数,处理libcurl下载数据
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t totalSize = size * nmemb;
    QByteArray *response = static_cast<QByteArray *>(userp);
    response->append(static_cast<char *>(contents), totalSize);
    return totalSize;
}
​
// 解析 JSON 数据
void parseJson(const QByteArray &data) {
    // 将 QByteArray 转换为 char*
    const char* jsonData = data.constData();
​
    // 解析 JSON
    cJSON *root = cJSON_Parse(jsonData);
    if (root == NULL) {
        qDebug() << "Error parsing JSON.";
        return;
    }
​
    // 解析 "device_id"
    cJSON *deviceId = cJSON_GetObjectItemCaseSensitive(root, "device_id");
    if (cJSON_IsString(deviceId) && (deviceId->valuestring != NULL)) {
        qDebug() << "Device ID:" << deviceId->valuestring;
    }
​
    // 解析 "shadow" 数组
    cJSON *shadow = cJSON_GetObjectItemCaseSensitive(root, "shadow");
    if (cJSON_IsArray(shadow)) {
        cJSON *shadowItem = NULL;
        cJSON_ArrayForEach(shadowItem, shadow) {
            // 解析每个 shadow 项目
            cJSON *serviceId = cJSON_GetObjectItemCaseSensitive(shadowItem, "service_id");
            if (cJSON_IsString(serviceId) && (serviceId->valuestring != NULL)) {
                qDebug() << "Service ID:" << serviceId->valuestring;
            }
​
            // 解析 "reported" 对象
            cJSON *reported = cJSON_GetObjectItemCaseSensitive(shadowItem, "reported");
            if (cJSON_IsObject(reported)) {
                // 解析 "properties" 对象
                cJSON *properties = cJSON_GetObjectItemCaseSensitive(reported, "properties");
                if (cJSON_IsObject(properties)) {
                    cJSON *data1 = cJSON_GetObjectItemCaseSensitive(properties, "data1");
                    if (cJSON_IsNumber(data1)) {
                        qDebug() << "data1:" << data1->valueint;
                    }
                    cJSON *data2 = cJSON_GetObjectItemCaseSensitive(properties, "data2");
                    if (cJSON_IsNumber(data2)) {
                        qDebug() << "data2:" << data2->valueint;
                    }
                    cJSON *data3 = cJSON_GetObjectItemCaseSensitive(properties, "data3");
                    if (cJSON_IsNumber(data3)) {
                        qDebug() << "data3:" << data3->valueint;
                    }
                    cJSON *data4 = cJSON_GetObjectItemCaseSensitive(properties, "data4");
                    if (cJSON_IsNumber(data4)) {
                        qDebug() << "data4:" << data4->valueint;
                    }
                }
​
                // 解析 "event_time"
                cJSON *eventTime = cJSON_GetObjectItemCaseSensitive(reported, "event_time");
                if (cJSON_IsString(eventTime) && (eventTime->valuestring != NULL)) {
                    qDebug() << "Event Time:" << eventTime->valuestring;
                }
            }
​
            // 解析 version
            cJSON *version = cJSON_GetObjectItemCaseSensitive(shadowItem, "version");
            if (cJSON_IsNumber(version)) {
                qDebug() << "Version:" << version->valueint;
            }
        }
    }
​
    // 释放 JSON 对象
    cJSON_Delete(root);
}
​
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
​
    // 模拟获取到的 JSON 数据
    QByteArray jsonData = R"(
    {
        "device_id": "663cb18871d845632a0912e7_dev1",
        "shadow": [
            {
                "service_id": "stm32",
                "desired": {
                    "properties": null,
                    "event_time": null
                },
                "reported": {
                    "properties": {
                        "data1": 18,
                        "data2": 90,
                        "data3": 38,
                        "data4": 70
                    },
                    "event_time": "20240509T113448Z"
                },
                "version": 3
            }
        ]
    })";
​
    // 调用解析函数
    parseJson(jsonData);
​
    return a.exec();
}

四、STM32设备端代码设计

下面给出的 STM32F103C8T6 主控的 main.c 代码框架,各子模块(PH传感器采集、DS18B20水温读取、485串口溶解氧读取、水位传感器ADC采集、TDS传感器ADC采集、OLED显示、4G模块MQTT通信、蜂鸣器报警)均有对应的驱动和接口函数。


设计思路(整体代码)

STM32主程序设计采用基于寄存器的编程方式,代码结构清晰,模块化管理。系统启动后,初始化所有硬件外设和通信接口(ADC、UART、I2C、GPIO等),然后进入主循环。

主循环中依次进行各传感器数据采集,通过ADC读取模拟传感器电压,串口通信读取溶解氧传感器数据,数字接口读取DS18B20温度数据。采集到的原始数据经过校准和算法转换,得到实际物理值。

随后将所有传感器数据更新到OLED屏幕,实现本地实时显示。同时,系统根据预设阈值判断是否产生报警,控制蜂鸣器提示。周期性地将完整数据打包成JSON格式,通过UART驱动的4G模块发送MQTT消息上传到华为云物联网平台。

系统通过定时器或延时函数控制采样和上传频率,确保实时性和稳定性。异常处理包括传感器故障检测和通信错误重试机制,保障系统可靠运行。


main.c 示例代码

scss 复制代码
#include "stm32f10x.h"
#include "adc.h"
#include "usart.h"
#include "i2c.h"
#include "oled.h"
#include "ds18b20.h"
#include "ph_sensor.h"
#include "do_sensor.h"
#include "tds_sensor.h"
#include "water_level_sensor.h"
#include "buzzer.h"
#include "mqtt_4g.h"
#include "json_utils.h"  // 自定义JSON封装函数
​
// 采样与上传周期 (单位: ms)
#define SAMPLE_INTERVAL 5000
#define UPLOAD_INTERVAL 60000
​
// 传感器数据结构体
typedef struct {
    float waterTemp;
    float phValue;
    float dissolvedOxygen;
    float waterLevel;
    float tdsValue;
    uint8_t alarmStatus;
} WaterData_t;
​
static WaterData_t g_waterData;
​
void SystemClock_Config(void);
void Periph_Init(void);
void Delay_ms(uint32_t ms);
uint8_t Check_Alarm(const WaterData_t *data);
​
int main(void)
{
    SystemClock_Config();
    Periph_Init();
​
    uint32_t lastSampleTime = 0;
    uint32_t lastUploadTime = 0;
​
    while(1)
    {
        uint32_t currentTime = GetSysTick_ms();
​
        // 定时采样
        if(currentTime - lastSampleTime >= SAMPLE_INTERVAL)
        {
            lastSampleTime = currentTime;
​
            // 读取各传感器数据
            g_waterData.waterTemp = DS18B20_ReadTemperature();
            g_waterData.phValue = PH_GetValue();          // 模拟量转换并校准
            g_waterData.dissolvedOxygen = DO_ReadValue(); // 485串口读取
            g_waterData.waterLevel = WaterLevel_ReadADC();// ADC读取
            g_waterData.tdsValue = TDS_ReadValue();       // ADC读取并算法转换
​
            // 判断报警状态
            g_waterData.alarmStatus = Check_Alarm(&g_waterData);
​
            // OLED显示更新
            OLED_Clear();
            OLED_ShowString(0, 0, "Water Temp:");
            OLED_ShowFloat(80, 0, g_waterData.waterTemp, 1);
            OLED_ShowString(0, 2, "PH Value:");
            OLED_ShowFloat(80, 2, g_waterData.phValue, 2);
            OLED_ShowString(0, 4, "DO mg/L:");
            OLED_ShowFloat(80, 4, g_waterData.dissolvedOxygen, 2);
            OLED_ShowString(0, 6, "Water Level:");
            OLED_ShowFloat(80, 6, g_waterData.waterLevel, 1);
            OLED_ShowString(0, 8, "TDS ppm:");
            OLED_ShowFloat(80, 8, g_waterData.tdsValue, 0);
​
            if(g_waterData.alarmStatus)
            {
                Buzzer_On();
            }
            else
            {
                Buzzer_Off();
            }
        }
​
        // 定时上传
        if(currentTime - lastUploadTime >= UPLOAD_INTERVAL)
        {
            lastUploadTime = currentTime;
​
            // 组装JSON数据
            char jsonData[256];
            Json_CreateWaterDataJson(&g_waterData, jsonData, sizeof(jsonData));
​
            // 通过4G模块上传
            MQTT_4G_Publish(jsonData);
        }
    }
}
​
// 报警判断逻辑示例
uint8_t Check_Alarm(const WaterData_t *data)
{
    if(data->phValue < 6.5f || data->phValue > 8.5f)
        return 1;
    if(data->dissolvedOxygen < 4.0f)
        return 1;
    if(data->tdsValue > 1000)
        return 1;
    // 根据需要扩展报警条件
    return 0;
}
​
// 初始化所有外设
void Periph_Init(void)
{
    ADC_Init_Config();
    USART1_Init(115200); // 4G模块串口
    USART2_Init(9600);   // 485串口(溶解氧)
    I2C_Init();
    OLED_Init();
    DS18B20_Init();
    PH_Sensor_Init();
    DO_Sensor_Init();
    TDS_Sensor_Init();
    WaterLevel_Sensor_Init();
    Buzzer_Init();
    MQTT_4G_Init();
}
​
// 系统时钟配置及systick定时器配置,具体由你实现
void SystemClock_Config(void)
{
    // 省略具体时钟初始化代码
}
​
// 获取系统运行时间(毫秒)
uint32_t GetSysTick_ms(void)
{
    // systick中断已配置,每1ms递增一个计数
    extern volatile uint32_t sysTickCounter;
    return sysTickCounter;
}

五、上位机开发

为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。

使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。

5.1 Qt开发环境安装

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

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

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

如果下载不了,可以在网盘里找到安装包下载: ccnr8sukk85n.feishu.cn/wiki/QjY8we...

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

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

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

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

【1】新建工程

【2】设置项目的名称。

【3】选择编译系统

【4】选择默认继承的类

【5】选择编译器

【6】点击完成

【7】工程创建完成

5.3 切换编译器

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

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

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

Android环境搭建的博客链接: blog.csdn.net/xiaolong112...

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

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

5.4 编译测试功能

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

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

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

5.5 设计UI界面与工程配置

【1】打开UI文件

打开默认的界面如下:

【2】开始设计界面

根据自己需求设计界面。

5.5 编译Windows上位机

点击软件左下角的绿色三角形按钮进行编译运行。

5.6 配置Android环境

如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)

然后才可以进行下面的步骤。

【1】选择Android编译器

选择编译器。

切换编译器。

【2】创建Android配置文件

创建完成。

【3】配置Android图标与名称

【3】编译Android上位机

Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。

然后点击构建。

成功之后,在目录下可以看到生成的apk文件,也就是Android手机的安装包,电脑端使用QQ发送给手机QQ,手机登录QQ接收,就能直接安装。

生成的apk的目录在哪里呢? 编译完成之后,在控制台会输出APK文件的路径。

知道目录在哪里之后,在Windows的文件资源管理器里,找到路径,具体看下图,找到生成的apk文件。

bash 复制代码
  -- File: D:/QtProject/build-265_AgritechIoTManager-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk

六、总结

本项目基于STM32F103C8T6主控芯片,设计实现了一个集水温、PH值、溶解氧、水位和TDS电导率多参数检测于一体的水资源环境监测系统。系统通过高精度传感器采集关键水质指标,利用OLED显示屏实现本地数据实时展示,保证现场监测的直观性与便捷性。结合Air780E 4G模块和MQTT协议,系统将采集的数据稳定可靠地上传至华为云物联网平台,支持远程监控与历史数据查询,极大提升了数据管理的智能化和自动化水平。

此外,系统设计中充分考虑了异常报警功能,能够及时发现并反馈水质异常,保障环境安全。Android手机APP和Windows上位机软件的双平台设计,实现了多终端的远程数据访问与管理,满足不同用户的使用需求。整体方案采用寄存器级STM32编程,提升了系统的稳定性和响应速度,兼顾硬件性能与软件效率。

本项目的完成不仅验证了基于STM32的多参数水质监测系统的可行性和实用性,也为后续环境监测设备的智能化升级提供了可靠的技术参考,具有较高的应用推广价值和社会意义。

相关推荐
Mr Aokey1 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
地藏Kelvin2 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
菠萝012 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺2 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
小奏技术3 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹4 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端
lanfufu4 小时前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端
编程轨迹4 小时前
如何在 Java 中实现 PDF 与 TIFF 格式互转
后端
编程轨迹4 小时前
面试官:你知道如何在 Java 中创建对话框吗
后端
编程轨迹4 小时前
深入理解 Java 中的信号机制
后端