Use of queue in CMSIS/FreeRTOS

Posted by kaushikgotecha on Tue, 01 Feb 2022 05:24:37 +0100

In the embedded system with streaming data processing, queue is almost inevitable to be used, but the FreeRTOS routines provided by most development boards do not contain queue, so you have to study it yourself. This time, my example inserts the data received from the serial port into the queue by bytes and then let another thread process it. This is a relatively superfluous approach. The main purpose of this is to explain how to use the queue. Queue is more suitable for processing ADC/DAC sampling data, fixed size packets of communication module, etc.

This time I still use the self-made STM32F0 module to experiment with last time Similarly, we still use STM32CubeMX to generate the initial code and add a queue in FreeRTOS:
Here, I set each item in the queue as uint8_t type, up to 16 queue elements. In fact, it is more common to set it to a structure, which can be modified in the code after the code is generated. This setting becomes the following code in the generated code:

osMessageQueueId_t QueueUartByteHandle;
const osMessageQueueAttr_t QueueUartByte_attributes = {
  .name = "QueueUartByte"
};

QueueUartByteHandle = osMessageQueueNew (16, sizeof(uint8_t), &QueueUartByte_attributes);

The queue occupancy of FreeRTOS can be calculated according to the following formula:
S p a c e ( B y t e s ) = 92 + E l e m S i z e × n E l e m Space(Bytes) =92 + ElemSize \times nElem Space(Bytes)=92+ElemSize×nElem
For example, if I set an element here as 1 byte, up to 16, it will occupy 92 + 16 = 108 bytes.

We still use the API of CMSIS v2 instead of FreeRTOS. Referring to the CMSIS document, CMSIS calls the queue "message queue", which realizes the FIFO function. The incoming and outgoing APIs are as follows:

osStatus_t 	osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout);
osStatus_t 	osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout);

Where, mq_id is the variable name of the queue, that is, the QueueUartByteHandle defined by us; msg_ptr is the data block pointer to join / leave the queue; msg_prio is strange. The corresponding variable types of queue entry and queue exit are different, indicating priority, but they are generally assigned NULL; The last timeout is 0 without waiting. Always wait with oswaitforver. Both functions can run in interrupts.

For FreeRTOS, each API has common thread version and interrupt version. When writing its API, CMSIS judges according to the current state. Take CMISIS's osMessageQueuePut as an example, through IS_IRQ() macro to judge:

// From cmsis_os2.c
osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout) {
  QueueHandle_t hQueue = (QueueHandle_t)mq_id;
  osStatus_t stat;
  BaseType_t yield;

  (void)msg_prio; /* Message priority is ignored */

  stat = osOK;

  if (IS_IRQ()) {
    if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
      stat = osErrorParameter;
    }
    else {
      yield = pdFALSE;

      if (xQueueSendToBackFromISR (hQueue, msg_ptr, &yield) != pdTRUE) {
        stat = osErrorResource;
      } else {
        portYIELD_FROM_ISR (yield);
      }
    }
  }
  else {
    if ((hQueue == NULL) || (msg_ptr == NULL)) {
      stat = osErrorParameter;
    }
    else {
      if (xQueueSendToBack (hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
        if (timeout != 0U) {
          stat = osErrorTimeout;
        } else {
          stat = osErrorResource;
        }
      }
    }
  }

  return (stat);
}

I let the serial port receive the interrupt callback function to put the received data into the queue and let a task thread process it. The interrupt callback notifies the task through a counting semaphore, which is also defined in STM32CubeMX:
After the code is generated, the initial value of the semaphore should be changed to 0:

semAddToQueueHandle = osSemaphoreNew(16, 0, &semAddToQueue_attributes);

Finally, I photographed the code of interrupt callback function and task thread function to show how to synchronize semaphores and access data to queues:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	uint8_t recv_char;
	HAL_UART_Receive_IT(huart, &recv_char, 0x01);
	osMessageQueuePut(QueueUartByteHandle, &recv_char, 0U, 0U); // without waiting
	osSemaphoreRelease(semAddToQueueHandle);
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13); // just to show uart is working
}
void task1fxn(void *argument)
{
  /* Infinite loop */
	uint8_t  recv_char;
  for(;;)
  {
		osSemaphoreAcquire(semAddToQueueHandle,osWaitForever);
    osDelay(1);
		osMessageQueueGet(QueueUartByteHandle, &recv_char, NULL, 0);
		HAL_UART_Transmit_IT(&huart1, &recv_char, 0x01);
  }
}

Topics: C Single-Chip Microcomputer stm32