ESP32 FreeRTOS Crash after suspend task

I've tried to dig up about this problem but have not found a solution.
I have ESP32 WRoom Dev Kit .
I used RTOS to run the main code on Core1 (in the main loop)
and another code run on Core0 as task (I also use Bluetooth classic too)
Core0 will automatically run after boot up so I set it to suspend with vTaskSuspend(TaskHandle0);
and let the Main code in Core1 run until meeting the condition.
I resume Task with vTaskResume(TaskHandle0), and it works well. It can run simultaneously with the main code.
after meeting another condition. I will suspend the task again. vTaskSuspend(TaskHandle0);
so... it's like I have the main code run on Core1 and turn on /off the task on Core0 as I want.
the point is when I Resume task and run for a while, Suspend task again and ESP32 crashed with the error
assert failed: xQueueGenericSend queue.c:832 (pxQueue->pcHead != ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle())

Backtrace: 0x400839b5:0x3ffdc0c0 0x400947a5:0x3ffdc0e0 0x4009a1b9:0x3ffdc100 0x4009525a:0x3ffdc230 0x400e6e31:0x3ffdc270 0x400dfdf6:0x3ffdc290 0x400dbe0a:0x3ffdc2b0 0x400df443:0x3ffdc310 0x400df51d:0x3ffdc360 0x400d71d7:0x3ffdc380 0x400d8411:0x3ffdc910 0x400e7b5a:0x3ffdc980

Can someone please explain what is wrong with esp32, and how to fix it?
Thank you.

Help us help you.

What is the result of putting the backtrace info into the ESP Exception decoder?

A failed assert sounds like an unexpected codeflow, like a code bug, not an exception they can handle.. This looks like a debug verision of RTOS? are there other macros to make it give more debug info in the Serial? Also post the (minimum repro) code maybe?

works in the emulator FreeRTOS - Wokwi ESP32, STM32, Arduino Simulator

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define BUTTON_PIN 15

// Declare a TaskHandle_t to manage the task state
TaskHandle_t TaskHandle_0;

void Core0Task(void* pvParameters) {
  // This task will run on core 0 and do nothing but delay itself.
  for (;;) {
    Serial.print("Task running on core ");
    Serial.println(xPortGetCoreID());
    delay(500); // Task action goes here.
  }
}

void setup() {
  // Initialize the button pin as an input.
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // Print the core number.
  Serial.begin(115200);
  while(!Serial);
  Serial.print("Setup running on core ");
  Serial.println(xPortGetCoreID());

  // Create a suspended task on core 0
  xTaskCreatePinnedToCore(
      Core0Task, /* Task function. */
      "Core0Task", /* name of task. */
      10000, /* Stack size of task */
      NULL, /* parameter of the task */
      1, /* priority of the task */
      &TaskHandle_0, /* Task handle to keep track of created task */
      0); /* pin task to core 0 */

  vTaskSuspend(TaskHandle_0); // Start the task suspended
}

void loop() {
  // Check for button press
  if (digitalRead(BUTTON_PIN) == LOW) {
    // Invert the state of the task on core 0
    if (eTaskGetState(TaskHandle_0) == eSuspended) {
      Serial.println("Resumed");
      vTaskResume(TaskHandle_0);
    } else {
      Serial.println("Suspended");
      vTaskSuspend(TaskHandle_0);
    }
    
    // Delay to debounce the button
    delay(200);
  }
}

my guess, you suspended a task that had ownership of a synchronization object semaphore, mutex..
ouch..

good luck.. ~q

1 Like

thank you for guide, this is my 1st time .

what is ESP Exception decoder? how to get that exception decoder.

thank you, I don't have any clue except the serial report error as I post above.
All the code is really long. so I shrink it like this.
Hardware: ESP32 + tft_espi touch screen+1 pin speaker
I want to display starfield animation on core0 while playing music from the main loop (core1)
(I can't play music and display Starfield animation on the same core because the performance will be very low, with bad music, and low FPS animation. so I separate tasks into core0)

//----------------------------
void TaskStarField(void *pvParameters) {
  esp_task_wdt_init(30, false);//disable wd
  za = random(256);
  zb = random(256);
  zc = random(256);
  zx = random(256);
  tft.fillScreen(TFT_BLACK);//clear screen
  for(;;) {
  uint8_t spawnDepthVariation = 127;//255
  for(int i = 0; i < NSTARS; ++i)  {
    if (sz[i] <= 1) {
      sx[i] = 160 - 120 + rng();
      sy[i] = rng();
      sz[i] = spawnDepthVariation--;
    } else {
      int old_screen_x = ((int)sx[i] - 160) * 256 / sz[i] + 160;
      int old_screen_y = ((int)sy[i] - 120) * 256 / sz[i] + 120;
      // This is a faster pixel drawing function for occassions where many single pixels must be drawn
      tft.drawPixel(old_screen_x, old_screen_y,TFT_BLACK);
      sz[i] -= 2;
      if (sz[i] > 1) {
        int screen_x = ((int)sx[i] - 160) * 256 / sz[i] + 160;
        int screen_y = ((int)sy[i] - 120) * 256 / sz[i] + 120;
  
        if (screen_x >= 0 && screen_y >= 0 && screen_x < 320 && screen_y < 240) {
          uint8_t r, g, b;
          r = g = b = 255 - sz[i];
          tft.drawPixel(screen_x, screen_y, tft.color565(r,g,b));
        } else sz[i] = 0; // Out of screen, die.
      }
    }
  }

  tft.setTextColor(TFT_YELLOW);
  tft.drawString("Firmware build date",0,37,2);
  tft.drawRightString(compile_date,319,37,2);
  tft.setTextColor(TFT_BLACK,TFT_WHITE);
  tft.drawCentreString("[- Press button to exit -]",159,180,2);
 }
}//task star field

void setup () {
 xTaskCreatePinnedToCore(
           &TaskStarField
           , "display star field animation with text"   // A name just for humans
           , 1024// This stack size can be checked & adjusted by reading the Stack Highwater
           , NULL// no variable passting to task
           , 0 //Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
           , &TaskHandle0
           , 0);//core0
 vTastSuspend(TaskHandle0);//stop task
}
//-----------------------
void loop() {

        vTaskResume(TaskHandle0);//start star field animation run on core 0
         player.play_nes(music , true, 0.5); //play nes music, loop, volume 0.5   //run on main loop core 1
           while (digitalRead(SELECTOR_PIN) == HIGH) { //wait for button press

         }//while
      vTaskSuspend(TaskHandle0);//start star field animation run on core 0
}

//Serial Error
assert failed: xQueueGenericSend queue.c:832 (pxQueue->pcHead != ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle())

Backtrace: 0x400839b5:0x3ffdc0c0 0x400947a5:0x3ffdc0e0 0x4009a1b9:0x3ffdc100 0x4009525a:0x3ffdc230 0x400e6e31:0x3ffdc270 0x400dfdf6:0x3ffdc290 0x400dbe0a:0x3ffdc2b0 0x400df443:0x3ffdc310 0x400df51d:0x3ffdc360 0x400d71d7:0x3ffdc380 0x400d8411:0x3ffdc910 0x400e7b5a:0x3ffdc980

Could u please explain more about "object semaphore" " mutex"
they are effect RTOS task? thank you.

Good questions to put to your internet search engine of choice.

When using freeRTOS on a ESP32 under the Arduino IDE, code in the loop() function may not ever run depending upon some factors such as priority and time slice availability. Also code in loop() while using freeRTOS tasks prevents the OS from doing house cleaning chores.

What does the ESP exception decoder report?

I believe you have an improperly initialized freeRTOS object.

Researching the error some..
Sounds like memory corruption of some kind..
Can you post the complete sketch please??

~q

I would try adding code little by little to the sketch in the emulator to see where it breaks. Or if you need a workaround you can use some bool variable to start/stop your task instead of actually suspending it.

I've tried creat task in main loop and after press button, I delete task ..
it give the same error.

I shorten the code and show below, I guess I know the cause of crash,
it's while(true) loop in play_animation() .

  • when I comment out // music can play and stop, but start field animation can play only 1 time and after delete task, it never run again after creat task.
  • when I included loop while(true) , it crased and return the error "assert bla bla bla"

so I 'm sure it's all about while(true) loop,

Full Sketch please download here -> freertos_starfield

snapshot -> Picture

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <TFT_eSPI.h>
#include <esp_task_wdt.h>
//Pin configuration
#define BUZZER_PIN 26//speaker
#define SELECTOR_PIN 27//push button
// setting PWM properties
#define backlightChannel 0
#define buzzerChannel 2
#include "music.h"
#include "nes_audio.h"

Cartridge player(BUZZER_PIN);//not specified 4 pin , use 1 pin at buzzer pin

// Use hardware SPI
TFT_eSPI tft = TFT_eSPI();
// With 1024 stars the update rate is ~65 frames per second
#define NSTARS 1024
uint8_t sx[NSTARS] = {};
uint8_t sy[NSTARS] = {};
uint8_t sz[NSTARS] = {};
uint8_t za, zb, zc, zx;

TaskHandle_t TaskHandle0 = NULL;

uint8_t rng() {
  zx++;
  za = (za^zc^zx);
  zb = (zb+za);
  zc = ((zc+(zb>>1))^za);
  return zc;
}

void TaskStarField(void *pvParameters)  // This is a task.
{
esp_task_wdt_init(30, false);
while(true) {
 unsigned long t0 = micros();
  uint8_t spawnDepthVariation = 255;
  for(int i = 0; i < NSTARS; ++i)
  {
    if (sz[i] <= 1)
    {
      sx[i] = 160 - 120 + rng();
      sy[i] = rng();
      sz[i] = spawnDepthVariation--;
    }
    else
    {
      int old_screen_x = ((int)sx[i] - 160) * 256 / sz[i] + 160;
      int old_screen_y = ((int)sy[i] - 120) * 256 / sz[i] + 120;

      // This is a faster pixel drawing function for occassions where many single pixels must be drawn
      tft.drawPixel(old_screen_x, old_screen_y,TFT_BLACK);

      sz[i] -= 2;
      if (sz[i] > 1)
      {
        int screen_x = ((int)sx[i] - 160) * 256 / sz[i] + 160;
        int screen_y = ((int)sy[i] - 120) * 256 / sz[i] + 120;
  
        if (screen_x >= 0 && screen_y >= 0 && screen_x < 320 && screen_y < 240)
        {
          uint8_t r, g, b;
          r = g = b = 255 - sz[i];
          tft.drawPixel(screen_x, screen_y, tft.color565(r,g,b));
        }
        else
          sz[i] = 0; // Out of screen, die.
      }
    }
  }

  tft.setTextColor(TFT_YELLOW);
  tft.drawCentreString("Star Field Animation",159,50,4);
  tft.drawFastHLine(0,80,320,TFT_RED);
  tft.setTextColor(TFT_BLACK,TFT_WHITE);
  tft.drawCentreString("[- Press button to exit -]",159,180,2);
 // Calcualte frames per second
   unsigned long t1 = micros();
  Serial.println(1.0/((t1 - t0)/1000000.0));
  }
}

void play_animation() {//control configuration menu
  while(true) { //when I uncomment while true, ESP32 crashed
    xTaskCreatePinnedToCore(//create task
    &TaskStarField
    ,  "TaskStarField"   // A name just for humans
    ,  1024  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  &TaskHandle0
    ,  0);
    
     player.play_nes(starwars , true, 0.5); //play nes music, loop, volume 0.5 
     //there is  if (digitalRead(27) == LOW) break; inside play_nes to exit from play
     //music loop in nes_audio.cpp
     vTaskDelete(TaskHandle0);//delete task.
  }
}



// the setup function runs once when you press reset or power the board
void setup() {
  
  za = random(256);
  zb = random(256);
  zc = random(256);
  zx = random(256);
   //pin configuration

  pinMode(BUZZER_PIN,OUTPUT);//speaker
  pinMode(SELECTOR_PIN,INPUT_PULLUP);//button

  //pwm setup
  ledcSetup(buzzerChannel, 1500, 10);//buzzer 10 bit
  ledcSetup(backlightChannel, 12000, 8);//backlight 8 bit
  ledcAttachPin(BUZZER_PIN, buzzerChannel);//attach buzzer

  Serial.begin(115200);
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setSwapBytes(true);
 }

void loop()
{
  if (digitalRead(SELECTOR_PIN) == LOW) {//button pressed  
      Serial.println("BEGIN");
      delay(200);//delay avoid bounce
      play_animation();//open config menu
      Serial.println("END");
      delay(200);//delay avoid bounce
    } 
}
  

I’m not sure about the cause of the assert, on which iteration of the loop does it fail? but mainly why do you create an and destroy a task in a while(true) loop? What is the idea behind it? Why not have the loop inside the task itself take care of repeating? Maybe instead of killing the task signal it to exit properly?

Actually, in While(true) loop, I have another code to be select and run with button pressed.
(like click button for next menu)
it's long sketch, like I have 7 menu, and TaskStarField is in the 6th menu. when I click button and the menu count to 6, then start the taskstarfield.
I removed unnecessary code to scope the problem. so it look like unreasonable to have while(true) loop, but it's for keep reading button press to jump to each menu.

the result is,
without while(true) - starfield animation run fine until I delete task, and the next time when I press button, task wont run. except song play run normally.

with while(true) - starfield animation run fine only 1 time as well. but after delete task, it crashed and throw "assert ..... error"

I have watch about rtos, not sure if I have to put samaphore , mutex in the code.
becoz I most of code run on core 0 ,(main loop) and Task Starfield animation will be run only for sometime when select the menu to play animation .

Do you mean you use this logic to create and destroy tasks for all your menu items? It just fails for this one? Maybe because it’s using TFT and you destroy it without proper clean up or something? Try exiting the task properly instead of terminating it

you are right. I've tried remove tft command after vTaskSuspend() and it works well. I can suspend task and resume it without problem.
but whenever I add tft command after suspend, it crashed.

I don't know why
you can test my sketch here https://www.dropbox.com/sh/t1tjdxd4vhehhph/AACdhtq1f-Wa9MDvbbYP596Fa?dl=0

I found this
Owner

Bodmer commented on Dec 27, 2021

You are instantiating two copies of the library so this will cause resource conflicts. You ust either use handshake semaphores to prevent both cores simultaneously trying to access the TFT or just run the TFT library on core 1.

I suggest you avoid RTOS tasks.