一、项目概述
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开发环境
-
STM32CubeMX配置:
- 配置USART3用于ESP8266通信
- 配置I2C1用于BH1750
- 配置ADC1用于土壤湿度
- 配置定时器用于PWM输出
- 生成HAL库代码
-
开发工具:
- 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); // 拉低至少18msHAL_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 0x10float 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;}// 设置模式为StationESP8266_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 datetimeclass GreenhouseMQTTClient:def __init__(self, broker="localhost", port=1883):self.client = mqtt.Client()self.client.on_connect = self.on_connectself.client.on_message = self.on_messageself.client.on_disconnect = self.on_disconnectself.broker = brokerself.port = portself.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 = Falsedef 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("启动灌溉系统")# 这里可以发送控制指令到STM32elif 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 GreenhouseMQTTClientclass 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 paneldef 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 paneldef 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 = resultself.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 项目成果
通过本项目的实施,成功实现了:
- 实时监控:温湿度、光照、土壤湿度等关键参数的实时采集和显示
- 远程控制:支持通过电脑远程控制灌溉系统
- 数据存储:历史数据本地存储,支持数据分析和趋势预测
- 报警功能:阈值超限自动报警提醒
- 低功耗:优化后的系统功耗显著降低
6.2 关键技术点
- STM32 HAL库应用:熟练使用UART、I2C、ADC、PWM等外设
- ESP8266 AT指令:掌握WiFi模块的配置和数据传输
- MQTT协议:理解物联网通信协议的设计和实现
- Python GUI开发:使用PyQt5构建用户友好的界面
- 数据库操作:SQLite数据存储和查询
6.3 项目价值
本项目不仅实现了温室环境的智能化监控,更重要的是:
- 技术学习价值:涵盖了嵌入式开发、物联网通信、上位机开发等多个技术领域
- 实用价值:可直接应用于实际农业生产,提高管理效率
- 扩展价值:为后续的智慧农业项目奠定了技术基础
- 教育价值:适合作为物联网和嵌入式系统的学习项目
通过这个项目,我们不仅掌握了STM32、WiFi通信、Python开发等技术,更重要的是学会了如何将多种技术整合成一个完整的系统解决方案。这种系统集成能力在实际工作中具有非常重要的价值。
如果这篇文章对您有帮助,请点赞支持!如有问题或建议,欢迎在评论区交流。