Giới thiệu nhanh
Trong phần trước, chúng ta đã tìm hiểu về Task trong FreeRTOS – những “nhân viên” trong nhà máy phần mềm, mỗi task đảm nhận một nhiệm vụ cụ thể, chạy độc lập theo lịch của Scheduler. Tuy nhiên, một hệ thống không thể hoạt động hiệu quả nếu mỗi thành phần chỉ làm việc một cách cô lập. Các task cần giao tiếp – chia sẻ dữ liệu, ra lệnh, gửi tín hiệu... và đó chính là lúc Queue xuất hiện.
1. Queue trong FreeRTOS là gì?
Trong FreeRTOS, Queue là một cơ chế IPC (Inter-Process Communication) kiểu FIFO (First In First Out), cho phép truyền dữ liệu an toàn giữa các task, hoặc giữa ISR (Interrupt Service Routine) và task.
Hãy tưởng tượng:
- Task A là một bộ thu tín hiệu từ cảm biến.
- Task B là một bộ xử lý dữ liệu.
- Thay vì chia sẻ trực tiếp biến toàn cục (rất nguy hiểm!), Task A chỉ việc “xếp hàng” dữ liệu vào queue, và Task B lấy dữ liệu ra khi đến lượt.
2. Cách hoạt động của Queue
2.1 Tạo queue
Bạn cần tạo một queue trước khi dùng:
QueueHandle_t xQueue;
xQueue = xQueueCreate(10, sizeof(int));
10
: số lượng phần tử tối đa trong queue.sizeof(int)
: kích thước mỗi phần tử (ví dụ: kiểuint
).
2.2 Gửi dữ liệu vào queue
Từ task:
xQueueSend(xQueue, &value, portMAX_DELAY);
Từ ISR:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(xQueue, &value, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
2.3 Nhận dữ liệu từ queue
int value;
xQueueReceive(xQueue, &value, portMAX_DELAY);
3. Queue hoạt động như thế nào bên trong?
Khi một task gọi xQueueSend
, dữ liệu được copy vào một buffer vòng (circular buffer) bên trong queue. Một task khác gọi xQueueReceive
sẽ nhận phần tử đầu tiên theo thứ tự FIFO.
Nếu queue rỗng? Task sẽ bị block cho đến khi có dữ liệu (nếu dùng portMAX_DELAY
) hoặc timeout.
Nếu queue đầy? Task gửi sẽ bị block hoặc trả về lỗi tùy cấu hình.
4. Khi nào nên dùng Queue?
Tình huống | Có nên dùng queue? |
---|---|
Task nhận dữ liệu từ cảm biến và task khác xử lý | Rất phù hợp |
ISR gửi tín hiệu trạng thái đơn giản | Nên dùng Semaphore |
Gửi tin nhắn dạng chuỗi | Dùng Message Queue hoặc Stream Buffer |
Truyền dữ liệu khối lớn | Nên xem xét dùng buffer trực tiếp + signal |
5. Ưu điểm của Queue trong FreeRTOS
- An toàn luồng (thread-safe): Được bảo vệ bằng mutex nội bộ.
- Linh hoạt: Có thể dùng cho ISR <-> task, task <-> task.
- Tự động block: Task không phải chờ polling.
- Rất nhẹ: Phù hợp với hệ thống nhúng nhỏ gọn.
6. Các loại queue trong FreeRTOS
Tên | Mô tả | Ví dụ |
---|---|---|
Queue thường | FIFO cố định | int, struct nhỏ |
Queue set | Chờ nhận từ nhiều queue | Ví dụ 2 cảm biến gửi về |
Stream Buffer | Dòng byte liên tục | UART, ADC |
Message Buffer | Tin nhắn có độ dài thay đổi | Tin nhắn dạng TLV |
7. Một số tình huống thực tế & ví dụ
7.1 Giao tiếp ISR với task
// ISR
void ADC_IRQHandler() { int val = read_adc(); BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(xQueue, &val, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// Task
void vAdcProcessingTask(void *pvParameters) { int val; while (1) { if (xQueueReceive(xQueue, &val, portMAX_DELAY)) { process(val); } }
}
7.2 Task gửi nhận dữ liệu lẫn nhau
// Task 1
xQueueSend(xQueue, &data, 0); // Task 2
if (xQueueReceive(xQueue, &rx_data, 0)) { // xử lý rx_data
}