stm32duino/STM32FreeRTOS

Calling vTaskDelay in loop() leads to infamous vListInsert death spiral

cversek opened this issue · 3 comments

This problem took me a long time to figure out which line of code was responsible for my sketch hanging at a distant call.
Symptoms:

  • calling xQueueReceive on an empty queue with a finite timeout would cause a hanging of the kernel
  • debugging pointed to a forever loop in the function vListInsert (which is surprisingly common and can be caused by lots of things!)

Eventually I tracked it down to a line in the loop() function calling vTaskDelay

Now, since I thought loop is iterated by the Idle Task, shouldn't this be allowed?

I'm using an Adafruit STM32F405 Feather board, with setting:

#define configMEMMANG_HEAP_NB             3

Anyway, here is a simple sketch to reproduce the issue:

#include <STM32FreeRTOS.h>

QueueHandle_t  mQueue = NULL;

// HeartBeatTask, pulse the LED every couple of seconds
static void vHeartBeatTask(void* arg) {
  UNUSED(arg);
  pinMode(LED_BUILTIN, OUTPUT);

  while (1) {
    // Sleep for 2000 milliseconds.
    vTaskDelay((2000L * configTICK_RATE_HZ) / 1000L);
    // Turn LED on.
    digitalWrite(LED_BUILTIN, HIGH);
    // Sleep for 200 milliseconds.
    vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);
    // Turn LED off.
    digitalWrite(LED_BUILTIN, LOW);
  }
}

static void vQueueRecvTask(void* arg) {
  UNUSED(arg);
  uint8_t pkt;

  // Sleep for 5000 milliseconds.
  vTaskDelay(pdMS_TO_TICKS(5000));

  while (1) {
    // attempt to receive from queue
    Serial.println(F("# trying xQueueReceive"));
    if (xQueueReceive(mQueue,&pkt,pdMS_TO_TICKS(5000)) == pdTRUE){
      Serial.print(F("# recvd pkt: "));
      Serial.println(pkt);
    } else{ //xQueueReceive timed-out
      Serial.println(F("# xQueueReceive empty"));      
    }
    
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial){}; //WAIT FOR SERIAL CONNECTION TO OPEN, DEBUG ONLY!

  // queues
  mQueue = xQueueCreate(1,1);

  xTaskCreate(vHeartBeatTask,NULL,configMINIMAL_STACK_SIZE+50,NULL,1,NULL);
  xTaskCreate(vQueueRecvTask,NULL,configMINIMAL_STACK_SIZE+50,NULL,1,NULL);

  // start FreeRTOS
  vTaskStartScheduler();

  // should never return
  Serial.println(F("Die"));
  assert_param(false);
}

void loop() {
  // WARNING this seems to cause sketch to hang after xQueueReceive blocks
  vTaskDelay(pdMS_TO_TICKS(10));
}
fpistm commented

Why called it on loop?

Because I don't know any better. Let's say I wanted to blink an LED in the Idle task? Is blocking the Idle task bad form? Should that lead to possible corruption of the scheduler data?

fpistm commented

Well all is explained here:

#if ( configUSE_TICK_HOOK == 1 )
/** This function will be called by each tick interrupt if
configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h. User code can be
added here, but the tick hook is called from an interrupt context, so
code must not attempt to block, and only the interrupt safe FreeRTOS API
functions can be used (those that end in FromISR()). */
void __attribute__((weak)) vApplicationTickHook() {
}
#endif /* configUSE_TICK_HOOK == 1 */