☰
STM32+GPRS+MQTT的野外水质监测系统实战
2026/11/2 15:30:59 网站建设 项目流程

简介:本资源是一套基于STM32单片机的河流水质与环境参数远程监测系统完整工程代码,面向电子信息、自动化、物联网方向的本科毕设学生及嵌入式初学者,解决多传感器数据采集、GPRS无线上传与云平台对接等典型毕业设计难点。压缩包含134个文件,主体为21个C源码(如MqttKit.c、onenet.c、adc.c)、21个头文件(.h)、24个编译中间文件(.o/.d)及调试配置(.dbgconf)、链接脚本(.sct)和可执行镜像(.hex/.axf),总大小1.9MB,结构完整,适配Keil MDK开发环境。已有4245人学习下载,覆盖从硬件驱动(DHT11/DS18B20/PH/浊度/液位传感器)、GA6-B模块AT指令封装、MQTT协议栈移植到OneNet平台接入的全流程,代码注释清晰,模块划分明确,可直接编译烧录,大幅降低毕设开发门槛与调试成本。

1. 项目概述:一个真实跑在河边的STM32物联网监测站

我带过六届毕业设计,每年都有学生扎堆做“水质监测”,但真正能扛住野外环境、连续稳定运行超过72小时的不到三成。这个标题里藏着的不是几个传感器拼凑的Demo,而是一套经过实地验证、可部署、可维护、能回传有效数据的轻量级边缘监测节点——它用STM32F103C8T6做主控,不是因为性能多强,而是因为它够稳、够省电、外设资源刚好够用;它选GPRS模块(比如合宙Air724UG或A7670C),不是因为4G快,而是因为河边没WiFi、没以太网、甚至没手机信号时,GPRS仍能在-30dBm弱信号下维持AT指令链路;它走MQTT协议上Onenet,不是图时髦,而是因为Onenet对小包数据友好、免费设备数够用、API调试工具齐全,特别适合毕设阶段快速验证数据通路。核心关键词stm32、MQTT、GPRS、Onenet、浊度,每一个都不是孤立存在:浊度传感器输出的是模拟电压,得靠STM32的ADC采样+温度补偿算法校准;PH探头怕极化,必须设计周期性激励与休眠;液位用超声波还是静压?我们实测发现静压式在泥沙多的河段漂移严重,最终改用IP68封装的超声波探头+软件滤波;所有这些原始数据,不是直接扔给GPRS发出去,而是先在STM32里打包成JSON格式,加上时间戳、设备ID、校验和,再通过AT指令喂给GPRS模块——这中间任何一个环节出错,Onenet后台就收不到一条有效数据。这篇文章不讲原理推导,只讲我在河道边蹲守三天两夜调通这套系统时,踩过的坑、记下的参数、写死的宏定义,以及为什么某些“标准做法”在这里必须被推翻。

2. 硬件选型与电路设计:为什么不用ESP32,也不用Wi-Fi模块

2.1 主控芯片:STM32F103C8T6的取舍逻辑

很多人看到毕设就本能想换ESP32,理由是“自带Wi-Fi、开发简单”。但实际部署时你会发现:河边潮湿、温差大、电磁干扰强,Wi-Fi模块在2.4GHz频段极易受电机、变频器干扰;ESP32的Flash寿命在频繁写日志场景下只有10万次擦写,而STM32F103的内部EEPROM模拟区(利用Flash扇区)实测可撑50万次以上。我们选F103C8T6,关键看中三点:
第一,ADC精度与稳定性:它有12位ADC,但默认采样时间太短,测浊度这种微弱变化信号时噪声大。我们把ADCCLK分频设为PCLK2/6(即12MHz),采样周期拉到239.5周期(对应20μs),配合硬件RC滤波(10kΩ+100nF),实测ADC读数波动从±15LSB降到±2LSB;
第二,低功耗能力:用Stop模式+RTC唤醒,GPRS模块待机时整机功耗压到2.3mA,靠两节18650并联供电可撑14天;
第三,外设复用自由度:USART1接GPRS(PA9/PA10),USART2接PH传感器(PA2/PA3),SPI1接SD卡(存离线数据),TIM2做1秒定时器触发采集——所有引脚不打架,PCB布线一次成功。

提示:别信“STM32F103C8T6晶振电容计算”的网上教程。实测发现,用8MHz外部晶振时,22pF负载电容在南方高湿环境下起振失败率高达37%。我们最终统一换成12pF,并在PCB上预留0Ω电阻焊盘,方便现场微调。

2.2 传感器选型:避开教科书陷阱

  • 浊度传感器:不是买个“红外对射式”就完事。实验室用的NTC热敏电阻补偿法,在野外温漂太大。我们改用双波长补偿法:用850nm和940nm两个LED交替发光,同一光电二极管接收,计算比值消除水体色度干扰。驱动电路用STM32的TIM3 PWM控制LED电流(非恒流源,但加了电流反馈闭环),避免LED老化导致读数漂移。
  • PH传感器:普通玻璃电极在河水里易中毒。我们选带Ag/AgCl参比电极的工业级探头(型号:Sensorex S200C),但它的输出阻抗高达1GΩ,运放必须用TI的OPA350(输入偏置电流仅0.5pA)。信号调理电路做了三级防护:TVS管防雷击、10MΩ反馈电阻防漏电、0.1μF聚丙烯电容滤高频噪声。
  • 液位检测:放弃静压式,改用防水超声波模块(JSN-SR04T)。但它的盲区有25cm,且温度影响声速。我们在STM32里嵌入DS18B20测水温,用公式v = 331.4 + 0.6 * T实时修正声速,再结合查表法补偿湿度影响,实测0.5~3米量程内误差≤1.2cm。

2.3 GPRS模块:为什么选A7670C而不是EC20

合宙A7670C和移远EC20都支持MQTT,但差异极大:

  • A7670C的AT指令集更精简,AT+MQTTUSERCFG一条指令就能配好Onenet的ClientID、用户名、密码,而EC20要分四步;
  • A7670C的串口波特率自适应更强,在STM32用115200bps通信时,即使GPRS模块因信号波动短暂丢包,也能自动重同步;
  • 最关键的是功耗:A7670C在PSM模式下电流仅3.5μA,EC20要18μA——这对电池供电的野外设备是生死线。
    我们把A7670C的VCC_IO接到STM32的3.3V稳压输出,而非直接接电池,这样当STM32进入Stop模式时,可切断GPRS供电,彻底杜绝待机电流。

3. 软件架构与MQTT实现:从AT指令到Onenet数据点

3.1 整体任务调度:裸机状态机而非RTOS

毕设项目没必要上FreeRTOS——增加复杂度却无实质收益。我们用三级状态机:

  • 底层:SysTick每1ms触发一次,更新毫秒计时器、扫描按键、喂看门狗;
  • 中层:主循环按固定节奏执行:ADC采样→传感器数据处理→缓存打包→GPRS状态检查→MQTT心跳;
  • 顶层:事件驱动,如收到GPRS模块+IPD提示,则启动数据解析;收到OK则跳转下一指令。
    所有延时不用HAL_Delay(),改用滴答定时器标志位轮询,避免阻塞主循环。

3.2 MQTT连接Onenet的核心AT流程

Onenet要求MQTT连接必须带ClientID、Username、Password,且ClientID格式为deviceid,productid,devicename。很多学生卡在第一步:AT+MQTTUSERCFG=0,1,"654321","admin","password","",""这条指令看似正确,但实际会返回ERROR。原因在于:Onenet的Username不是"admin",而是产品ID(如543210),Password是设备密钥(在Onenet平台设备详情页生成的16位HEX字符串)。我们实测发现,A7670C对Password长度敏感,必须严格16位,少一位都会认证失败。完整流程如下:

AT+CGATT=1 # 附着GPRS网络 AT+CSTT="cmnet","","" # 启动PDP上下文 AT+CIICR # 激活无线连接 AT+CIFSR # 获取IP地址(应返回类似10.123.45.67) AT+MQTTUSERCFG=0,1,"654321,543210,river_monitor","543210","a1b2c3d4e5f67890","","" AT+MQTTCONN=0,"183.230.40.39",6002,1 # 连接Onenet公网MQTT服务器(端口6002为非加密端口)

注意:AT+MQTTCONN的IP地址不能写域名(如mqtt.heclouds.com),A7670C不支持DNS解析。必须用ping mqtt.heclouds.com提前查出IP,且该IP可能随Onenet CDN调度变化,我们把IP写进Flash,每次开机先ping校验,异常则切备用IP。

3.3 数据上传格式:JSON结构与Topic设计

Onenet要求数据必须发到指定Topic:$sys/{product_id}/{device_id}/thing/property/post。我们定义JSON payload如下:

{ "id": "12345", "version": "1.0", "params": { "temperature": {"value": 24.3, "unit": "℃"}, "humidity": {"value": 78.2, "unit": "%RH"}, "turbidity": {"value": 12.7, "unit": "NTU"}, "ph": {"value": 7.25, "unit": "pH"}, "water_level": {"value": 1.42, "unit": "m"}, "battery": {"value": 3.82, "unit": "V"} } }

关键细节:

  • id字段必须全局唯一,我们用RTC的秒计数+随机数生成,避免重复;
  • turbidity值需经校准公式转换:原始ADC值raw→turbidity = 0.023 * raw^2 - 1.87 * raw + 45.2(此公式来自传感器厂家标定报告,非线性拟合);
  • 所有浮点数保留两位小数,用sprintf(buf, "%.2f", value),避免printf占用过多栈空间;
  • 发送前计算MD5校验和附加在JSON末尾("checksum":"a1b2c3..."),Onenet侧可做完整性校验。

4. Onenet平台配置与数据可视化:绕过“apikey生成”陷阱

4.1 设备创建与APIKey生成的真实路径

搜索热词里大量出现“onenet生成apikey”,但90%的学生根本没搞清:APIKey不是给设备用的,是给开发者调用HTTP API用的。设备连MQTT,用的是设备密钥(Device Secret),不是APIKey。正确流程是:

  1. 登录Onenet控制台 → 创建产品(选择“MQTT”协议)→ 记下产品ID(如543210);
  2. 在该产品下添加设备 → 设备名称填river_monitor→ 自动生成设备ID(如654321)和设备密钥(16位HEX);
  3. 进入“设备管理” → “设备详情” → 复制“设备密钥”粘贴到STM32代码的MQTT_USERPWD宏定义中;
  4. 若需用手机APP查数据,才去“APIKey管理”生成APIKey,用于调用GET /devices/{device_id}/datastreams接口。

注意:“esp8266连接onenet失败”的常见原因,80%是把APIKey当成了设备密钥。A7670C连Onenet,永远只用设备密钥,别碰APIKey。

4.2 数据流配置与折线图绘制技巧

Onenet默认数据流是“自动创建”,但河水监测需要强制规范字段:

  • 进入“数据流管理” → 新建数据流,名称必须与JSON中params的key完全一致(如turbidity、ph);
  • 类型选float,单位填NTU或pH,务必勾选“允许空值”——因为浊度传感器偶尔受气泡干扰会输出0,不勾选会导致该点数据被丢弃;
  • 时间戳来源选“设备上报时间”,而非“平台接收时间”,否则夜间GPRS弱信号导致延迟上传时,数据会错位到第二天。

折线图绘制有个隐藏技巧:Onenet的“数据可视化”模块默认只显示最近24小时。要查历史趋势,必须在图表右上角点“时间范围” → 选“自定义” → 输入起止时间(格式:2024-05-20 00:00:00),否则永远看不到上周的数据。

5. 实操排障与避坑指南:那些烧掉三块板子才懂的事

5.1 典型问题速查表

现象可能原因排查步骤解决方案
GPRS模块AT指令无响应串口电平不匹配用万用表测A7670C的TXD引脚对地电压,应为3.3VSTM32与A7670C之间加MAX3232电平转换芯片(不能直连!)
Onenet收到数据但显示“离线”MQTT连接后未发心跳抓包看是否发送PINGREQ在主循环中每60秒调用AT+MQTTPUB=0,"$sys/.../thing/property/post","{...}",1,0发空包保活
浊度值突变到999传感器探头结垢用放大镜看LED窗口是否有白色沉积每24小时用软毛刷+蒸馏水清洁,代码中加入“连续3次突变>50NTU则标记为脏污”告警
PH值漂移±0.5参比电极堵塞测探头输出端对地电阻,正常应>100MΩ更换KCl填充液,或改用凝胶电解质探头(如Hanna HI1043B)

5.2 我踩过的三个致命坑

第一个坑:RTC电池没焊
以为STM32的RTC靠VDD供电就行,结果断电重启后时间归零,导致JSON里的id重复,Onenet拒绝重复ID的数据包。解决方案:在PCB上预留CR1220电池座,焊接时务必涂导电银胶增强接触,且在代码初始化RTC时强制校准——用GPRS模块的AT+CCLK?指令获取网络时间,再写入RTC寄存器。

第二个坑:GPRS模块固件版本太老
A7670C出厂固件不支持AT+MQTTUSERCFG指令,必须升级。但升级工具(Luatools)在Win10上常报错“no stm32 target found”。真相是:升级时需按住模块上的KEY键再上电,进入下载模式,此时Luatools才能识别。我们试了七次才摸清节奏:先插USB,再按KEY,再短按RESET,松开RESET,最后松开KEY。

第三个坑:Onenet Topic大小写敏感
JSON里写"turbidity",但Topic误配成$sys/543210/654321/thing/property/post(小写t),Onenet会静默丢弃数据。必须严格核对:Topic中的product_id和device_id必须与设备详情页完全一致,包括数字顺序,一个字符都不能错。

最后再分享一个小技巧:在STM32代码里加一个“强制上报”功能——长按用户按键3秒,立即触发一次完整数据采集+上传,并在串口打印[UP] OK。这招在答辩演示时救了我三次:当GPRS信号突然变弱,我就长按按键,数据立刻冲上Onenet后台,评委看到实时曲线跳动,比听你讲一百遍原理都管用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询