任务看门狗被触发 - 任务没有及时重置看门狗

Sam*_*Sam 10 webserver asynchronous task watchdog esp32

我正在尝试编写一个小型异步 WebServer。我简单描述一下这个场景:

我的 ESP32 也是一个路由器。因此,如果我用手机连接 ESP32 正在传播的 WiFi,并使用浏览器调用 IP 地址和特殊路径,就会发送一个网站。这里显示一个按钮。到目前为止,它运行得很好。现在,如果我单击该按钮,则会将 HTTPS Web 请求(方法:GET)发送到特殊机器。本机应答并返回 JSON。这可能会持续几秒钟。从 JSON 字符串中提取值后,应显示该值。

为了实现这一目标,我使用以下库:

我知道(通过另一个草图)最后三个可以正常工作。

不幸的是,当我单击该按钮时,以下输出出现在我的串行监视器上:

开始连接到服务器...
[HTTPS] 开始... 路径: https: //192.168.4.101/api/unlock/generate_pin
[HTTPS] GET...
E (137906) task_wdt:任务看门狗已触发。以下任务未及时重置看门狗:
E (137906) task_wdt: - async_tcp (CPU 0/1)
E (137906) task_wdt: 当前正在运行的任务:
E (137906) task_wdt: CPU 0: IDLE0
E (137906) task_wdt :CPU 1:loopTask
E(137906)task_wdt:正在中止。
abort() 在核心 0 上的 PC 0x400e08af 回溯上被调用

: 0x4008cc18:0x3ffbe170 0x4008ce49:0x3ffbe190 0x400e08af:0x3ffbe1b0 0x40084f21:0x3ffbe1d0 0x4016581b:0x3ffbc120 0x400e1c66:0x3ffbc140 0x4008ab21:0x3ffbc160 0x4008932d:0x3ffbc180 正在

重新启动...
ets Jun 8 2016 00:22:57

rst:0xc(SW_CPU_RESET),引导:0x17(SPI_FAST_FLASH_BOOT)
configsip:0,SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
模式:DIO,时钟div: 1
负载:0x3fff0018,len:4
负载:0x3fff001c,len:1044
负载:0x40078000,len:8896
负载:0x40080400,len:5816
条目 0x400806ac
串行初始完成

任何人都知道发生了什么以及如何解决这个问题?以便正确发送 GET 请求/收到应答?

我正在使用Heltec WiFi Kit 32

对每个答案都会非常高兴,提前致谢。

此致

PS:请让我最后添加我的代码:

#include <heltec.h>
#include "WiFi.h"
#include "ESPAsyncWebServer.h"

#include <WiFiClientSecure.h>
#include <HTTPClient.h>
 
const char* ssid = "MyWiFiSSID";
const char* password =  "MyWiFiPW";
 
AsyncWebServer server(80);

void setup() {

  Heltec.begin(true, false, true, true, 470E6);

  WiFi.softAP(ssid, password);
  
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AccessPoint IP address: ");
  Serial.println(IP);
  
  server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){
    
    request->send(200, "text/html", "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" charset=\"UTF-8\"><link rel=\"icon\" href=\"data:,\"><style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}.button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}</style></head><body><h1>Welcome to the Landing Page of the Web Server</h1><p><a href=\"/get_unlock_pin\"><button class=\"button\">Click Me</button></a></p></body></html>");
  });

  server.on("/get_unlock_pin", HTTP_GET, [](AsyncWebServerRequest *request){

    String firstpartofrawhtmlcode = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" charset=\"UTF-8\"><link rel=\"icon\" href=\"data:,\"><style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}.button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}</style></head><body><h2>Received Pin: </h2><h2 style=\"color: #FF0000\">";
    String receivedPin = getPin("192.168.4.101");
    String secondpartofrawhtmlcode = "</h2></body></html>";
    String fullrawhtmlcode;
    firstpartofrawhtmlcode = firstpartofrawhtmlcode.concat(receivedPin);
    fullrawhtmlcode = firstpartofrawhtmlcode.concat(secondpartofrawhtmlcode);
    request->send(200, "text/html", fullrawhtmlcode);
  });
 
  server.begin();
}

void loop() {

}

String getPin(String ip){
    Serial.println("\nStarting connection to server...");  
    WiFiClientSecure *wificlient = new WiFiClientSecure;

    HTTPClient https;
    https.setAuthorization("MyUserName", "MyPassword");

    String path = "https://" + ip + "/api/unlock/generate_pin";
      
    Serial.print("[HTTPS] begin... Path: " + path + "\n");
    if (https.begin(*wificlient, path)) { 
        Serial.print("[HTTPS] GET...\n");
        int httpCode = https.GET();
        if (httpCode > 0) {
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
  
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
            String payload = https.getString();
            Serial.println(payload);
            //Extract Pin from JSON
            String tmp = payload.substring(payload.indexOf(':'), payload.indexOf('}'));
            String tmp2 = tmp.substring(tmp.indexOf('"')+1,tmp.lastIndexOf('"')); 
            if(tmp2.substring(0,1) == "-"){
               return "-";
            }else{
               return tmp2;
            }              
          }
        } else {
               Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }  
        https.end();
    } else {
        Serial.printf("[HTTPS] Unable to connect\n");
    }
}
Run Code Online (Sandbox Code Playgroud)

mo *_*EAR 35

从问题的表述方式来看,我假设您不知道看门狗是什么,也不知道它为什么会触发。因此:

用于 ESP 的 Arduino 是在 ESP-IDF 上构建的,而 ESP-IDF 又是围绕 FreeRTOS 构建的。FreeRTOS 为每个核心创建一个 IDLE 任务。它还为这些任务设置看门狗定时器。这意味着如果这些任务缺乏执行时间,那么在超时后,看门狗就会被触发并重置芯片。IDLE 任务在后台执行一些重要的 FreeRTOS“家务”工作,因此您必须给它们时间。它们的优先级也是最低的。因此,如果任何任务以更高的优先级运行(例如回调),这些任务将始终首先运行,而 IDLE 任务则必须等待。因此,所有较高优先级的任务必须足够短,以避免触发看门狗。如果这不可行,则必须以足够的时间间隔插入暂停,例如通过调用 vTaskDelay(...) 或执行一些阻塞足够长的 IO 函数。无论哪种情况,当前任务都会进入睡眠状态,FreeRTOS 将启动另一个任务,如果没有其他更高优先级的任务在等待,它最终将允许 IDLE 任务执行。所有这一切意味着,通常您编写的任何代码都不应该在任何长于看门狗超时周期的时间内 100% 地占用 CPU。此外,您无法在 Arduino 中关闭此功能。它只能在 ESP-IDF 中进行配置,这意味着您必须在该框架内进行编码或自行重新编译 Arduino。此外,无论如何将其关闭也是一个坏主意。

在调用 vTaskDelay(...) 时必须使用至少 1 的值。由于整数算术和作为参数传递的各种表达式,它最终可能等于 0,即根本没有延迟。

如果您确实需要不间断地运行任务,则必须自行创建该任务并为其指定 tskIDLE_PRIORITY 优先级,例如:

xTaskCreate(someFunction, "HumanReadableNameofTask", 4096, NULL, tskIDLE_PRIORITY, NULL);

其中您的函数具有以下签名:

void someFunction(void* arg) {...}

您也可以在 Arduino 中执行此操作。因此,要么将回调中运行的代码保持在最低限度,要么将繁重的工作转移到单独的任务,并让回调简单地将任何相关信息转发到该任务(例如使用易失性变量和信号量,即通常的方法并行处理中的同步。这超出了本答案的范围)。

请注意,我不确定在 Arduino 中调用 delay(...) 是否具有与 vTaskDelay 相同的效果,但它可能具有相同的效果(有一些微妙之处)。另外,有一个任务可以“屈服于”另一个较低任务的官方方法,但我不确定细节。

重要提示:在回调(尤其是计时器回调)中使用延迟(例如 vTaskDelay(...))是一个坏主意,因为它们会阻止其他回调(在同一任务内)执行。因此,最好的选择是将信息传递给以空闲优先级(即优先级 0)运行的单独任务。以 0 优先级运行的任务不会触发看门狗的原因是,IDLE 任务也具有该优先级,并且 FreeRTOS 以相同优先级循环所有这些任务,即为它们提供交错的时间片以执行“平行线”。但是,当您有两个具有不同优先级的任务时,较高优先级的任务始终会执行,直到完成或停止/睡眠,只有那时较低优先级的任务才会运行,直到它本身完成,或者直到较高优先级的任务再次唤醒并要求执行时间。

更新:

将 Arduino Loop() 保持为空,肯定会触发 wdt,因为它并不是真正的“空”,因为 Loop() 函数内部包裹在无限循环中,因此 CPU 消耗实际上会在不执行任何操作的情况下飙升至 100%任何有用的东西。这就是触发看门狗的原因。GET 过程似乎是异步发生的,因此它不会阻止 Loop() 的执行。


Tar*_*rmo 5

ESPAsyncWebServer 回调可防止看门狗计时器在运行时重置。这意味着它们不适合进行任何实际处理。注册请求并将处理推迟到主循环(或其他线程)。看看这个问题的详细信息。