RTOS Queues

Ở bài viết này mình sẽ hướng dẫn các bạn về Queue trong RTOS. Queue trong RTOS là gì, có bao nhiêu loại queue, nó được sử dụng và khởi tạo như thế nào, làm sao để gửi nhận data giữa các task với nhau ? Các API của queue là những API nào và cách sử dụng nó ra sao ? Lý thuyết ít, thực hành là chính nhé các bạn.

Tổng quan

Trước tiên chúng ta cần xem lại khái quát nội dung về Queue trong RTOS ở bài viết RTOS cơ bản

Queue có 2 dạng là message queue và mail queue, đây là 2 cơ chế được dùng để các task có thể trao đổi với nhau, sự khác biệt lớn nhất giữa 2 dạng này là message queue thì truyền dữ liệu dưới dạng đơn, còn mail queue sẽ truyền dưới dạng khối.

Message Queue

Tạo Message Queue

Bước 1: Tạo Queue dùng hàm

osMessageQDef(myQueue01, 16, uint16_t)

Trong đó

  • myQueue01: Tên của message queue
  • 16: Số lượng item trong queue
  • uint16_t : Size của item trong queue

Bước 2: Tạo message và cấp phát bộ nhớ dùng osMessageCreate(), hàm này sẽ trả về một handle/ID được sử dụng bởi một API khác liên quan tới việc tạo message queue

myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);

Trong đó

  • osMessageQ(myQueue01): Định nghĩa message queue
  • NULL: Task ID ở đây không sử dụng thì ta sẽ set NULL

Gửi nhận data

Ta có thể gửi nhận data từ các task khác với message queue bằng cách dùng hàm osMessagePut và osMessageGet

Task có thể gửi data tới các task khác với hàm

osMessagePut(myQueue01Handle, data, 0);

Trong đó

  • myQueue01Handle : Message queue handle/ID được sử dụng
  • data: dữ liệu được gửi đi
  • 0: Thời gian timeout, 0 nghĩa là ko có timeout

Ngoài ra thì task cũng có thể nhận được data từ các task khác thông qua hàm

message_event = osMessageGet(myQueue01Handle, 0);

Trong đó

  • myQueue01Handle : Message queue handle/ID
  • 0: Thời gian timeout
  • osMessageGet() sẽ return một osEvent structure/object, data sẽ được lưu trữ trong message_event.value

Dưới đây là hình minh họa quá trình Put và Get của Message Queue

Cấu trúc của osEvent

typedef struct
{
  osStatus status;            //status code: event or error information
  union {
    uint32_t v;               //message as 32-bit value
    void *p;                  //message or mail as void pointer
    int32_t signals;          //signal flags
  } value;                    //event value
  union {
    osMailQId mail_id;        //mail id obtained by \ref osMailCreate
    osMessageQId message_id;  //message id obtained by \ref osMessageCreate
  } def;                      //event definition
} osEvent;

Nếu chúng ta muốn lấy dữ liệu từ osEvent thì cần phải sử dụng

  • osEventName.v nếu giá trị là 32bit message(hoặc 8/16bit)
  • osEventName.p và gõ lại giá trị datatype được lựa chọn

Mail Queue

Với mail queue thì data được gửi đi dưới dạng block chứ không phải dạng đơn như message queue, các memory block này cần được cấp phát(allocate) trước khi đưa data vào và giải phóng(free) sau khi cho data ra. Với FreeRTOS thì nó sẽ dùng message queue để pointer vào memory block còn CMSIS thì có thêm mail queue như các bạn đã thấy. Hơi buồn 1 xíu là mình ko tìm thấy phần cấu hình mail queue trong Cubemx, đành phải tạo message queue rồi thay đổi chút thôi.

Tạo mail queue

Định nghĩa mail queue bằng hàm

osMailQDef(myMailQueue01, 16, properties_t);

Trong đó

  • myMailQueue01: Tên của queue
  • 16: số lượng item tối đa trong queue
  • properties_t: Dạng dữ liệu được lưu trong Queue

Tạo mail queue và cấp phát bộ nhớ với

osMailQId (myMailQueue01Handle);
myMailQueue01Handle = osMailCreate(osMailQ(myMailQueue01), NULL);

Trong đó:

  • myMailQueue01: định nghĩa mail queue
  • NULL: Task ID không sử dụng sẽ là NULL

Gửi data

Trước khi gửi data thì cần phải khai báo data struct, sau đó dùng hàm osMailPut để gửi data

//Assume data to be sent is details about a delivery package
typedef struct
{
  uin32_t length;
  uint32_t width;
  uint32_t height;
  uint32_t weight;
} packaget_t
 
properties_t *data = osMailAlloc(myMailQueue01Handle, osWaitForever);
data->length = 10;
data->width = 20;
data->height = 5;
data->weight = 10;
osMailPut(myMailQueue01Handle, data);

Trong đó:

  • Phần properties_t *data = osMailAlloc: là cấp phát bộ nhớ từ mail queue để data có thể được đặt trong mail queue
  • Phần data->length = 10; .. là dữ liệu được lưu trữ sẽ được đưa vào bộ nhớ cấp phát dữ liệu (allocated memory)
  • Phần osMailPut .. là đưa data vào trong mail queue.

Nhận data

Đoạn code dưới đây sẽ dùng để nhận data trong mail  queue

OsEvent mail_event = osMailGet(myMailQueue01Handle, 0);
package_t *data = mail_event.p;
//Use received data, using data->height, data->weight, etc.
osMailFree(myMailQueue01Handle, data)

Trong đó:

  • Đoạn OsEvent mail_event .. là lấy data từ mail queue, osMailGet sẽ return một osEvent struct/object
  • Đoạn package_t *data = mail_event.p; là lấy con trỏ tới data, data này sẽ được lưu trong mail_event.value.p
  • Cuối cùng osMailFree dùng để giải phóng bộ nhứ sau khi dùng xong data

Queue API

Bên dưới là bảng tổng hợp các API có trong Message Queue và Mail Queue

Feature CMSIS RTOS API FreeRTOS API
Create message queue osMessageQCreate xQueueCreate
Get message osMessageQGet xQueueReceive
Put message osMessageQPut xQueueSend
Receive message withoutremoving message from queue osMessagePeek xQueuePeek
Create mail queue osMailCreate xQueueCreate
Get mail osMailGet xQueueReceive
Put mail osMailPut xQueueSend
Allocate memory block formail queue osMailAlloc osPoolAlloc
Free memory for mailqueue osMailFree osPoolFree

Ví dụ

Để dễ hiểu hơn về cách sử dụng các hàm API của queue này chúng ta đi vào 3 ví dụ sau: Tạo queue để put và get data, tạo queue với 2 sender, tạo mail queue với 2 sender có priority

Nếu bạn là người mới xem bài viết này thì nên xem qua 2 bài viết sau để hiểu rõ hơn về cách tạo project và sử dụng debug

Tạo queue

Ở ví dụ này ta sẽ tạo ra 2 task với priority như nhau, Task 1 sẽ gửi data và Task 2 sẽ nhận data từ Task 1

Mở CubeMX chỉnh cấu hình cho FreeRTOS

  • Set cả 2 task với priority là normal
  • Phần Queue thêm một queue mới với size là 256 và type là uint8_t

Generate code từ CubeMX sau đó quan sát file main.c ta sẽ thấy

Queue handler sẽ được định nghĩa như sau

/* Private variables ---------------------------------------------------------*/
osThreadId Sender1Handle;
osThreadId ReceiverHandle;
osMessageQId Queue1Handle;

Kiểu Queue item sẽ được thiết lập, độ dài được define trước và tạo ra queue cũng như cấp phát bộ nhớ

/* Create the queue(s) */
/* definition and creation of Queue1 */
osMessageQDef(Queue1, 256, uint8_t);
Queue1Handle = osMessageCreate(osMessageQ(Queue1), NULL);

Sender 1 task

void StartSender1(void const * argument) {
 
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  uint32_t i = 0;
  for (;;) {
    printf("Task 1\n");
    osMessagePut(Queue1Handle, 0x1, 200);
    printf("Task 1 delay\n");
    osDelay(1000);
  }
  /* USER CODE END 5 */
}

Receiver task

/* StartReceiver function */
void StartReceiver(void const * argument) {
  /* USER CODE BEGIN StartReceiver */
  osEvent retvalue;
  /* Infinite loop */
  for (;;) {
    printf("Task 2\n");
    retvalue = osMessageGet(Queue1Handle, 4000);
    printf("%d \n", retvalue.value.p);
    osDelay(1);
  }
  /* USER CODE END StartReceiver */
}

Queue blocking sẽ xảy ra sau khi gọi hàm osMessageGet. Nếu không có data nào trong queue thì task sẽ block trong thời gian định trước, còn nếu có data trong queue thì task sẽ tiếp tục.
Sau khi call osMessageGet sẽ xảy ra Queue blocking

Nếu có bất kỳ data nào mà không ở trong queue thì task 1 sẽ bị block theo thời gian set trước

Nếu data đang ở trong queue thì task sẽ tiếp tục

Kết quả

2 sender và 1 receiver

Ở ví dụ tiếp theo ta sẽ tạo ra 3 task với priority như nhau,trong đó có 2 sender và 1 receiver

Mở CubeMX lên và cấu hình như sau:

2 task send và 1 task receive, tất cả đều cùng priority

2 task send sẽ như nhau và có cùng mã code là
TaskSender 1

/* StartSender1 function */
void StartSender1(void const * argument) {
 
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  uint32_t i = 0;
  for (;;) {
    printf("Task 1\n");
    osMessagePut(Queue1Handle, 0x1, 200);
    printf("Task 1 delay\n");
    osDelay(2000);
  }
  /* USER CODE END 5 */
}

TaskSender2

/* StartSender2 function */
void StartSender2(void const * argument) {
  /* USER CODE BEGIN StartSender2 */
  /* Infinite loop */
  for (;;) {
    printf("Task 2\n");
    osMessagePut(Queue1Handle, 0x2, 200);
    printf("Task 2 delay\n");
    osDelay(2000);
  }
  /* USER CODE END StartSender2 */
}

Task receiver đơn giản sẽ có dạng

/* StartReceiver function */
void StartReceiver(void const * argument) {
  /* USER CODE BEGIN StartReceiver */
  osEvent retvalue;
  /* Infinite loop */
  for (;;) {
    retvalue = osMessageGet(Queue1Handle, 4000);
    printf("Receiver \n");
    printf("%d \n", retvalue.value.p);
  }
  /* USER CODE END StartReceiver */
}

Sau đó ta build và chạy thử chương trình sẽ có kết quả:

Hình minh họa quá trình hoạt động của queue

Vì các task này có cùng priority nên receiver sẽ nhận data từ queue sau khi cả 2 task đưa data vào trong queue, với các task đơn giản thì có thể còn nhận được data, tuy nhiên nếu nhiều task hơn cùng một priority thì sẽ rất khó để kiểm soát.

Gửi data với mail queue

Tận dụng lại project bên trên ta thay đổi cấu hình FreeRTOS trong CubeMX như sau:

  • Sender 1 và 2 sẽ có cùng priority
  • Receiver sẽ có priority thấp nhất

Queue cho phép định nghĩa các dạng (các biến khác nhau hoặc struct) mà queue cần dùng, ta điều chỉnh lại dạng data size ở đây là Data thay cho unint8_t ở trên, sau đó gen lại project

Sau đó ta điều chỉnh trong file main.c

Thay đổi osMessageQId thành osMailQId để sử dụng mail queue

osMessageQId Queue1Handle;

thành

osMailQId Queue1Handle;

Thay đổi đoạn create queue từ

osMessageQDef(Queue1, 256, Data);
Queue1Handle = osMessageCreate(osMessageQ(Queue1), NULL);

Thành

osMailQDef(Queue1, 1, Data);
Queue1Handle = osMailCreate(osMailQ(Queue1), NULL);

Tạo một struture mới cho data có dạng

/* Private variables ---------------------------------------------------------*/
typedef struct {
  uint8_t Value;
  uint8_t Source;
}
Data;

Send dữ liệu từ sender task bằng cách
Sender1

void StartSender1(void const * argument)
{
 
  /* USER CODE BEGIN 5 */
  Data *dataMail;
  /* Infinite loop */
  for (;;)
  {
    printf("Task 1\n");
    dataMail = osMailAlloc(Queue1Handle, osWaitForever); /* Allocate memory */
    dataMail->Value = 10;
    dataMail->Source = 1;
 
    if (osMailPut(Queue1Handle, dataMail) != osOK) /* Send Mail */
    {
      printf("Task 1 send mail\n");
      osDelay(250);
    }
  }
  /* USER CODE END 5 */
}
}

Sender2

void StartSender2(void const * argument)
{
  /* USER CODE BEGIN StartSender2 */
  Data *dataMail;
  /* Infinite loop */
  for (;;)
  {
    printf("Task 2\n");
    dataMail = osMailAlloc(Queue1Handle, osWaitForever); /* Allocate memory */
    dataMail->Value = 20;
    dataMail->Source = 2;
 
    if (osMailPut(Queue1Handle, dataMail) != osOK) /* Send Mail */
    {
      printf("Task 2 send mail\n");
      osDelay(250);
    }
  }
  /* USER CODE END StartSender2 */
}

Nhận dữ liệu từ sender task bằng lệnh

void StartReceiver(void const * argument)
{
  /* USER CODE BEGIN StartReceiver */
  osEvent retvalue;
  Data *dataMail;
  /* Infinite loop */
  for (;;)
  {
    /* Get the message from the queue */
    retvalue = osMailGet(Queue1Handle, osWaitForever); /* wait for mail */
    printf("Receiver \n");
    if (retvalue.status == osEventMail)
    {
      dataMail = retvalue.value.p;
      if (dataMail->Source == 1)
      {
        printf("Receiver receive message from sender 1 \n");
      }
      else
      {
        printf("Receiver receive message from sender 2 \n");
      }
      printf("Value: %d \n", dataMail->Value);
      printf("Source: %d \n", dataMail->Source);
      osMailFree(Queue1Handle, dataMail); /* free memory allocated for mail */
    }
  }
  /* USER CODE END StartReceiver */
}

Receiver sẽ được unblock mỗi khi sender task gửi data vào trong queue

Kết quả:

Hình bên dưới minh họa ví dụ trên

Tạm kết

Vậy là ở bài viết này mình đã làm rõ về Queue trong RTOS là gì, sơ lược về Message Queue và Mail Queue, cách tạo Queue ? Các API của queuevà cách sử dụng nó trong các ví dụ tạo queue, 2 sender 1 receiver với message queue và mail queue. Còn các vấn đề về semaphore xin hẹn các bạn ở bài viết sau.