FreeRTOS Queue Management

Inter-Process Communication

In bare-metal development, two applications typically use global variables for message passing. However, in applications using an operating system, using global variables for message passing involves “resource management” issues. FreeRTOS implements message passing between tasks and between tasks and interrupts through a mechanism called “queues”.

Queue Management

Queues generally use a first-in-first-out (FIFO) storage buffer mechanism (unidirectional tunnel), but last-in-first-out (LIFO) storage buffers can also be used. In FreeRTOS, queues default to FIFO, while LIFO (similar to stacks) is encapsulated in the implementation.

Comparison of Message Queue Mechanisms in FreeRTOS and uCOS

uCOS message queues (pass by reference)

Only message pointers (addresses) are passed, without copying the actual data, saving time and memory. Limitations: The message memory must remain valid for a long time (e.g., global variables), while local variables cannot be used due to their short lifecycle.

FreeRTOS default mechanism (pass by value)

Data is fully copied to the queue, storing the original value rather than a pointer. Advantages: The original buffer can be immediately reused or released after sending (e.g., local variable data safety). Cost: Large data copies may take time, affecting real-time performance. Flexibility: Supports pointer passing, i.e., directly sending the value of pointer variables to the queue.

Example: xQueueSend(queue, &data_ptr, timeout), where data_ptr points to a large block of data. Suitable for transferring large data (e.g., network packets), avoiding copy overhead.

FreeRTOS Queues

  • • Queue classification (task-level, interrupt-level ISR) subdivided based on semaphore classification.
  • • Queue creation (dynamic/static structures).
  • • Operations on queues (enqueue, dequeue, lock, unlock, clear, delete…).

Blocking Time (during enqueue and dequeue) is measured in clock ticks.

The time a task is blocked when it fails to read a message from the queue. Dequeueing refers to reading a message from the queue, and dequeue blocking refers to the task that is reading a message from the queue.Example: Task A (processing data received from the serial port) will place the received data into queue Q. Task A reads data from queue Q.

If queue Q is empty at this time, with no data, Task A will not be able to retrieve anything.

Task A now has three choices: One: exit directly. Two: wait, as there may be data soon. Three: wait indefinitely.

Which option to choose is determined by this blocking time.

FreeRTOS Queue Management

Experiment

A multitasking system simulating the process of “data collection -> data processing -> data reporting” (serial port logging).

Experiment design: Create three tasks:Task_Sensor: Simulates sensor data collection.Priority: Medium (taskIDLE_PRIORITY + 2)Behavior: Every 500ms, generates a simulated sensor data packet (structure containing timestamp, temperature, humidity, etc.), sending the structure data to a queue named sensor_data_queue.

Task_Process: Simulates data processing and decision-making.Priority: Medium (taskIDLE_PRIORITY + 2)Behavior: Permanently blocks waiting for sensor_data_queue. Once data is received, checks if the temperature exceeds the threshold; if exceeded, constructs an alarm message and sends it to another queue named alarm_command_queue.

Task_Report: Simulates data/alarm reporting.Priority: High (taskIDLE_PRIORITY + 3)Behavior: Waits for alarm_command_queue with a timeout of 1 second; if an alarm message is received, prints “ALERT! High temperature detected!” to the serial port; if it times out, prints “System Normal.” to the serial port.

Flowchart

FreeRTOS Queue Management
FreeRTOS Queue Management
FreeRTOS Queue Management
FreeRTOS Queue Management

IDE Configuration

sys SW, change HAL time base source to a basic timer (RTOS will occupy SysTick).clock HSE (external crystal 24M) clock tree configuration.serial port asynchronous.freertos select CMSIS_V2.1 Total memory size set to 3kb.2 Clock ticks 1000, i.e., 1ms.3 Advanced settings USE_NEWLIB_REENTRANT changed to Enabled.

The USE_NEWLIB_REENTRANT option ensures that the newlib library is fully reentrant (multiple tasks calling simultaneously without errors), which will increase RAM usage.Newlib is a commonly used implementation of the standard C library (libc) in embedded systems, providing functions like printf, malloc, strcpy, etc.

Example: If Task A calls printf and is switched to Task B while printing, and Task B also calls printf, if printf is not reentrant, its internal static or global variables will be disrupted by Task B, leading to output confusion when switching back to Task A.

Enabling the USE_NEWLIB_REENTRANT option allocates a small amount of extra memory for newlib in each task’s TCB (Task Control Block) to store its own context. This way, when tasks switch, printf and other functions use their respective task contexts, preventing interference and achieving “reentrancy”.

When using the GCC toolchain in CubeIDE, this must be enabled, but it is not required for Keil’s armcc (with Microlib).

FreeRTOS Queue Management

Core Code

#include "main.h"
#include "cmsis_os.h"
#include "usart.h"
#include "gpio.h"


/* USER CODE BEGIN PV */


// Sensor data structure
typedef struct {
  uint32_t timestamp;
  float temperature;
  float humidity;
} SensorData_t;

// Message queue handles
osMessageQueueId_t sensor_data_queueHandle;
osMessageQueueId_t alarm_command_queueHandle;

// Task handles 
osThreadId_t task_sensorHandle;
osThreadId_t task_processHandle;
osThreadId_t task_reportHandle;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);

/* USER CODE BEGIN PFP */
void Start_Task_Sensor(void *argument);
void Start_Task_Process(void *argument);
void Start_Task_Report(void *argument);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

#include <stdio.h>

// Redirection
#ifdef __GNUC__
  /* With GCC, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART1 and Loop until the end of transmission */
  HAL_UART_Transmit(&amp;huart1, (uint8_t *)&amp;ch, 1, 0xFFFF); // 这里的huart1要和你配置的串口对应
  return ch;
}


int main(void)
{

  HAL_Init();


  /* Configure the system clock */
  SystemClock_Config();


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN 2 */
  // Sensor data queue
  //  Queue length 16, element size attributes
  sensor_data_queueHandle = osMessageQueueNew(16, sizeof(SensorData_t), NULL);

  // Alarm command queue
  alarm_command_queueHandle = osMessageQueueNew(8, sizeof(int), NULL);

  // Create tasks
  // osThreadNew(task function, task parameters, task attribute pointer)
  const osThreadAttr_t task_sensor_attributes = {
    .name = "TaskSensor",
    .stack_size = 128 * 4, // 128 words = 512 bytes
    .priority = (osPriority_t) osPriorityNormal,
  };
  task_sensorHandle = osThreadNew(Start_Task_Sensor, NULL, &amp;task_sensor_attributes);

  const osThreadAttr_t task_process_attributes = {
    .name = "TaskProcess",
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityNormal,
  };
  task_processHandle = osThreadNew(Start_Task_Process, NULL, &amp;task_process_attributes);

  const osThreadAttr_t task_report_attributes = {
    .name = "TaskReport",
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityHigh, // Set to high priority
  };
  task_reportHandle = osThreadNew(Start_Task_Report, NULL, &amp;task_report_attributes);


  printf("All tasks and queues created successfully!\r\n");


  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* Call init function for freertos objects (in cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

// This point will not be reached, FreeRTOS takes control

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV3;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&amp;RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&amp;RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */


// Simulated sensor data collection task
void Start_Task_Sensor(void *argument)
{
  SensorData_t sensor_data;
  uint32_t counter = 0;

  for(;;) // Task main loop
  {
    // Simulate data generation
    sensor_data.timestamp = counter++;
    sensor_data.temperature = 25.0f + (counter % 10); 
    
    sensor_data.humidity = 60.0f;
    printf("Sensor Task: Generated data, Temp=%.1f\r\n", sensor_data.temperature);

    // Send data to queue
    // osMessageQueuePut(queue handle, data pointer, message priority (usually 0), timeout)
    osStatus_t status = osMessageQueuePut(sensor_data_queueHandle, &amp;sensor_data, 0, 100); // Timeout 100ms
    if (status != osOK)
    {
      printf("Sensor Task: Failed to send to queue!\r\n");
    }

    // 3. Block for 500ms
    osDelay(500);
  }
}

// Data processing task
void Start_Task_Process(void *argument)
{
  SensorData_t received_data;
  int alarm_command = 1; // 1 represents alarm

  for(;;)
  {
    // Receive data from queue, permanently block
    // osMessageQueueGet(queue handle, data storage address, message priority pointer (usually NULL), timeout)
    osStatus_t status = osMessageQueueGet(sensor_data_queueHandle, &amp;received_data, NULL, osWaitForever);

    if (status == osOK)
    {
      printf("Process Task: Received data, Temp=%.1f\r\n", received_data.temperature);

      // Process data: Check if temperature exceeds limit
      if (received_data.temperature > 30.0f)
      {
        printf("Process Task: Temperature exceeds threshold! Sending alarm.\r\n");
        // Send alarm command to another queue
        osMessageQueuePut(alarm_command_queueHandle, &amp;alarm_command, 0, 0); // Non-blocking send
      }
    }
  }
}

// Reporting task
void Start_Task_Report(void *argument)
{
  int received_command;

  for(;;)
  {
    // Receive data from alarm queue, timeout 1 second
    osStatus_t status = osMessageQueueGet(alarm_command_queueHandle, &amp;received_command, NULL, 1000);

    if (status == osOK)
    {
      // Received alarm
      if (received_command == 1)
      {
        printf("!!! REPORT TASK: ALERT! High temperature detected! !!!\r\n\r\n");
      }
    }
    else if (status == osErrorTimeout)
    {
      // If timeout
      printf("Report Task: System Normal.\r\n\r\n");
    }
  }
}


/* USER CODE END 4 */

/**
  * @brief  Period elapsed callback in non-blocking mode
  * @note   This function is called when TIM6 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM6) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Serial Port Log

FreeRTOS Queue Management

Handling Full/Empty Queues:

In Task_Sensor, use xQueueSend instead of xQueueSendToBack and set the blocking time to 0. Then deliberately slow down Task_Process (after processing + vTaskDelay(2000)) to observe what xQueueSend returns when the queue is full, and print the error message “Queue is full!”.

<span>Task_Sensor</span>‘s production speed (one every 500ms) far exceeds <span>Task_Process</span>‘s processing speed (one every 2000ms), causing <span>sensor_data_queue</span> to fill up.

Modify the two task functions:<span>Start_Task_Sensor</span> and <span>Start_Task_Process</span>.

<span>Start_Task_Sensor</span> will no longer wait when sending data and will check the sending result.

void Start_Task_Sensor(void *argument)
{
  SensorData_t sensor_data;
  uint32_t counter = 0;

  for(;;)
  {
    sensor_data.timestamp = counter++;
    sensor_data.temperature = 25.0f + (counter % 10);
    sensor_data.humidity = 60.0f;
    printf("[Sensor Task]: Generating data, Temp=%.1f\r\n", sensor_data.temperature);

    // (Modification point)
    // Use osMessageQueuePut and set timeout to 0 (non-blocking)
    osStatus_t status = osMessageQueuePut(sensor_data_queueHandle, &amp;sensor_data, 0, 0);
    
    // Check sending status 
    if (status == osOK)
    {
      // Sending successful
      printf("[Sensor Task]: Successfully sent data to queue.\r\n");
    }
    else if (status == osErrorResource)
    {
      // osErrorResource corresponds to FreeRTOS's errQUEUE_FULL
      // The queue is full, sending failed
      printf("[Sensor Task]: !!! QUEUE IS FULL! Data discarded. !!!\r\n");
    }
    else
    {
      // Other possible errors
      printf("[Sensor Task]: An unexpected error occurred while sending.\r\n");
    }

    // Block for 500ms, maintaining production speed
    osDelay(500);
  }
}

<span>osMessageQueuePut()</span> is equivalent to <span>xQueueSend()</span> or <span>xQueueSendToBack()</span><span> in the CMSIS-V2 API.</span><code><span>osMessageQueuePut</span>‘s last parameter <span>timeout</span> changed from <span>100</span> to <span>0</span>.

Check the return value of <span>osMessageQueuePut</span> for <span>status</span>.

  • <span>osOK</span>: Indicates successful sending.
  • <span>osErrorResource</span>: This is a CMSIS-V2 error code when the underlying FreeRTOS returns <span>errQUEUE_FULL</span> (queue full) or <span>errQUEUE_EMPTY</span> (queue empty).

Modify <span>Start_Task_Process</span> to make it process very slowly

  osDelay(2000); // After processing one data, rest for 2 seconds!

Queue Operations in ISR (Interrupt -> Task Communication)

  • • In the previous code, we have been using the CMSIS-OS v2 API (osMessageQueuePut), which provides a layer of standard abstraction for different RTOS kernels. In interrupt service routines, to handle complex logic related to task scheduling (xHigherPriorityTaskWoken), sometimes it is necessary to use the lower-level native FreeRTOS API. In CubeMX generated projects, these two sets of APIs can be mixed.

For safety, the interrupt priority must be higher than configMAX_SYSCALL_INTERRUPT_PRIORITY, which defaults to 5.In ARM Cortex-M, the lower the value, the higher the priority.

FreeRTOS Queue Management
/* USER CODE BEGIN 4 */

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  // External interrupt triggered by button pin
  if(GPIO_Pin == GPIO_PIN_13) // Assume
  {

    BaseType_t xHigherPriorityTaskWoken = pdFALSE; // Initialize to false

    // FromISR version of API
    BaseType_t xResult = xQueueSendFromISR(
        alarm_command_queueHandle,      // Target queue
        &amp;emergency_alarm_command,       // Address of the data to send
        &amp;xHigherPriorityTaskWoken       // Pointer to receive "whether a higher priority task was woken"
    );


    if (xResult == pdPASS)
    {
      // Sending successful
    }
    else
    {
      // Queue is full
    }
    
     //Key!!!
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

  }
}
/* USER CODE END 4 */

If xHigherPriorityTaskWoken is set to pdTRUE, it indicates that a task has been woken up, and its priority is higher than the currently running task; we need to request a context switch.

portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

xHigherPriorityTaskWoken parameter

is a “messenger” variable. We pass it to xQueueSendFromISR. If Task_Report (high priority) is woken from a blocked state by this message, and its priority is higher than the task currently running when the interrupt occurred (e.g., Task_Sensor), xQueueSendFromISR will internally modify the value of xHigherPriorityTaskWoken to pdTRUE.

Function of the portYIELD_FROM_ISR() macro

It checks the value of xHigherPriorityTaskWoken. If the value is pdTRUE, it will manually trigger a PendSV exception before returning from the interrupt, requesting a context switch. This ensures the fastest interrupt response speed.

  • • If this macro is not called, the context switch will only occur at the next SysTick interrupt, causing a delay in response.

This section does not specifically show the API and queue management diagrams; please refer to the official documentation. Subsequent semaphore notes will be organized in the same way. 😺😺😺

Leave a Comment