0

Stream Buffer trong FreeRTOS


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
Ư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.



All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí