ESP8266 随机软 WDT 复位

ESP8266 random Soft WDT resets

我使用带有 Bosche BME680 传感器防护罩的 WeMos D1 mini Pro。我从传感器获取数据并经常将它们放入 Firebase 数据库。一切正常,除了我的设备随机崩溃。

传感器库的工作方式是,大约前 5 分钟它不显示 IAQ 数据(它返回 iaq = 25 和 accuracy = 0)。 ESP8266 在工作程序的第 5 分钟左右崩溃 - 当 IAQ 数据可用并且已经进行了一些正确的读数时。

我认为问题可能是由 bsec_iot_loop() 工作时间过长引起的。我试图在 bsec_iot_loop() 的随机位置使用 yield(),但它没有用。当我注释掉 Firebase 设置方法时,程序运行完美。

我的大部分代码基于博世官方文档。坦率地说,它是扩展的复制粘贴。这是文档:https://www.bosch-sensortec.com/bst/products/all_products/bsec

代码如下:

/**********************************************************************************************************************/
/* header files */
/**********************************************************************************************************************/

#include "bsec_integration.h"
#include <Wire.h>
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <FirebaseArduino.h>
#include <time.h>

#define DEVICE_NAME "device1"

#define SSID "ssid"
#define PWD "pass"

#define FIREBASE_HOST "host"
#define FIREBASE_AUTH "auth"

#define UPDATE_INTERVAL 20

int startupTime;

/**********************************************************************************************************************/
/* functions */
/**********************************************************************************************************************/

/*!
 * @brief           Write operation in either Wire or SPI
 *
 * param[in]        dev_addr        Wire or SPI device address
 * param[in]        reg_addr        register address
 * param[in]        reg_data_ptr    pointer to the data to be written
 * param[in]        data_len        number of bytes to be written
 *
 * @return          result of the bus communication function
 */
int8_t bus_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
    Wire.beginTransmission((uint8_t) 0x77);
    Wire.write(reg_addr);    /* Set register address to start writing to */

    /* Write the data */
    for (int index = 0; index < data_len; index++) {
        Wire.write(reg_data_ptr[index]);
    }

    return (int8_t)Wire.endTransmission();
}

/*!
 * @brief           Read operation in either Wire or SPI
 *
 * param[in]        dev_addr        Wire or SPI device address
 * param[in]        reg_addr        register address
 * param[out]       reg_data_ptr    pointer to the memory to be used to store the read data
 * param[in]        data_len        number of bytes to be read
 *
 * @return          result of the bus communication function
 */
int8_t bus_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
    int8_t comResult = 0;
    Wire.beginTransmission((uint8_t) 0x77);
    Wire.write(reg_addr);                    /* Set register address to start reading from */
    comResult = Wire.endTransmission();

    delayMicroseconds(150);                 /* Precautionary response delay */
    Wire.requestFrom((uint8_t) 0x77, (uint8_t)data_len);    /* Request data */

    int index = 0;
    while (Wire.available())  /* The slave device may send less than requested (burst read) */
    {
        reg_data_ptr[index] = Wire.read();
        index++;
    }

    return comResult;
}

/*!
 * @brief           System specific implementation of sleep function
 *
 * @param[in]       t_ms    time in milliseconds
 *
 * @return          none
 */
void sleep(uint32_t t_ms)
{
    delay(t_ms);
}

/*!
 * @brief           Capture the system time in microseconds
 *
 * @return          system_current_time    current system timestamp in microseconds
 */
int64_t get_timestamp_us()
{
    return (int64_t) millis() * 1000;
}

/*!
 * @brief           Load previous library state from non-volatile memory
 *
 * @param[in,out]   state_buffer    buffer to hold the loaded state string
 * @param[in]       n_buffer        size of the allocated state buffer
 *
 * @return          number of bytes copied to state_buffer
 */
uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
{
    // ...
    // Load a previous library state from non-volatile memory, if available.
    //
    // Return zero if loading was unsuccessful or no state was available,
    // otherwise return length of loaded state string.
    // ...
    return 0;
}

/*!
 * @brief           Save library state to non-volatile memory
 *
 * @param[in]       state_buffer    buffer holding the state to be stored
 * @param[in]       length          length of the state string to be stored
 *
 * @return          none
 */
void state_save(const uint8_t *state_buffer, uint32_t length)
{
    // ...
    // Save the string some form of non-volatile memory, if possible.
    // ...
}

/*!
 * @brief           Load library config from non-volatile memory
 *
 * @param[in,out]   config_buffer    buffer to hold the loaded state string
 * @param[in]       n_buffer        size of the allocated state buffer
 *
 * @return          number of bytes copied to config_buffer
 */
uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer)
{
    // ...
    // Load a library config from non-volatile memory, if available.
    //
    // Return zero if loading was unsuccessful or no config was available,
    // otherwise return length of loaded config string.
    // ...
    return 0;
}

void connectToWiFi() {
  Serial.print("Connecting to ");
  Serial.println(SSID);

  WiFi.begin(SSID, PWD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void configureFirebase() {
  Serial.print("Connecting to ");
  Serial.println(FIREBASE_HOST);
  Serial.println("");

  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);

  delay(500);
}

void configureTime() {
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  Serial.println("\nWaiting for time");
  while (!time(nullptr)) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("");
}

void configureSensor() {
  return_values_init ret;

  /* Init I2C and serial communication */
  Wire.begin();

  /* Call to the function which initializes the BSEC library
   * Switch on low-power mode and provide no temperature offset */
  ret = bsec_iot_init(BSEC_SAMPLE_RATE_LP, 5.0f, bus_write, bus_read, sleep, state_load, config_load);
  if (ret.bme680_status)
  {
      /* Could not intialize BME680 */
      Serial.println("Error while initializing BME680");
      return;
  }
  else if (ret.bsec_status)
  {
      /* Could not intialize BSEC library */
      Serial.println("Error while initializing BSEC library");
      return;
  }

  Serial.println("Sensor success");
}

/*!
 * @brief           Handling of the ready outputs
 *
 * @param[in]       timestamp       time in nanoseconds
 * @param[in]       iaq             IAQ signal
 * @param[in]       iaq_accuracy    accuracy of IAQ signal
 * @param[in]       temperature     temperature signal
 * @param[in]       humidity        humidity signal
 * @param[in]       pressure        pressure signal
 * @param[in]       raw_temperature raw temperature signal
 * @param[in]       raw_humidity    raw humidity signal
 * @param[in]       gas             raw gas sensor signal
 * @param[in]       bsec_status     value returned by the bsec_do_steps() call
 *
 * @return          none
 */
void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, float temperature, float humidity,
     float pressure, float raw_temperature, float raw_humidity, float gas, bsec_library_return_t bsec_status)
{
    yield();
    char startupTimeStr[32];
    itoa(startupTime, startupTimeStr, 10);
    //Get current time
    time_t now = time(nullptr);

    //Get last update time
    int lastUpdate = Firebase.getInt("device1/lastUpdate");
    if (Firebase.failed()) {
      Serial.print("getting device1/lastUpdate failed:");
      Serial.println(Firebase.error());
      return;
    }

    if (lastUpdate + UPDATE_INTERVAL <= (int) now) {
      //Set last update
      Firebase.setInt("device1/lastUpdate", (int) now);

      //Set the reading
      char nowStr[32];
      itoa(now, nowStr, 10);
      String path = "device1/readings/" + String(nowStr);
      // Firebase.setInt(path + "/iaq", iaq);
      // Firebase.setFloat(path + "/temp", temperature);
      // Firebase.setFloat(path + "/humid", humidity);
      // Firebase.setFloat(path + "/press", pressure);

      //Set uptime
      int uptime = (int) now - startupTime;
      //Firebase.setInt("device1/uptimes/" + String(startupTimeStr), uptime);

      //Verbose data
      Serial.print("Updated: ");
      Serial.print((int) now);
      Serial.print(" | Uptime: ");
      Serial.print(uptime);
      Serial.print(" | IAQ: ");
      Serial.print(iaq);
      Serial.print(" | Acc: ");
      Serial.println(iaq_accuracy);
    }
}

void setup()
{
    Serial.begin(9600);
    while (!Serial);

    connectToWiFi();
    configureFirebase();
    configureTime();
    configureSensor();

    startupTime = (int) time(nullptr);
    Serial.print("Startup time:");
    Serial.println(startupTime);

    /* Call to endless loop function which reads and processes data based on sensor settings */
    /* State is saved every 10.000 samples, which means every 10.000 * 3 secs = 500 minutes  */
    bsec_iot_loop(sleep, get_timestamp_us, output_ready, state_save, 10000);
}

void loop()
{
}

这是典型的串行监视器转储的开始:

Soft WDT reset

ctx: cont
sp: 3fff0df0 end: 40101b51 offset: 01b0


>>>stack>>>
3fff0fa0:  3fff31f4 3fff70ec 3fff662c 3fff372c
3fff0fb0:  0002d5a7 3fff70ec 3fff662c 40208866
3fff0fc0:  3fff662c 00000000 3fff703c 40201952
3fff0fd0:  3fff703c 00001388 3fff3b04 3fff0680
3fff0fe0:  000001bb 3fff662c 3fff31f4 3fff0680
3fff0ff0:  000001bb 3fff662c 3fff31f4 402089fd
3fff1000:  3ffe9770 5561c923 3ffe9770 5561c923
3fff1010:  3fff367c 00000000 3fff3684 4020717c
3fff1020:  00000000 00000206 00000206 4020526c
3fff1030:  fffffff4 00000000 3fff3684 40207980
3fff1040:  3ffe9584 00000046 3ffe96a9 40201ff3
3fff1050:  3fff36fc 3fff10c0 3fff367c 4020204c
3fff1060:  3fff3708 00000000 00000000 3ffe96a6
3fff1070:  3fff367c 3fff10a0 3ffe96a6 3ffe96a6
3fff1080:  3fff367c 3fff0680 3fff1188 402030fa
3fff1090:  3fff0660 3fff0680 3fff1188 40203b71
3fff10a0:  3fff3708 3e9b1316 3e9b1316 3d003b33
3fff10b0:  41ad99fb bf64685f 00000000 40212b8c
3fff10c0:  3fff39d0 af3cd700 0000d700 00000012
3fff10d0:  00000000 3fff11ac 3fff1198 3fff065c
3fff10e0:  3fff1128 3fff1128 402030e4 40202032
3fff10f0:  3fff112c 40590000 3ed1ca3e 3fff0660
3fff1100:  3fff11ac 3fff1120 3fff1188 40203e0a
3fff1110:  3fff1120 40fb6e3e 3fff2180 40219384
3fff1120:  3fff367c 3fff3944 3fff0680 41ad99fb

我几乎可以肯定会发生这种情况,因为您的代码永远不会到达 loop() 函数,在 ESP8266 Arduino 库上,它会在每次循环函数交互时重置看门狗定时器。

我认为你可以通过两种方式解决你的问题,一种是打开函数 bsec_iot_loop() 并将调用放在 while(1)loop() 函数中,另一种选择是在 while(1) 中调用 ESP.wdtFeed() 来重置看门狗定时器。

下面的 link 对 ESP Arduino 库上的看门狗定时器有很好的解释。

https://techtutorialsx.com/2017/01/21/esp8266-watchdog-functions/

好的,所以我解决了这个问题。我怀疑看门狗定时器有问题。具体来说,代码永远不会到达 loop() 函数,因为 while(1)bsec_iot_loop() 中。解决方法很简单。我把while(1)里面的代码放到了loop()里面。代码的其余部分 bsec_iot_loop() 是变量声明,我将它们设为全局变量。

不释放系统资源是arduino库的通病。特别是在像 esp8266 这样的单核 CPU 系统上,系统没有可用的本机线程。底层 RTOS 需要处理时间来维护其 TCP 堆栈和其他内容。偶尔调用 yield() 不是一个可靠的解决方案。

esp8266 的 arduino 标准库主要由阻塞函数组成,这经常导致程序员陷入 WDT 陷阱。

我总是建议人们为 esp8266 使用异步库。有很多可用的。并且总是 return 尽可能多地将控制权交还给操作系统,因为即使是简单的延迟 () 调用也能够触发 WDT 复位。