STM32+无线传输+PyQt5 温室环境远程监控系统设计思路(示例代码)

发布于:2025-06-23 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、项目概述

1.1 项目背景与目标

在现代农业中,传统的温室管理方式主要依赖人工巡检,存在以下问题:

  • 人工成本高,效率低下
  • 无法实时监控环境参数
  • 缺乏数据分析和预警机制
  • 远程控制能力有限

项目目标:通过STM32+WiFi+Python技术栈,构建一套完整的温室环境远程监控与自动化控制系统,实现:

  • 实时监测温湿度、光照、土壤湿度等关键参数
  • 支持手机/电脑远程查看数据和控制设备
  • 自动报警和阈值控制功能
  • 历史数据存储和分析

1.2 技术栈选型

技术领域 具体选型 选择理由
主控芯片 STM32F103C8T6 成本低,外设丰富,开发资料完善
WiFi模块 ESP8266-01S 支持AT指令,兼容性强,功耗适中
温湿度传感器 DHT11 数字输出,误差±2%RH,性价比高
光照传感器 BH1750 I2C接口,精度高,功耗低
通信协议 MQTT over TCP 轻量级,适合物联网场景,支持QoS
上位机开发 Python 3.8 + PyQt5 开发效率高,界面友好,生态丰富

二、系统架构设计

2.1 整体架构

系统采用分层架构设计,确保各模块职责清晰,便于维护和扩展:

┌─────────────────┐
│   应用层        │  Python上位机 (数据可视化、远程控制)
├─────────────────┤
│   传输层        │  ESP8266 WiFi模块 (数据传输)
├─────────────────┤
│   感知层        │  STM32 + 传感器 (数据采集)
└─────────────────┘

2.2 数据流向

在这里插入图片描述

三、环境搭建与硬件连接

3.1 硬件清单

组件 型号 数量 备注
主控芯片 STM32F103C8T6 1 最小系统板
WiFi模块 ESP8266-01S 1 支持AT指令
温湿度传感器 DHT11 1 数字输出
光照传感器 BH1750 1 I2C接口
土壤湿度传感器 模拟量 1 0-3.3V输出
继电器模块 5V继电器 1 控制水泵
电源模块 3.3V/5V 1 双路输出

3.2 关键连接说明

ESP8266接线(重要!)
ESP8266引脚    →    STM32引脚
TX            →    USART3_RX (PB11)
RX            →    USART3_TX (PB10)
CH_PD         →    3.3V
VCC           →    3.3V
GND           →    GND

⚠️ 重要提醒

  • ESP8266必须使用3.3V供电,直接接5V会烧毁模块
  • CH_PD引脚必须接3.3V才能正常工作
  • 建议在TX/RX之间加入电平转换电路
传感器连接
DHT11:    DATA → PA0 (GPIO输入)
BH1750:   SCL → PB6 (I2C1_SCL)
          SDA → PB7 (I2C1_SDA)
土壤传感器: OUT → PA1 (ADC1_IN1)
继电器:    IN → PA2 (PWM输出)

3.3 软件环境配置

STM32开发环境
  1. STM32CubeMX配置

    • 配置USART3用于ESP8266通信
    • 配置I2C1用于BH1750
    • 配置ADC1用于土壤湿度
    • 配置定时器用于PWM输出
    • 生成HAL库代码
  2. 开发工具

    • STM32CubeIDE
    • ST-Link调试器
Python环境配置
# 安装依赖包
pip install paho-mqtt pyqt5 sqlite3 numpy matplotlib

# 验证安装
python -c "import paho.mqtt.client; print('MQTT OK')"
python -c "import PyQt5; print('PyQt5 OK')"

四、代码实现详解

4.1 STM32传感器数据采集

DHT11温湿度读取
// dht11.h
typedef struct {
    float temperature;
    float humidity;
} DHT11_Data_TypeDef;

void DHT11_Init(void);
HAL_StatusTypeDef DHT11_ReadData(DHT11_Data_TypeDef *data);

// dht11.c
void DHT11_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = DHT11_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
}

HAL_StatusTypeDef DHT11_ReadData(DHT11_Data_TypeDef *data) {
    uint8_t buffer[5];
    uint32_t timeout = 0;
    
    // 主机发送开始信号
    HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET);
    HAL_Delay(18);  // 拉低至少18ms
    
    HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET);
    HAL_Delay(1);   // 拉高1ms
    
    // 配置为输入模式
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DHT11_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
    
    // 等待DHT11响应信号
    while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET) {
        timeout++;
        if(timeout > 1000) return HAL_ERROR;
    }
    
    // 读取40位数据
    for(int i = 0; i < 5; i++) {
        buffer[i] = 0;
        for(int j = 0; j < 8; j++) {
            while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_RESET);
            
            uint32_t start_time = HAL_GetTick();
            while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET);
            uint32_t duration = HAL_GetTick() - start_time;
            
            if(duration > 50) {
                buffer[i] |= (1 << (7-j));
            }
        }
    }
    
    // 验证校验和
    if(buffer[4] != (buffer[0] + buffer[1] + buffer[2] + buffer[3])) {
        return HAL_ERROR;
    }
    
    // 计算温湿度
    data->humidity = (float)buffer[0] + (float)buffer[1] / 10.0;
    data->temperature = (float)buffer[2] + (float)buffer[3] / 10.0;
    
    return HAL_OK;
}
BH1750光照传感器读取
// bh1750.h
#define BH1750_ADDR 0x46  // 器件地址
#define BH1750_POWER_ON 0x01
#define BH1750_RESET 0x07
#define BH1750_CONT_H_MODE 0x10

float BH1750_ReadLux(void);

// bh1750.c
float BH1750_ReadLux(void) {
    uint8_t cmd = BH1750_POWER_ON;
    HAL_I2C_Master_Transmit(&hi2c1, BH1750_ADDR, &cmd, 1, 1000);
    
    cmd = BH1750_CONT_H_MODE;
    HAL_I2C_Master_Transmit(&hi2c1, BH1750_ADDR, &cmd, 1, 1000);
    
    HAL_Delay(120);  // 等待测量完成
    
    uint8_t data[2];
    HAL_I2C_Master_Receive(&hi2c1, BH1750_ADDR, data, 2, 1000);
    
    uint16_t raw_data = (data[0] << 8) | data[1];
    float lux = (float)raw_data / 1.2;
    
    return lux;
}

4.2 WiFi数据传输实现

ESP8266 AT指令封装
// esp8266.h
typedef enum {
    ESP8266_OK = 0,
    ESP8266_ERROR,
    ESP8266_TIMEOUT
} ESP8266_Status;

ESP8266_Status ESP8266_Init(void);
ESP8266_Status ESP8266_ConnectWiFi(const char* ssid, const char* password);
ESP8266_Status ESP8266_ConnectTCP(const char* ip, uint16_t port);
ESP8266_Status ESP8266_SendData(const char* data, uint16_t len);

// esp8266.c
ESP8266_Status ESP8266_Init(void) {
    char response[100];
    
    // 测试AT指令
    ESP8266_SendCommand("AT", response, 1000);
    if(strstr(response, "OK") == NULL) {
        return ESP8266_ERROR;
    }
    
    // 设置模式为Station
    ESP8266_SendCommand("AT+CWMODE=1", response, 1000);
    if(strstr(response, "OK") == NULL) {
        return ESP8266_ERROR;
    }
    
    return ESP8266_OK;
}

ESP8266_Status ESP8266_ConnectWiFi(const char* ssid, const char* password) {
    char command[100];
    char response[200];
    
    sprintf(command, "AT+CWJAP=\"%s\",\"%s\"", ssid, password);
    ESP8266_SendCommand(command, response, 10000);  // 连接可能需要10秒
    
    if(strstr(response, "WIFI GOT IP") == NULL) {
        return ESP8266_ERROR;
    }
    
    return ESP8266_OK;
}

ESP8266_Status ESP8266_SendData(const char* data, uint16_t len) {
    char command[50];
    char response[50];
    
    // 发送数据长度
    sprintf(command, "AT+CIPSEND=%d", len);
    ESP8266_SendCommand(command, response, 1000);
    
    if(strstr(response, ">") == NULL) {
        return ESP8266_ERROR;
    }
    
    // 发送实际数据
    HAL_UART_Transmit(&huart3, (uint8_t*)data, len, 1000);
    
    // 等待发送完成
    ESP8266_SendCommand("", response, 2000);
    
    if(strstr(response, "SEND OK") == NULL) {
        return ESP8266_ERROR;
    }
    
    return ESP8266_OK;
}
数据打包与发送
// main.c 主循环示例
void main_loop(void) {
    static uint32_t last_send_time = 0;
    uint32_t current_time = HAL_GetTick();
    
    // 每5秒发送一次数据
    if(current_time - last_send_time >= 5000) {
        DHT11_Data_TypeDef dht11_data;
        float light_lux = BH1750_ReadLux();
        uint16_t soil_moisture = HAL_ADC_GetValue(&hadc1);
        
        if(DHT11_ReadData(&dht11_data) == HAL_OK) {
            // 构建JSON数据包
            char json_data[200];
            sprintf(json_data, 
                "{\"temp\":%.1f,\"hum\":%.1f,\"light\":%.1f,\"soil\":%d,\"time\":%lu}",
                dht11_data.temperature,
                dht11_data.humidity,
                light_lux,
                soil_moisture,
                current_time
            );
            
            // 发送数据
            if(ESP8266_SendData(json_data, strlen(json_data)) == ESP8266_OK) {
                printf("数据发送成功: %s\n", json_data);
            } else {
                printf("数据发送失败\n");
            }
        }
        
        last_send_time = current_time;
    }
}

4.3 Python上位机实现(效果图)

可视化界面展示

下图为本系统的上位机可视化界面,基于 PyQt5 和 pyqtgraph 实现,主要功能如下:

  • 系统状态:显示系统运行和WiFi连接状态。
  • 实时数据:动态显示温度、湿度、光照、土壤湿度等关键环境参数。
  • 设备控制:支持一键启动/关闭灌溉、通风、补光等功能。
  • 报警信息:实时推送环境异常报警,便于及时处理。
  • 环境参数趋势图:以曲线图方式直观展示各项环境参数的历史变化趋势。
  • 数据统计:自动统计并显示温湿度的平均、最高、最低值。

在这里插入图片描述

该界面极大提升了温室环境监控的可视化和交互体验,便于用户远程管理和数据分析。


MQTT客户端实现
# mqtt_client.py
import paho.mqtt.client as mqtt
import json
import sqlite3
from datetime import datetime

class GreenhouseMQTTClient:
    def __init__(self, broker="localhost", port=1883):
        self.client = mqtt.Client()
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.on_disconnect = self.on_disconnect
        
        self.broker = broker
        self.port = port
        self.connected = False
        
        # 数据库连接
        self.db_conn = sqlite3.connect('greenhouse.db')
        self.init_database()
    
    def init_database(self):
        cursor = self.db_conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS sensor_data (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                temperature REAL,
                humidity REAL,
                light REAL,
                soil_moisture INTEGER,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        self.db_conn.commit()
    
    def on_connect(self, client, userdata, flags, rc):
        print(f"连接到MQTT代理,返回码: {rc}")
        self.connected = True
        
        # 订阅主题
        client.subscribe("greenhouse/sensor_data")
        client.subscribe("greenhouse/control")
    
    def on_message(self, client, userdata, msg):
        try:
            if msg.topic == "greenhouse/sensor_data":
                data = json.loads(msg.payload.decode())
                self.save_sensor_data(data)
                print(f"收到传感器数据: {data}")
                
            elif msg.topic == "greenhouse/control":
                control_data = json.loads(msg.payload.decode())
                self.handle_control_command(control_data)
                
        except json.JSONDecodeError as e:
            print(f"JSON解析错误: {e}")
    
    def on_disconnect(self, client, userdata, rc):
        print("与MQTT代理断开连接")
        self.connected = False
    
    def save_sensor_data(self, data):
        cursor = self.db_conn.cursor()
        cursor.execute('''
            INSERT INTO sensor_data (temperature, humidity, light, soil_moisture)
            VALUES (?, ?, ?, ?)
        ''', (data['temp'], data['hum'], data['light'], data['soil']))
        self.db_conn.commit()
    
    def handle_control_command(self, control_data):
        if 'relay' in control_data:
            if control_data['relay'] == 'on':
                print("启动灌溉系统")
                # 这里可以发送控制指令到STM32
            elif control_data['relay'] == 'off':
                print("关闭灌溉系统")
    
    def connect(self):
        try:
            self.client.connect(self.broker, self.port, 60)
            self.client.loop_start()
        except Exception as e:
            print(f"连接失败: {e}")
    
    def disconnect(self):
        self.client.loop_stop()
        self.client.disconnect()
        self.db_conn.close()
PyQt5图形界面
# main_window.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import pyqtgraph as pg
from mqtt_client import GreenhouseMQTTClient

class GreenhouseMonitor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.mqtt_client = GreenhouseMQTTClient()
        self.init_ui()
        self.init_mqtt()
    
    def init_ui(self):
        self.setWindowTitle('温室环境监控系统')
        self.setGeometry(100, 100, 1200, 800)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建布局
        layout = QHBoxLayout()
        central_widget.setLayout(layout)
        
        # 左侧控制面板
        control_panel = self.create_control_panel()
        layout.addWidget(control_panel, 1)
        
        # 右侧数据显示
        data_panel = self.create_data_panel()
        layout.addWidget(data_panel, 2)
    
    def create_control_panel(self):
        panel = QWidget()
        layout = QVBoxLayout()
        panel.setLayout(layout)
        
        # 实时数据显示
        self.temp_label = QLabel('温度: --°C')
        self.hum_label = QLabel('湿度: --%')
        self.light_label = QLabel('光照: -- lux')
        self.soil_label = QLabel('土壤湿度: --')
        
        for label in [self.temp_label, self.hum_label, self.light_label, self.soil_label]:
            label.setStyleSheet("font-size: 16px; padding: 10px;")
            layout.addWidget(label)
        
        layout.addStretch()
        
        # 控制按钮
        self.irrigation_btn = QPushButton('启动灌溉')
        self.irrigation_btn.clicked.connect(self.toggle_irrigation)
        layout.addWidget(self.irrigation_btn)
        
        return panel
    
    def create_data_panel(self):
        panel = QWidget()
        layout = QVBoxLayout()
        panel.setLayout(layout)
        
        # 创建图表
        self.plot_widget = pg.PlotWidget()
        self.plot_widget.setBackground('w')
        self.plot_widget.showGrid(x=True, y=True)
        self.plot_widget.setLabel('left', '数值')
        self.plot_widget.setLabel('bottom', '时间')
        
        # 添加数据曲线
        self.temp_curve = self.plot_widget.plot(pen='r', name='温度')
        self.hum_curve = self.plot_widget.plot(pen='b', name='湿度')
        
        layout.addWidget(self.plot_widget)
        
        return panel
    
    def init_mqtt(self):
        self.mqtt_client.connect()
        
        # 定时更新界面
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_display)
        self.timer.start(1000)  # 每秒更新一次
    
    def update_display(self):
        # 从数据库获取最新数据
        cursor = self.mqtt_client.db_conn.cursor()
        cursor.execute('''
            SELECT temperature, humidity, light, soil_moisture 
            FROM sensor_data 
            ORDER BY timestamp DESC 
            LIMIT 1
        ''')
        
        result = cursor.fetchone()
        if result:
            temp, hum, light, soil = result
            
            self.temp_label.setText(f'温度: {temp:.1f}°C')
            self.hum_label.setText(f'湿度: {hum:.1f}%')
            self.light_label.setText(f'光照: {light:.1f} lux')
            self.soil_label.setText(f'土壤湿度: {soil}')
    
    def toggle_irrigation(self):
        if self.irrigation_btn.text() == '启动灌溉':
            self.irrigation_btn.setText('关闭灌溉')
            self.mqtt_client.client.publish('greenhouse/control', 
                json.dumps({'relay': 'on'}))
        else:
            self.irrigation_btn.setText('启动灌溉')
            self.mqtt_client.client.publish('greenhouse/control', 
                json.dumps({'relay': 'off'}))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = GreenhouseMonitor()
    window.show()
    sys.exit(app.exec_())

五、调试与优化

5.1 常见问题解决

ESP8266连接问题
# 测试AT指令
AT                    # 应该返回 OK
AT+CWMODE=1          # 设置Station模式
AT+CWLAP             # 扫描WiFi网络
AT+CWJAP="SSID","密码"  # 连接WiFi
AT+CIPSTART="TCP","192.168.1.100",8080  # 测试TCP连接
数据丢包问题
  • 增加ACK确认机制
// 发送数据后等待确认
ESP8266_Status ESP8266_SendWithAck(const char* data, uint16_t len) {
    ESP8266_Status status = ESP8266_SendData(data, len);
    if(status == ESP8266_OK) {
        // 等待确认包
        char response[50];
        if(ESP8266_WaitResponse(response, 2000) == ESP8266_OK) {
            if(strstr(response, "ACK") != NULL) {
                return ESP8266_OK;
            }
        }
    }
    return ESP8266_ERROR;
}

5.2 性能优化

低功耗优化
// 定时唤醒机制
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM2) {
        // 每5分钟唤醒一次
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        
        // 采集数据并发送
        collect_and_send_data();
        
        // 进入低功耗模式
        HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
    }
}
数据压缩
// 简单的数据压缩算法
uint16_t compress_sensor_data(DHT11_Data_TypeDef *dht11, float light, uint16_t soil) {
    uint16_t compressed = 0;
    
    // 温度: 0-50°C -> 0-255 (精度0.2°C)
    compressed |= (uint16_t)(dht11->temperature * 5) << 10;
    
    // 湿度: 0-100% -> 0-255 (精度0.4%)
    compressed |= (uint16_t)(dht11->humidity * 2.55) << 2;
    
    // 土壤湿度: 0-4095 -> 0-3 (12位转2位)
    compressed |= (soil >> 10) & 0x03;
    
    return compressed;
}

六、项目总结与扩展

6.1 项目成果

通过本项目的实施,成功实现了:

  1. 实时监控:温湿度、光照、土壤湿度等关键参数的实时采集和显示
  2. 远程控制:支持通过电脑远程控制灌溉系统
  3. 数据存储:历史数据本地存储,支持数据分析和趋势预测
  4. 报警功能:阈值超限自动报警提醒
  5. 低功耗:优化后的系统功耗显著降低

6.2 关键技术点

  • STM32 HAL库应用:熟练使用UART、I2C、ADC、PWM等外设
  • ESP8266 AT指令:掌握WiFi模块的配置和数据传输
  • MQTT协议:理解物联网通信协议的设计和实现
  • Python GUI开发:使用PyQt5构建用户友好的界面
  • 数据库操作:SQLite数据存储和查询

6.3 项目价值

本项目不仅实现了温室环境的智能化监控,更重要的是:

  1. 技术学习价值:涵盖了嵌入式开发、物联网通信、上位机开发等多个技术领域
  2. 实用价值:可直接应用于实际农业生产,提高管理效率
  3. 扩展价值:为后续的智慧农业项目奠定了技术基础
  4. 教育价值:适合作为物联网和嵌入式系统的学习项目

通过这个项目,我们不仅掌握了STM32、WiFi通信、Python开发等技术,更重要的是学会了如何将多种技术整合成一个完整的系统解决方案。这种系统集成能力在实际工作中具有非常重要的价值。


如果这篇文章对您有帮助,请点赞支持!如有问题或建议,欢迎在评论区交流。


网站公告

今日签到

点亮在社区的每一天
去签到