Esp32 core0 watchdog error?

hello, my program uses core 1 and core 0 of the esp32. in short, core 1 is used to handle a bluetooth connection, and has been running fine with no errors.
I have began using core 0 to run some fairly blocking code(interfaceing with tft, when the device isnt used for <30s it displays some nice images) so i put in a lot of yields() to avoid blocking some of the important system tasks core 0 runs.

everything works for a bit until the esp crashes at any random time during usage by the user, with this error:

E (190368) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (190368) task_wdt:  - IDLE (CPU 0)
E (190368) task_wdt: Tasks currently running:
E (190368) task_wdt: CPU 0: Task2
E (190368) task_wdt: CPU 1: loopTask
E (190368) task_wdt: Aborting.

abort() was called at PC 0x400ea01d on core 0


Backtrace:0x400830d9:0x3ffbe7dc |<-CORRUPTED




ELF file SHA256: 0000000000000000

Rebooting...

This is my code:

const int dataPin = 25;   /* Q7 */
const int clockPin = 13;  /* CP */
const int latchPin = 14;  /* PL */
String keymap[] = {"56", "\\", "71", "esc", "70", "1", "69", "2", "68", "3", "63", "4", "62", "5", "61", "6", "60", "7", "55", "8", "54", "9", "53", "0", "52", "-", "47", "+", "46", "back", "45", "tab", "44", "q", "39", "w", "38", "e", "37", "r", "36", "t", "31", "y", "30", "u", "29", "i", "28", "o", "23", "p", "22", "[", "21", "]", "20", "enter", "15", "capslock", "14", "a", "13", "s", "12", "d", "7", "f", "6", "g", "5", "h", "4", "j", "58", "k", "59", "l", "50", ";", "57", "\'", "40", "lshift", "48", "z", "43", "x", "41", "c", "64", "v", "33", "b", "49", "n", "51", "m", "35", "<", "32", ">", "42", "/", "34", "rshift", "26", "lctrl", "25", "windows", "18", "lalt", "19", " ", "24", "options", "27", "ralt", "16", "rctrl", "17", "fn"};
TaskHandle_t Task1;
TaskHandle_t Task2;
const int bits = 72;   //8x number of registers e.g 2 registers = 16
#include <BleKeyboard.h>
#define KEY_ENTER 0xE0
BleKeyboard bleKeyboard;
#include <LittleFS.h>
#define FileSys LittleFS

// Include the PNG decoder library
#include <PNGdec.h>

PNG png;
#define MAX_IMAGE_WIDTH 240 // Adjust for your images

int16_t xpos = 0;
int16_t ypos = 0;

// Include the TFT library https://github.com/Bodmer/TFT_eSPI
#include "SPI.h"
#include <TFT_eSPI.h>              // Hardware-specific library
TFT_eSPI tft = TFT_eSPI();         // Invoke custom library
#define TFT_GREY 0x5AEB // New colour
#define B1 0x18E3  //button
#define B2 0x2965 //selected
#define B3 0x9CD3 //border

void setup() {
  delay(5000);
  Serial.begin(115200);
  if (!FileSys.begin()) {
    Serial.println("LittleFS initialisation failed!");
    while (1) yield(); // Stay here twiddling thumbs waiting
  }
  bleKeyboard.begin();
  tft.init();
  tft.setRotation(3);
  pinMode(dataPin, INPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  tft.fillScreen(TFT_RED);
  tft.setTextColor(TFT_WHITE, TFT_RED);
  tft.setCursor(30, 30);
  tft.setTextFont(4);
  File root = LittleFS.open("/", "r");
  File file = root.openNextFile();
  String strname = file.name();
  strname = "/" + strname;
  Serial.println(file.name());
  // If it is not a directory and filename ends in .png then load it
  if (!file.isDirectory() && strname.endsWith(".png")) {
    // Pass support callback function names to library
    int16_t rc = png.open(strname.c_str(), pngOpen, pngClose, pngRead, pngSeek, pngDraw);
    if (rc == PNG_SUCCESS) {
      tft.startWrite();
      Serial.printf("image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
      uint32_t dt = millis();
      if (png.getWidth() > MAX_IMAGE_WIDTH) {
        Serial.println("Image too wide for allocated line buffer size!");
      }
      else {
        rc = png.decode(NULL, 0);
        png.close();
      }
      tft.endWrite();
      // How long did rendering take...
      Serial.print(millis() - dt); Serial.println("ms");
    }
  }
  xTaskCreatePinnedToCore(
    Task1code,   /* Task function. */
    "Task1",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task1,      /* Task handle to keep track of created task */
    1);          /* pin task to core 0 */
  delay(500);
  xTaskCreatePinnedToCore(
    Task2code,   /* Task function. */
    "Task2",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    tskIDLE_PRIORITY,           /* priority of the task */
    &Task2,      /* Task handle to keep track of created task */
    0);          /* pin task to core 1 */
  delay(500);
}
String ios = ""; //in my case, strings fit better. recommend using array
String ios2 = "";
bool capslock = false;
bool menuopen = false;
int key = 0;
unsigned long idlestamp = millis();
void Task1code( void * pvParameters ) {
  // Step 1: Sample
  for (;;) {
    digitalWrite(latchPin, LOW);
    digitalWrite(latchPin, HIGH);

    // Step 2: Shift
    ios = "";
    for (int i = 0; i < bits; i++) {
      int bit = digitalRead(dataPin);
      if (bit == HIGH) {
        ios.concat("1");
      } else {
        ios.concat("0");
      }
      digitalWrite(clockPin, HIGH); // Shift out the next bit
      digitalWrite(clockPin, LOW);



    }
    for (int b = 0; b < ios.length(); b++) {
      if (ios[b] != ios2[b]) {
        if (ios[b] == '0') {
          Serial.println(b);
          key = b;
          for (int h = 0; h < 120; h = h + 2) {
            if (keymap[h] == String(b)) {
              String letter = keymap[h + 1];
              if (letter == "esc") {
                bleKeyboard.press(KEY_ESC);
              }
              else if (letter == "back") {
                bleKeyboard.press(KEY_BACKSPACE);
              }
              else if (letter == "tab") {
                bleKeyboard.press(KEY_TAB);
              }
              else if (letter == "enter") {
                bleKeyboard.press(KEY_ENTER);
              }
              else if (letter == "lshift") {
                bleKeyboard.press(KEY_LEFT_SHIFT);
              }
              else if (letter == "rshift") {
                bleKeyboard.press(KEY_RIGHT_SHIFT);
              }
              else if (letter == "lctrl") {
                bleKeyboard.press(KEY_LEFT_CTRL);
              }
              else if (letter == "rctrl") {
                bleKeyboard.press(KEY_RIGHT_CTRL);
              }
              else if (letter == "lalt") {
                bleKeyboard.press(KEY_LEFT_ALT);
              }
              else if (letter == "ralt") {
                bleKeyboard.press(KEY_RIGHT_ALT);
              }
              else if (letter == "windows") {
                bleKeyboard.press(KEY_LEFT_GUI);
              }
              else if (letter == "windows") {
                bleKeyboard.press(KEY_LEFT_GUI);
              }
              else if (letter == "options") {
                idlestamp = millis();
                menuopen = true;
              }
              else if (letter == "capslock") {
                if (capslock == false) {
                  capslock = true;
                }
                else {
                  capslock = false;
                }
              }
              else {
                if (capslock == true) {
                  letter.toUpperCase();
                  bleKeyboard.press(letter[0]);


                }
                else {
                  bleKeyboard.press(letter[0]);


                }

              }
            }
          }
        }
        else if (ios[b] == '1') {
          for (int h = 0; h < 120; h = h + 2) {
            if (keymap[h] == String(b)) {
              String letter = keymap[h + 1];
              if (letter == "esc") {
                bleKeyboard.release(KEY_ESC);
              }
              else if (letter == "back") {
                bleKeyboard.release(KEY_BACKSPACE);
              }
              else if (letter == "tab") {
                bleKeyboard.release(KEY_TAB);
              }
              else if (letter == "enter") {
                bleKeyboard.release(KEY_ENTER);
              }
              else if (letter == "lshift") {
                bleKeyboard.release(KEY_LEFT_SHIFT);
              }
              else if (letter == "rshift") {
                bleKeyboard.release(KEY_RIGHT_SHIFT);
              }
              else if (letter == "lctrl") {
                bleKeyboard.release(KEY_LEFT_CTRL);
              }
              else if (letter == "rctrl") {
                bleKeyboard.release(KEY_RIGHT_CTRL);
              }
              else if (letter == "lalt") {
                bleKeyboard.release(KEY_LEFT_ALT);
              }
              else if (letter == "ralt") {
                bleKeyboard.release(KEY_RIGHT_ALT);
              }
              else if (letter == "windows") {
                bleKeyboard.release(KEY_LEFT_GUI);
              }
              else if (letter == "windows") {
                bleKeyboard.release(KEY_LEFT_GUI);
              }
              else {
                if (capslock == true) {
                  letter.toUpperCase();
                  bleKeyboard.release(letter[0]);

                }
                else {
                  letter.toLowerCase();
                  bleKeyboard.release(letter[0]);

                }

              }
            }
          }
        }
        delay(70);//debounce
      }

    }


    ios2 = ios;
  }
}
const long interval = 50000;
int latch1 = 1;
int currentbutton = 1;
int screen = 1;
void os() {

}
void Task2code(void* arg) {
  for (;;) {

    if (millis() - idlestamp > interval) {
      Serial.println("idling");
      latch1 = 1;
      menuopen = false;
    }
    while (millis() - idlestamp > interval) {
      backdrop();
    }
    if (menuopen == true) {
      if (latch1 == 1) {
        tft.fillScreen(TFT_BLACK);
        latch1 = 0;
      }

      if (key == 8) {
        yield();
        Serial.println("cb:" + String(currentbutton));
        currentbutton = currentbutton + 1;
        if (currentbutton > 4) {
          currentbutton = 1;
        }
        if (screen == 1) {
          vTaskDelay(1);
          yield();
          tft.setTextColor(TFT_WHITE);
          if (currentbutton == 1) {
            tft.fillSmoothRoundRect(10, 20, 220, 30, 15, B2, TFT_BLACK);
            tft.fillRect(10, 35, 220, 35, B2);
          }
          else {
            tft.fillSmoothRoundRect(10, 20, 220, 30, 15, B1, TFT_BLACK);
            tft.fillRect(10, 35, 220, 35, B1);
          }
          tft.drawCentreString("macro's", 120, 28, 4);
          if (currentbutton == 2) {
            tft.fillRect(10, 70, 220, 50, B2);
          }
          else {
            tft.fillRect(10, 70, 220, 50, B1);
          }
          tft.drawCentreString("binds", 120, 82, 4);
          if (currentbutton == 3) {
            tft.fillRect(10, 120, 220, 50, B2);
          }
          else {
            tft.fillRect(10, 120, 220, 50, B1);
          }
          tft.drawCentreString("wallpaper", 120, 132, 4);
          if (currentbutton == 4) {
            tft.fillRect(10, 170, 220, 35, B2);
            tft.fillSmoothRoundRect(10, 190, 220, 30, 15, B2, TFT_BLACK);
          }
          else {
            tft.fillRect(10, 170, 220, 35, B1);
            tft.fillSmoothRoundRect(10, 190, 220, 30, 15, B1, TFT_BLACK);
          }
          tft.drawCentreString("settings", 120, 182, 4);
          yield();
          key = 0;
        }
      }


    }
    yield();
  }
}

void loop() {

}

void backdrop() {

  File root = LittleFS.open("/", "r");
  while (File file = root.openNextFile()) {
    String strname = file.name();
    strname = "/" + strname;
    Serial.println(file.name());
    // If it is not a directory and filename ends in .png then load it
    if (!file.isDirectory() && strname.endsWith(".png")) {
      // Pass support callback function names to library
      int16_t rc = png.open(strname.c_str(), pngOpen, pngClose, pngRead, pngSeek, pngDraw);
      if (rc == PNG_SUCCESS) {
        tft.startWrite();
        Serial.printf("image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
        uint32_t dt = millis();
        if (png.getWidth() > MAX_IMAGE_WIDTH) {
          Serial.println("Image too wide for allocated line buffer size!");
        }
        else {
          rc = png.decode(NULL, 0);
          png.close();
        }
        tft.endWrite();
        // How long did rendering take...
        Serial.print(millis() - dt); Serial.println("ms");
      }
    }
    int p = 0;
    while (p < 35000 && millis() - idlestamp > interval) {

      delay(1);
      p = p + 1;
      yield();
    }

  }
}
void pngDraw(PNGDRAW *pDraw) {
  uint16_t lineBuffer[MAX_IMAGE_WIDTH];
  png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
  tft.pushImage(xpos, ypos + pDraw->y, pDraw->iWidth, 1, lineBuffer);
}

Why do you pin your tasks to cores? Don't pin or pin both to core1.

so that i can have task1code running on core 1 and task2code running on core 0. correct me if i am misunderstanding

looking into the possibility of a resorce sharing conflict between the two cores

Nope. Exactly wrong. You're almost always better off controlling exactly where things run. Especially given the sensitivity of Core 0 to blocking ... the system Bluetooth runs on Core 0 ... as evidenced by the Core 0 WDT crashes. Core 1 likely has enough horse power to run both of @loveofsparks's tasks. Set them to equal priority and they'll timeshare on the 1ms FreeRTOS ticks.

1 Like

Yield() keeps your task in the state "Want to run". As long as something is in the state "Running" or "Wants to run" idle is not called. Maybe a few vTaskDelay(10) might help you.

For task synchronization take a look at the IPC stuff FreeRtos has to offer, e.g. xTaskNotifyWait()

Edit: BTW: your loop() is empty. This probably causes a high load looping around doing nothing. Send it to sleep with vTaskDelay().

idlestamp is used in both tasks. It probably should be volatile.

What happens when if(...) does not match but the while(...) later does?

void Task2code(void* arg) {
  for (;;) {

    if (millis() - idlestamp > interval) {
...
    }
    while (millis() - idlestamp > interval) {
...

As alluded to by gfvalvo, by default, the two cores have different watchdog configurations.

Core 0 has the task watchdog enabled in order to protect the WiFi service etc.
Core 1 does not, so that is the recommended place to run your code.

So what's wrong with moving obviously blocking tasks to core 1?

Pinning tasks should be reserved for people who know about ESP and FreeRTOS, not good for users who state:

and then wonder about watchdog errors.

Sorry, I've only recently started using esp32. Are you saying i could pin both tasks to core 1? Task2code is blocking, so would it not block task1code from running if they are both on the same core. Thats why i am currently running them on different cores to allow task1code to run uninterrupted(i know, bad practice having blocking code)

After adding some of your recommendations i have not had any crashes yet.
changed:


void Task2code(void* arg) {
  for (;;) {

    if (millis() - idlestamp > interval) {

         while (millis() - idlestamp > interval) {

         }
    }
}

Also added in vtaskdelays in the loop and code

That's still blocking code! On an ESP better use delay(x) ot vTaskDelay(x) to pause a task for a certain time without blocking.

I think the best is when Task2code is non-blocking and event driven. The base structure can be similar to this:

void Task2code(void* arg) {
  for (;;) {
    auto timeout;
    switch(displayMode){
      case Screensaver:
        timeout = timeoutImageFlip;
        break;
      case Menu:
        timeout = timeoutMenu;
        break;
    }
    
    uint32_t event;
    auto ret = xTaskNotifyWait(0, 0xffffffff, &event, timeout);
    if(ret == pdTRUE){
      handleEvent(event);
    } else {
      // handle timeout
      switch(displayMode){
        case Screensaver:
	      handleFlipImage();
  	      break;
        case Menu:
	      startScreensaver();
  	      break;
      }
    }
  }
}

Yes, of course. That's the whole point of the FreeRTOS operating system. It's a multi-tasking environment. But, it requires a different coding mindset.

The most rudimentary thing for you to try would be to just put both tasks on Core 1 with equal priority. And, make that priority > 1 so they're higher than the task running loop() since it doesn't contain useful code. With this setup (assuming you haven't disabled interrupts on Core 1), the two tasks will time share based on the FreeRTOS 1m Time Slice.

There are likely smarter ways of doing it. But, I don't have time to look at your code in detail. I suggest you spend some time learning about how FreeRTOS implements multi-tasking. Then you might try something along the lines of what @Rintin suggested.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.