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.
Bước 1: Tạo Queue dùng hàm
1
osMessageQDef(myQueue01, 16, uint16_t)
Trong đó
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
1
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
Trong đó
Bước 1: Tạo Queue dùng hàm
1
osMessageQDef(myQueue01, 16, uint16_t)
Trong đó
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
1
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
Trong đó
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
1
osMessagePut(myQueue01Handle, data, 0);
Trong đó
Ngoài ra thì task cũng có thể nhận được data từ các task khác thông qua hàm
1
message_event = osMessageGet(myQueue01Handle, 0);
Trong đó
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
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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.
Định nghĩa mail queue bằng hàm
1
osMailQDef(myMailQueue01, 16, properties_t);
Trong đó
Tạo mail queue và cấp phát bộ nhớ với
1
2
osMailQId (myMailQueue01Handle);
myMailQueue01Handle = osMailCreate(osMailQ(myMailQueue01), NULL);
Trong đó:
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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 đó:
Đoạn code dưới đây sẽ dùng để nhận data trong mail queue
1
2
3
4
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 đó:
Bên dưới là bảng tổng hợp các API có trong Message Queue và Mail Queue
Queue API RTOS
Để 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
Ở 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
Generate code từ CubeMX sau đó quan sát file main.c ta sẽ thấy
Queue handler sẽ được định nghĩa như sau
1
2
3
4
/* 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ớ
1
2
3
4
/* Create the queue(s) */
/* definition and creation of Queue1 */
osMessageQDef(Queue1, 256, uint8_t);
Queue1Handle = osMessageCreate(osMessageQ(Queue1), NULL);
Sender 1 task
1
2
3
4
5
6
7
8
9
10
11
12
13
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
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 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ả
Ở 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
/* 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.
Tận dụng lại project bên trên ta thay đổi cấu hình FreeRTOS trong CubeMX như sau:
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
1
osMessageQId Queue1Handle;
thành
1
osMailQId Queue1Handle;
Thay đổi đoạn create queue từ
1
2
osMessageQDef(Queue1, 256, Data);
Queue1Handle = osMessageCreate(osMessageQ(Queue1), NULL);
Thành
1
2
osMailQDef(Queue1, 1, Data);
Queue1Handle = osMailCreate(osMailQ(Queue1), NULL);
Tạo một struture mới cho data có dạng
1
2
3
4
5
6
/* Private variables ---------------------------------------------------------*/
typedef
struct
{
uint8_t Value;
uint8_t Source;
}
Data;
Send dữ liệu từ sender task bằng cách
Sender1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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