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