1. Giới thiệu về Stream Buffer
Trong hệ điều hành thời gian thực (RTOS) như FreeRTOS, Stream Buffer là một loại cơ chế truyền dữ liệu từ task này sang task khác hoặc giữa interrupt và task theo dạng luồng byte liên tục (stream of bytes).
Stream Buffer là giải pháp cực kỳ hữu ích trong các tình huống:
- Truyền dữ liệu nhỏ và liên tục giữa các task (ví dụ: UART, SPI, CAN...)
- Truyền dữ liệu từ ISR đến task mà không cần sử dụng queue (nhẹ hơn queue)
- Giao tiếp giữa phần mềm với phần cứng qua DMA hoặc ngoại vi
2. Nguyên lý hoạt động
Một Stream Buffer là một vùng bộ nhớ kiểu FIFO. Task hoặc ISR sẽ ghi dữ liệu vào buffer và một task khác sẽ đọc dữ liệu này ra.
Ghi (Send) → từ một task hoặc từ ISR Đọc (Receive) → chỉ từ task (ISR không được phép đọc)
Các tính năng chính:
- Hỗ trợ cơ chế blocking hoặc non-blocking
- Không cần phân vùng dữ liệu như queue (dữ liệu là luồng byte thuần)
- Có thể cấu hình trigger level để task nhận chỉ khi có đủ dữ liệu
3. Các thành phần chính
Thành phần | Mô tả |
---|---|
xStreamBufferCreate() |
Tạo stream buffer |
xStreamBufferSend() |
Gửi dữ liệu vào buffer |
xStreamBufferReceive() |
Nhận dữ liệu từ buffer |
xStreamBufferReset() |
Reset buffer |
xStreamBufferIsFull() |
Kiểm tra buffer đã đầy chưa |
xStreamBufferIsEmpty() |
Kiểm tra buffer đã rỗng chưa |
4. Những lưu ý khi sử dụng Stream Buffer
4.1 Không dùng để truyền dữ liệu dạng khối rời rạc
Stream Buffer chỉ truyền dữ liệu dạng liên tục (liên byte), không hỗ trợ cấu trúc phân mảnh như Queue (phân định từng item). Nếu bạn muốn truyền các cấu trúc (struct) hoặc dữ liệu rời rạc, hãy dùng Message Buffer
.
4.2 Không thể nhận dữ liệu trong ISR
Chỉ có thể gọi xStreamBufferSendFromISR()
trong ngắt. Việc đọc (xStreamBufferReceive
) chỉ được phép trong task.
4.3 Cẩn thận khi sử dụng Trigger Level
Trigger Level là ngưỡng byte tối thiểu để task được đánh thức khi có dữ liệu. Ví dụ:
xStreamBufferReceive(xBuffer, data, 10, portMAX_DELAY);
- Nếu trong buffer có <10 byte, task sẽ block.
- Chỉ khi có ≥10 byte trong buffer thì task mới được đánh thức.
⇒ Điều này giúp giảm overhead đánh thức task quá sớm nhưng cần cân nhắc kỹ.
4.4 Quản lý bộ nhớ
Stream Buffer dùng RAM nội bộ. Nếu bạn tạo nhiều buffer lớn (vd 1024 byte), hãy đảm bảo hệ thống đủ heap memory. Cấu hình trong FreeRTOSConfig.h
:
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSTREAM_BUFFER_LENGTH_TYPE size_t
5. Mã nguồn minh họa
Mô tả:
- Task A: Gửi dữ liệu
"ABC"
mỗi 2s - Task B: Đọc dữ liệu nếu đủ 3 byte
Code mẫu:
#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"
#include <string.h>
#include <stdio.h> #define BUFFER_SIZE 64
#define TRIGGER_LEVEL 3 StreamBufferHandle_t xStreamBuffer; void vSenderTask(void *pvParameters) { const char *data = "ABC"; while (1) { size_t bytesSent = xStreamBufferSend(xStreamBuffer, data, strlen(data), pdMS_TO_TICKS(100)); printf("Sent %d bytes: %s\n", (int)bytesSent, data); vTaskDelay(pdMS_TO_TICKS(2000)); }
} void vReceiverTask(void *pvParameters) { uint8_t rxData[10]; while (1) { size_t bytesReceived = xStreamBufferReceive(xStreamBuffer, rxData, 3, portMAX_DELAY); rxData[bytesReceived] = '\0'; printf("Received %d bytes: %s\n", (int)bytesReceived, rxData); }
} int main(void) { xStreamBuffer = xStreamBufferCreate(BUFFER_SIZE, TRIGGER_LEVEL); if (xStreamBuffer == NULL) { printf("Failed to create stream buffer!\n"); while(1); } xTaskCreate(vSenderTask, "Sender", 256, NULL, 2, NULL); xTaskCreate(vReceiverTask, "Receiver", 256, NULL, 1, NULL); vTaskStartScheduler(); for (;;);
}
Kết quả trên UART console:
Sent 3 bytes: ABC
Received 3 bytes: ABC
Sent 3 bytes: ABC
Received 3 bytes: ABC
6. So sánh nhanh: Queue vs Stream Buffer vs Message Buffer
Tiêu chí | Queue | Stream Buffer | Message Buffer |
---|---|---|---|
Kiểu dữ liệu | item rời rạc | chuỗi byte | khối dữ liệu |
Có thể dùng trong ISR | Có (send/receive) | Chỉ send | Chỉ send |
Blocking Receive | Có | Có | Có |
Ưu điểm | Linh hoạt cho task-to-task | Nhẹ, nhanh, phù hợp dữ liệu liên tục | Truyền struct dễ dàng |
Hạn chế | Tốn RAM hơn | Không truyền được struct trực tiếp | Không đọc từ ISR |
7. Ứng dụng thực tế
Truyền dữ liệu UART liên tục
DMA/ISR truyền byte vào stream buffer, task đọc và phân tích từng frame.
Truyền dữ liệu từ Sensor/ISR sang Task phân tích
Sensor update nhanh và ngắt sẽ send nhanh vài byte (vd: giá trị nhiệt độ). Task đọc, xử lý, hiển thị lên LCD.
Truyền Audio (PCM) từ ADC DMA sang xử lý FFT
ADC DMA → ISR → Stream Buffer → Task FFT
Logging hệ thống
Task gửi thông tin log (chuỗi byte) vào stream buffer. Một task duy nhất nhận và ghi vào thẻ SD hoặc gửi qua UART.
8. Kết luận
Stream Buffer
là một công cụ mạnh mẽ và nhẹ nhàng cho các hệ thống nhúng cần giao tiếp dữ liệu byte liên tục. Sử dụng đúng cách giúp giảm tải CPU, giảm context switching, tăng hiệu suất truyền nhận và đơn giản hóa lập trình ISR.
Tuy nhiên, bạn cần hiểu rõ bản chất của Stream Buffer (dữ liệu byte liên tục, không phân mảnh) để dùng đúng mục đích. Nếu bạn cần truyền từng khối hoặc cấu trúc cụ thể → Message Buffer hoặc Queue sẽ phù hợp hơn.