ESP32结合BME280传感器控制线性执行器的代码优化方案
ESP32自动化园艺执行器控制代码优化方案
项目背景
基于ESP32开发板搭建自动化园艺控制系统,通过BME280传感器采集环境温度,控制线性执行器开合。当前代码可正常运行,但需完成两项优化:
- 继电器闲置状态下自动断电,降低无用能耗,延长继电器、执行器元器件使用寿命
- 将代码中所有阻塞式
delay()函数替换为非阻塞millis()实现,避免主循环被卡住,方便后续扩展风扇、水阀、浮球开关等外设控制逻辑
现存问题
- 控制逻辑bug:当前逻辑设置温度高于27℃时执行器伸出,控制伸出的继电器通电7秒(匹配执行器全行程时长)后断电;但温度持续高于27℃时,循环逻辑会反复触发继电器通断,出现通电7秒、断电7秒的循环动作,无法保持继电器闲置断电的状态。此前尝试通过逻辑与运算符
&&或analogRead()读取执行器状态做优化,但实现复杂度较高未落地。 - 阻塞运行问题:当前采用
delay()实现7秒行程计时,逻辑简单但会阻塞整个程序运行,无法并行处理其他任务。
原有代码
#include <Adafruit_BME280.h> #include <WiFi.h> const char* ssid = "#####"; // ESP32 仅支持2.4G WiFi const char* password = "#####"; //MQTT 配置 #include <PubSubClient.h> #define mqtt_server "####.###.##.##" WiFiClient espClient; PubSubClient client(espClient); #define mqttActuator "growShed/Acuator" #define mqttTemp1 "growShed/temp1" int actuatorUp = 15; //执行器伸出控制GPIO int actuatorDown = 2; //执行器收回控制GPIO int actuatorUpread = (!digitalRead(actuatorUp)); //读取GPIO电平状态 int actuatorDownread = (!digitalRead(actuatorDown)); //读取GPIO电平状态 Adafruit_BME280 bme1; // I2C接口传感器 float temp1, hum1, pres1; //传感器数据上报延时控制 unsigned long millisNow1 = 0; unsigned int sendDelay1 = 2000; //MQTT上报间隔2秒 //执行器动作延时控制 unsigned long millisNow3 = 0; unsigned int sendDelay3 = 7000; //执行器全行程时长7秒 void setup() { Serial.begin(9600); Serial.println(); //WiFi连接 Serial.println("正在连接WiFi: "); Serial.println(ssid); WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(2000); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi连接成功"); Serial.println("局域网IP地址: "); Serial.println(WiFi.localIP()); client.setServer(mqtt_server, 1883); //BME280初始化 delay(5000); unsigned status; status = bme1.begin(0x76); if (!status) { Serial.println("未检测到BME280传感器,请检查接线、地址和传感器ID!"); Serial.print("传感器ID为: 0x"); Serial.println(bme1.sensorID(),16); Serial.print(" ID为0xFF通常表示地址错误,连接的是BMP180或BMP085\n"); Serial.print(" ID为0x56-0x58为BME280\n"); Serial.print(" ID为0x60为BME280\n"); Serial.print(" ID为0x61为BME680\n"); while (1); } //执行器初始化 pinMode(actuatorUp, OUTPUT); digitalWrite(actuatorDown, HIGH); //初始状态继电器断开 pinMode(actuatorDown, OUTPUT); digitalWrite(actuatorUp, HIGH); //初始状态继电器断开 } bool getValues() { temp1 = round(bme1.readTemperature()); //温度取整 actuatorUpread = (digitalRead(actuatorUp)); //继电器为低电平触发,做电平反转 Serial.print("BME1 温度 = "); Serial.print(temp1); Serial.println(" °C"); Serial.print("执行器伸出引脚状态 = "); Serial.println(actuatorUpread); Serial.print("执行器收回引脚状态 = "); Serial.print(actuatorDownread); Serial.println(); } void reconnect() { int counter = 0; while (!client.connected()) { if (counter==5){ ESP.restart(); } counter+=1; Serial.println("尝试连接MQTT服务器..."); if (client.connect("growTentController")) { Serial.println("连接成功"); } else { Serial.println("连接失败, 错误码="); Serial.println(client.state()); Serial.println("5秒后重试"); delay(5000); } } } void loop() { if (!client.connected()){ reconnect(); } if (millis() > millisNow1 + sendDelay1){ if (getValues()) { client.publish(mqttTemp1, String(temp1).c_str(),true); client.publish(mqttActuator, String(actuatorUpread).c_str(),true); millisNow1 = millis(); } } //执行器温度自动控制逻辑 temp1 = bme1.readTemperature(); if (millis() > millisNow3 + sendDelay3){ if ((temp1 > 27)){ digitalWrite(actuatorUp, LOW); delay(7000); //执行器全行程时长 digitalWrite(actuatorUp, HIGH); } else { digitalWrite(actuatorDown, LOW); delay(7000); //执行器全行程时长 digitalWrite(actuatorDown, HIGH); } millisNow3 = millis(); } }
优化实现方案
核心修改思路
通过状态标记记录执行器当前位置、运行状态,完全去掉阻塞延时,从根源避免重复触发动作:
- 新增全局状态变量:分别记录执行器运行状态(闲置/伸出中/收回中)、动作启动时间戳、执行器当前全开/全关位置、行程时长常量
- 动作触发逻辑加前置判断:只有当温度达到阈值、执行器当前位置和目标位置不一致、且执行器处于闲置状态时,才启动对应动作,触发后立刻记录时间戳、更新运行状态,不做任何延时等待
- 动作运行过程中,每次主循环都判断当前时间和启动时间的差值是否达到7秒,达到后立刻切断对应继电器电源,更新执行器位置标记,切回闲置状态
- MQTT重连逻辑中的阻塞延时也用同样的
millis()逻辑替换,彻底消除主循环阻塞
优化后核心控制逻辑片段
// 全局变量新增 const unsigned long ACTUATOR_RUN_TIME = 7000; byte actuatorState = 0; // 0:闲置 1:伸出中 2:收回中 unsigned long actionStartTime = 0; bool actuatorIsOpen = false; unsigned long mqttRetryTime = 0; const unsigned long MQTT_RETRY_DELAY = 5000; void loop() { unsigned long currentMillis = millis(); // MQTT非阻塞重连逻辑 if (!client.connected()){ if(currentMillis - mqttRetryTime >= MQTT_RETRY_DELAY){ mqttRetryTime = currentMillis; static int counter = 0; if (counter==5) ESP.restart(); counter+=1; if (client.connect("growTentController")) { Serial.println("MQTT连接成功"); counter = 0; } else { Serial.printf("MQTT连接失败,错误码%d,5秒后重试\n", client.state()); } } } // 传感器数据非阻塞上报 if (currentMillis - millisNow1 >= sendDelay1){ getValues(); client.publish(mqttTemp1, String(temp1).c_str(),true); client.publish(mqttActuator, String(actuatorUpread).c_str(),true); millisNow1 = currentMillis; } // 执行器非阻塞控制逻辑 temp1 = bme1.readTemperature(); switch(actuatorState) { case 0: // 闲置状态,判断是否需要触发动作 if(temp1 > 27 && !actuatorIsOpen) { // 需要打开,触发伸出动作 digitalWrite(actuatorUp, LOW); actionStartTime = currentMillis; actuatorState = 1; } else if(temp1 <= 27 && actuatorIsOpen) { // 需要关闭,触发收回动作 digitalWrite(actuatorDown, LOW); actionStartTime = currentMillis; actuatorState = 2; } break; case 1: // 伸出中,判断是否到位 if(currentMillis - actionStartTime >= ACTUATOR_RUN_TIME) { digitalWrite(actuatorUp, HIGH); // 切断继电器电源 actuatorIsOpen = true; actuatorState = 0; // 回到闲置 } break; case 2: // 收回中,判断是否到位 if(currentMillis - actionStartTime >= ACTUATOR_RUN_TIME) { digitalWrite(actuatorDown, HIGH); // 切断继电器电源 actuatorIsOpen = false; actuatorState = 0; // 回到闲置 } break; } }
注意:
setup()里的引脚初始化、BME280初始化、WiFi连接逻辑可以保留,直接替换原有loop()里的阻塞逻辑即可,替换后可实现继电器闲置自动断电、主循环无阻塞运行,也不会出现温度持续超阈值时反复触发继电器通断的问题。
内容的提问来源于stack exchange,提问作者NiphitAe
相关产品推荐
相关产品推荐

