一、项目概述
(一)项目背景
随着物联网技术的快速发展,智能化控制在智能家居、工业监控等领域的应用愈发广泛。传统的环境监测与设备控制方式存在精度低、响应慢、依赖人工操作等问题,难以满足现代化管理需求。基于此,本项目设计了一套以 STM32 为主控制器、ESP8266 为无线通信模块的物联网控制系统,实现对温湿度的精准采集及设备的智能控制。
(二)项目目标
- 实现温湿度数据的实时采集,采用多传感器融合技术提高测量精度。
- 通过无线通信模块将数据上传至云端平台,支持远程监控。
- 根据采集到的数据及云端指令,自动控制风扇及抽水设备的运行。
- 解决设备启动时的浪涌电压和冲击电流问题,保障系统稳定运行。
二、系统总体设计
(一)系统架构
系统采用 “主控 + 通信 + 感知 + 执行” 的架构,以 STM32 为核心控制器,负责数据采集、处理及设备控制;ESP8266 作为无线通信模块,实现与云端平台的数据交互;温湿度传感器负责环境参数采集;继电器模块作为执行单元,控制风扇和抽水设备的运行。
(二)核心功能
- 温湿度采集:通过 DS18B20、PT100 及湿度传感器获取环境数据。
- 数据处理:对多传感器数据进行校准与融合,提高测量精度。
- 无线传输:将处理后的数据上传至 TingsCloud 云端,接收远程控制指令。
- 设备控制:根据预设逻辑及云端指令,控制风扇挡位切换和抽水设备启停。
三、硬件系统设计与实现
(一)主控模块
选用 STM32 系列芯片(如 STM32F103)作为主控制器,其具备丰富的外设资源(GPIO、ADC、TIM 等),可满足数据采集、定时器控制及串口通信等需求。
(二)传感器模块
- 温度采集:采用 DS18B20(数字输出,-55℃~125℃,精度 ±0.5℃)和 PT100(配合运算放大电路,高精度测量)。
- 湿度采集:选用 DHT11 等湿度传感器,实现环境湿度的实时监测。
(三)通信模块
采用 ESP8266 模块,通过串口与 STM32 通信,支持 MQTT 协议,实现与 TingsCloud 云端的数据传输。
(四)执行模块
继电器模块采用光耦隔离设计,连接风扇(多挡位)和抽水设备,实现设备的安全控制。
(五)防护电路
- 浪涌抑制:在 220V 交流回路中接入 385V 压敏电阻(MOV),抑制启动浪涌电压。
- 过流保护:串联自恢复保险丝,防止冲击电流损坏电路及 PCB 板。
四、软件系统开发
(一)数据采集与处理
- 定时采集:STM32 配置 TIM 定时器产生定时中断(如 100ms 周期),触发数据采集。
- 数据传输:采用 DMA 方式处理 ADC 数据(PT100)和 GPIO 信号(DS18B20),减少 CPU 占用。
- 校准与融合:通过标准温度源校准双传感器,建立误差修正模型,对修正后的数据取均值,提升精度。
(二)数据传输
- 本地通信:STM32 将温湿度数据打包,通过串口(DMA 传输)发送至 ESP8266。
- 云端交互:ESP8266 解析数据后,通过 MQTT 协议上传至 TingsCloud,并接收远程指令反馈给 STM32。
(三)控制逻辑
STM32 根据采集数据及云端指令,控制继电器吸合 / 断开:
- 温度超阈值时,启动风扇并切换挡位;
- 湿度低于阈值时,触发抽水设备运行。
五、系统调试与优化
(一)调试过程
- 硬件调试:检查电路连接(短路、断路),通过万用表和示波器验证传感器信号及电源稳定性。
- 软件调试:测试数据采集准确性(串口打印校验)、通信链路(STM32 与 ESP8266、云端交互)及控制逻辑(继电器动作响应)。
(二)问题解决
- 电机启动冲击:通过压敏电阻、自恢复保险丝及光耦隔离继电器,解决浪涌电压和过流问题。
- 数据偏差:优化传感器校准模型,提高测量精度。
(三)优化效果
系统实现温湿度精准采集(误差 ±0.5℃/±5% RH),设备响应延迟 < 100ms,运行稳定可靠。
六、项目总结与展望
(一)总结
本项目成功研发了基于 STM32 与 ESP8266 的物联网控制系统,实现了温湿度监测、远程控制及设备防护等功能,验证了方案的可行性与实用性。
(二)展望
未来可拓展传感器类型(如光照、气体),引入智能算法(如 PID)优化控制逻辑,提升系统智能化水平,适配更多应用场景。
#include <ThingsCloudWiFiManager.h>
#include <ThingsCloudMQTT.h>
#include <ArduinoJson.h>
/******************** WiFi 配置******************************/
const char *ssid = "WIFIname";
const char *password = "password";
/**********************MQTT配置****************************/
// ThingsCloud 配置
#define THINGSCLOUD_MQTT_HOST ""
#define THINGSCLOUD_DEVICE_ACCESS_TOKEN ""
#define THINGSCLOUD_PROJECT_KEY ""
ThingsCloudMQTT client(THINGSCLOUD_MQTT_HOST, THINGSCLOUD_DEVICE_ACCESS_TOKEN, THINGSCLOUD_PROJECT_KEY);
/**************** JSON 上报相关**********************************/
DynamicJsonDocument obj(512);
char attributes[512];
/**************************************************/
unsigned long previousPrintTime = 0; // 用于控制打印间隔
const unsigned long printInterval = 1000*30; // 30秒打印/上报间隔(原注释有误,已修正)
/**************************************************/
void store_data(float x, float y)
{
obj["temperature"] = x;
obj["humidity"] = y;
}
void push_data()
{
if (obj.containsKey("temperature") && obj.containsKey("humidity"))
{
serializeJson(obj, attributes);
client.reportAttributes(attributes);
Serial.print("上报数据: ");
Serial.println(attributes);
}
else
{
Serial.println("无有效数据,跳过上报");
}
}
/************************串口缓冲区配置**************************/
#define RX_BUFFER_SIZE 256
/***********************变量数据***************************/
struct SerialData
{
float temperature;
float humidity;
};
/*****************串口数据事件处理**************************/
namespace SerialEvent
{
static SerialData circularBuffer[RX_BUFFER_SIZE]; // 缓冲区
static int headIndex = 0; // 写索引
static int tailIndex = 0; // 读索引
static SerialData currentData; // 【新增】全局可访问的当前数据(核心修改)
void Serialanalyze()
{
while (Serial.available()) {
String line = Serial.readStringUntil('\n');
line.trim();
if (line.endsWith("\r")) {
line.remove(line.length() - 1);
}
float temp = NAN, humi = NAN;
// 解析完整格式(T=和H=)
if (sscanf(line.c_str(), "T=%f,H=%f", &temp, &humi) == 2 ||
sscanf(line.c_str(), "t=%f,h=%f", &temp, &humi) == 2 ||
sscanf(line.c_str(), "T= %f ,H= %f", &temp, &humi) == 2 ||
sscanf(line.c_str(), "t= %f ,h= %f", &temp, &humi) == 2) {
// 【修复】补全数据存储逻辑(原代码此处为空,导致缓冲区无数据)
circularBuffer[headIndex].temperature = temp;
circularBuffer[headIndex].humidity = humi;
headIndex = (headIndex + 1) % RX_BUFFER_SIZE;
}
// 解析单独温度
else if (sscanf(line.c_str(), "T=%f", &temp) == 1 ||
sscanf(line.c_str(), "t=%f", &temp) == 1) {
circularBuffer[headIndex].temperature = temp;
circularBuffer[headIndex].humidity = NAN;
headIndex = (headIndex + 1) % RX_BUFFER_SIZE;
}
// 解析单独湿度
else if (sscanf(line.c_str(), "H=%f", &humi) == 1 ||
sscanf(line.c_str(), "h=%f", &humi) == 1) {
circularBuffer[headIndex].temperature = NAN;
circularBuffer[headIndex].humidity = humi;
headIndex = (headIndex + 1) % RX_BUFFER_SIZE;
}
else {
Serial.print("解析失败: 格式错误 - ");
Serial.println(line);
continue;
}
}
}
void Serialcope()
{
Serialanalyze();
int unreadCount = (headIndex - tailIndex + RX_BUFFER_SIZE) % RX_BUFFER_SIZE;
if (unreadCount > 0)
{
// 读取待消费数据
SerialData data = circularBuffer[tailIndex];
// 【新增】更新全局可访问的当前数据
currentData = data;
// 打印调试信息
Serial.print("最新数据(调试)- 温度: ");
Serial.print(data.temperature);
Serial.print(" °C, 湿度: ");
Serial.print(data.humidity);
Serial.println(" %");
tailIndex = (tailIndex + 1) % RX_BUFFER_SIZE;
}
}
int getUnreadCount()
{
return (headIndex - tailIndex + RX_BUFFER_SIZE) % RX_BUFFER_SIZE;
}
SerialData getLatestData()
{
if (getUnreadCount() > 0)
{
int latestIndex = (tailIndex + getUnreadCount() - 1) % RX_BUFFER_SIZE;
return circularBuffer[latestIndex];
}
return {0.0f, 0.0f};
}
}
/********************串口数据事件处理******************************/
/*******************定时任务*******************************/
unsigned long timer1 = millis();
const int REPORT_INTERVAL = 1000 * 30; // 30秒上报一次
bool firstReported = false;
void timer_tesk()
{
unsigned long currentMillis = millis();
if (currentMillis - previousPrintTime >= printInterval)
{
previousPrintTime = currentMillis;
SerialEvent::Serialcope(); // 解析数据并更新全局currentData
// 【修改】使用全局currentData(核心修改)
SerialData data = SerialEvent::currentData;
// 检查数据有效性(避免NAN或无效值)
if (!isnan(data.temperature) && !isnan(data.humidity))
{
store_data(data.temperature, data.humidity);
push_data();
}
else
{
Serial.println("数据无效,跳过上报");
}
}
}
// 连接回调函数
void onMQTTConnect()
{
Serial.println("MQTT 连接成功");
client.executeDelayed(1000 * 5, []() {
if (SerialEvent::getUnreadCount() > 0)
{
SerialData latest = SerialEvent::getLatestData();
store_data(latest.temperature, latest.humidity);
push_data();
}
});
}
void setup()
{
Serial.begin(9600);
client.enableDebuggingMessages();
client.setWifiCredentials(ssid, password);
Serial.println("ESP8266 启动中...");
store_data(10,0);
push_data();
}
void loop()
{
client.loop(); // 处理MQTT
SerialEvent::Serialcope(); // 实时解析数据(更新全局currentData)
timer_tesk(); // 定时上报
}