Problem with Queue Set and xQueueOverwriteFromISR() on Cortex-M4

matsbygg wrote on Friday, February 02, 2018:

Hi!

MCU: STM32F411VET6 100 MHz
FreeRTOS 9.0.0 (same thing happens in 10.0.0)

I have a problem using xQueueOverwriteFromISR() with a queue set.
xQueueSelectFromSet() tells that an item is available, but when trying to read out the item, xQueueReceive() returns pdFALSE.

If I instead use xQueueSendToBackFromISR(), everything (seems to) work normally. But this is not quite the functionality I want.

Here is a longer description of the problem area in my system:

I have a “timestamp” queue (length = 1) which I write to every ~20 ms using xQueueOverwriteFromISR() from an interrupt handler.
I have a “command” queue (length = 10) which I occationally write commands to using xQueueSendToBack() from a “command” task (task priority 1).
I have a “receiver” task (task priority 4) that waits on both of these queues using a queue set, and reads out data from the corresponding queue.
After a while of successful operation, the queue set indicates that an item in the “timestamp” queue is available, but when calling xQueueReceive(), it returns failure.

I have tried to isolate the problem in a simple standalone test application (below), but not fully succeeded.
In the below test application, I also get wrong behaviour (I think), after about 6 seconds, but at a different location than in my real system. (See comment in code where it fails.)
Because of the task priorities, and the wait time specified in xQueueSendToBack(), the “command” queue should not get full, but it does!
And also in the test application, if I replace xQueueOverwriteFromISR() with xQueueSendToBackFromISR(), everything works normally.

Anyone else have problems with Queue Sets and xQueueOverwriteFromISR() ?

And yes, the system should be properly set-up with HAL_Init() that calls HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4).

#include "ClockTick.h"
#include "HWTimer.h"
#include "SystemCore.h"

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h"

QueueHandle_t command_queue;
QueueHandle_t timestamp_queue;
QueueSetHandle_t queue_set;

TaskHandle_t command_task_handle;
TaskHandle_t receiver_task_handle;


void hw_timer_callback(unsigned ch, void * user)  // IRQ priority 6
{
   BaseType_t higher_pri_task_woken = pdFALSE;
   int value = 0;
   
   // If I instead use xQueueSendToBackFromISR() here, everything works normally
   (void) xQueueOverwriteFromISR(timestamp_queue, &value, &higher_pri_task_woken);
   portYIELD_FROM_ISR(higher_pri_task_woken);
}


void command_task(void * param)  // task priority 1
{
   BaseType_t ret;
   int value = 0;

   for (;;)
   {
      vTaskDelay(20);
      
      ret = xQueueSendToBack(command_queue, &value, 5);

      // **** This assert fails after a while in this test application *****
      configASSERT(ret == pdTRUE);
   }
}


void receiver_task(void * param)   // task priority 4
{
   BaseType_t ret;
   int value;

   // Start recurring HW-timer (330 us timeout)
   StartRecurringHWTimer(0, ClockTicksFrom_us(330), hw_timer_callback, NULL);

   for (;;)
   {
      // Wait for command or timestamp
      QueueSetMemberHandle_t handle = xQueueSelectFromSet(queue_set, 200);
      
      if (handle == (QueueSetMemberHandle_t) timestamp_queue)
      {
         ret = xQueueReceive(timestamp_queue, &value, 0);

         // **** This assert fails after a while in my real system *****
         configASSERT(ret == pdTRUE);
      }
      else if (handle == (QueueSetMemberHandle_t) command_queue)
      {
         ret = xQueueReceive(command_queue, &value, 0);
         configASSERT(ret == pdTRUE);
      }
      else
      {
         configASSERT(handle == NULL);
      }
   }
}


int main(void)
{
   // Init system: HAL, clocks, etc.
   ErrorHandler_Init();
   SystemCore_Init();
   ClockTick_Init();
   HWTimer_Init();


   // Create queues
   command_queue = xQueueCreate(10, sizeof(int));
   timestamp_queue = xQueueCreate(1, sizeof(int));

   // Create queue sets
   queue_set = xQueueCreateSet(10 + 1);
   xQueueAddToSet((QueueSetMemberHandle_t)command_queue, queue_set);
   xQueueAddToSet((QueueSetMemberHandle_t)timestamp_queue, queue_set);

   // Create command and receiver tasks
   xTaskCreate(command_task, "CMD", 3 * configMINIMAL_STACK_SIZE, NULL, 1, &command_task_handle);
   xTaskCreate(receiver_task, "RCV", 3 * configMINIMAL_STACK_SIZE, NULL, 4, &receiver_task_handle);
   
   vTaskStartScheduler();

   return 0;
}

/*
#define configUSE_PREEMPTION                       1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION    1
#define configUSE_TICKLESS_IDLE                    0
#define configCPU_CLOCK_HZ                         ( SystemCoreClock )
#define configTICK_RATE_HZ                         ( ( TickType_t ) 1000 ) /* Note! 1000 Hz is MAX, as HAL tick & FreeRTOS shares SysTick_Handler() */
#define configUSE_16_BIT_TICKS                     0
#define configMAX_PRIORITIES                       5
#define configUSE_TIME_SLICING                     1
#define configIDLE_SHOULD_YIELD                    1
#define configMINIMAL_STACK_SIZE                   ( ( unsigned short ) 130 )
#define configMAX_TASK_NAME_LEN                    10
#define configUSE_TASK_NOTIFICATIONS               0
#define configUSE_MUTEXES                          1
#define configUSE_RECURSIVE_MUTEXES                1
#define configUSE_COUNTING_SEMAPHORES              0
#define configUSE_QUEUE_SETS                       1
#define configUSE_ALTERNATIVE_API                  0
#define configQUEUE_REGISTRY_SIZE                  8
#define configUSE_NEWLIB_REENTRANT                 1  /* Integrating with Newlib & Newlib-Nano */
#define configENABLE_BACKWARD_COMPATIBILITY        0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS    0
#define configUSE_APPLICATION_TASK_TAG             0

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY  5
*/

rtel wrote on Friday, February 02, 2018:

If a queue is in a queue set, and data is written into that queue, then
the address of the queue is written into the queue set. When you read
from the queue set you obtain that address and then read from the queue.
There is therefore an assumption of a one to one relationship between
the number of items in the individual queues and the number of items in
the queue set. If you were to use a queue overwrite to add something to
a queue all should be good, if however you then used a queue overwrite
to overwrite the item already in the queue then the queue set is going
to expect to items to be in the queue, whereas in fact there would be
only one - a second read of the queue would then fail (as there would
have only been one item in it).

Do you think this is the scenario you are observing?

matsbygg wrote on Monday, February 05, 2018:

If a queue is in a queue set, and data is written into that queue, then
the address of the queue is written into the queue set. When you read
from the queue set you obtain that address and then read from the queue.

Yes, OK.

There is therefore an assumption of a one to one relationship between
the number of items in the individual queues and the number of items in
the queue set.

So that is why I should sum the lengths of all queues and pass that value to xQueueCreateSet() ?

If you were to use a queue overwrite to add something to
a queue all should be good,

Because that would be the same as a successful xQueueSendToBack()… ?

if however you then used a queue overwrite
to overwrite the item already in the queue then the queue set is going
to expect to items to be in the queue, whereas in fact there would be
only one - a second read of the queue would then fail (as there would
have only been one item in it).

So in my case, where I have setup the queue set with xQueueCreateSet(10 + 1), there will in this senario be two addresses stored in the queue set, pointing to the queue which have a length of only 1 ?

Isn’t this a bug in FreeRTOS ?

Based on this, I have now managed to reproduce it with the simple test application, and I think what you are describing is exactly what is happening.
Some of the commands take time to execute by the “receiver” task, therefore the “timestamp” queue will be overwritten several times until the “receiver” task has time to check the queue set.

I tried to replace the timestamp queue with a binary semaphore. The problem then dissapeared, however, with a binary semaphore, I must then pass the actual data (a timestamp value) through a separate volatile variable, and that is not as clean design as using a queue…

rtel wrote on Wednesday, February 07, 2018:

I’ve just added an assert to catch that scenario.

matsbygg wrote on Wednesday, February 07, 2018:

So this scenario is not supposed to work?