Ở 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 queue16
: Số lượng item trong queueuint16_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 queueNULL
: 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ụngdata
: dữ liệu được gửi đi0
: 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/ID0
: Thời gian timeoutosMessageGet()
sẽ return một osEvent structure/object, data sẽ được lưu trữ trongmessage_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 queue16
: số lượng item tối đa trong queueproperties_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 queueNULL
: 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 trongmail_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
- Dùng SWO debug
- Tạo project FreeRTOS dùng CubeMX
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.