实战项目STM32 与 ESP8266 的物联网控制系统

一、项目概述

(一)项目背景

随着物联网技术的快速发展,智能化控制在智能家居、工业监控等领域的应用愈发广泛。传统的环境监测与设备控制方式存在精度低、响应慢、依赖人工操作等问题,难以满足现代化管理需求。基于此,本项目设计了一套以 STM32 为主控制器、ESP8266 为无线通信模块的物联网控制系统,实现对温湿度的精准采集及设备的智能控制。

(二)项目目标

  1. 实现温湿度数据的实时采集,采用多传感器融合技术提高测量精度。
  1. 通过无线通信模块将数据上传至云端平台,支持远程监控。
  1. 根据采集到的数据及云端指令,自动控制风扇及抽水设备的运行。
  1. 解决设备启动时的浪涌电压和冲击电流问题,保障系统稳定运行。

二、系统总体设计

(一)系统架构

系统采用 “主控 + 通信 + 感知 + 执行” 的架构,以 STM32 为核心控制器,负责数据采集、处理及设备控制;ESP8266 作为无线通信模块,实现与云端平台的数据交互;温湿度传感器负责环境参数采集;继电器模块作为执行单元,控制风扇和抽水设备的运行。

(二)核心功能

  1. 温湿度采集:通过 DS18B20、PT100 及湿度传感器获取环境数据。
  1. 数据处理:对多传感器数据进行校准与融合,提高测量精度。
  1. 无线传输:将处理后的数据上传至 TingsCloud 云端,接收远程控制指令。
  1. 设备控制:根据预设逻辑及云端指令,控制风扇挡位切换和抽水设备启停。

三、硬件系统设计与实现

(一)主控模块

选用 STM32 系列芯片(如 STM32F103)作为主控制器,其具备丰富的外设资源(GPIO、ADC、TIM 等),可满足数据采集、定时器控制及串口通信等需求。

(二)传感器模块

  1. 温度采集:采用 DS18B20(数字输出,-55℃~125℃,精度 ±0.5℃)和 PT100(配合运算放大电路,高精度测量)。
  1. 湿度采集:选用 DHT11 等湿度传感器,实现环境湿度的实时监测。

(三)通信模块

采用 ESP8266 模块,通过串口与 STM32 通信,支持 MQTT 协议,实现与 TingsCloud 云端的数据传输。

(四)执行模块

继电器模块采用光耦隔离设计,连接风扇(多挡位)和抽水设备,实现设备的安全控制。

(五)防护电路

  1. 浪涌抑制:在 220V 交流回路中接入 385V 压敏电阻(MOV),抑制启动浪涌电压。
  1. 过流保护:串联自恢复保险丝,防止冲击电流损坏电路及 PCB 板。

四、软件系统开发

(一)数据采集与处理

  1. 定时采集:STM32 配置 TIM 定时器产生定时中断(如 100ms 周期),触发数据采集。
  1. 数据传输:采用 DMA 方式处理 ADC 数据(PT100)和 GPIO 信号(DS18B20),减少 CPU 占用。
  1. 校准与融合:通过标准温度源校准双传感器,建立误差修正模型,对修正后的数据取均值,提升精度。

(二)数据传输

  1. 本地通信:STM32 将温湿度数据打包,通过串口(DMA 传输)发送至 ESP8266。
  1. 云端交互:ESP8266 解析数据后,通过 MQTT 协议上传至 TingsCloud,并接收远程指令反馈给 STM32。

(三)控制逻辑

STM32 根据采集数据及云端指令,控制继电器吸合 / 断开:

  • 温度超阈值时,启动风扇并切换挡位;
  • 湿度低于阈值时,触发抽水设备运行。

五、系统调试与优化

(一)调试过程

  1. 硬件调试:检查电路连接(短路、断路),通过万用表和示波器验证传感器信号及电源稳定性。
  1. 软件调试:测试数据采集准确性(串口打印校验)、通信链路(STM32 与 ESP8266、云端交互)及控制逻辑(继电器动作响应)。

(二)问题解决

  1. 电机启动冲击:通过压敏电阻、自恢复保险丝及光耦隔离继电器,解决浪涌电压和过流问题。
  1. 数据偏差:优化传感器校准模型,提高测量精度。

(三)优化效果

系统实现温湿度精准采集(误差 ±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(); // 定时上报
}
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值