You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32结合BME280传感器控制线性执行器的代码优化方案

ESP32自动化园艺执行器控制代码优化方案

项目背景

基于ESP32开发板搭建自动化园艺控制系统,通过BME280传感器采集环境温度,控制线性执行器开合。当前代码可正常运行,但需完成两项优化:

  • 继电器闲置状态下自动断电,降低无用能耗,延长继电器、执行器元器件使用寿命
  • 将代码中所有阻塞式delay()函数替换为非阻塞millis()实现,避免主循环被卡住,方便后续扩展风扇、水阀、浮球开关等外设控制逻辑

现存问题

  1. 控制逻辑bug:当前逻辑设置温度高于27℃时执行器伸出,控制伸出的继电器通电7秒(匹配执行器全行程时长)后断电;但温度持续高于27℃时,循环逻辑会反复触发继电器通断,出现通电7秒、断电7秒的循环动作,无法保持继电器闲置断电的状态。此前尝试通过逻辑与运算符&&或analogRead()读取执行器状态做优化,但实现复杂度较高未落地。
  2. 阻塞运行问题:当前采用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:57:32