Vấn đề khi gửi dữ liệu vào Queue: Dữ liệu đã nhận nhưng sao chưa thực thi?

0 0 0

Người đăng: delinux

Theo Viblo Asia

Giới thiệu

Ở bài viết trước, mình đã nêu cho các bạn biết khái niệm Queue là gì, cách sử dụng nó ra sao và những lưu ý khi sử dụng nó.

Nhắc lại một chút cho các bạn chưa đọc bài viết đó thì trong các hệ thống nhúng thời gian thực, FreeRTOS là một lựa chọn phổ biến nhờ tính linh hoạt và nhẹ. Một trong những cơ chế giao tiếp liên tiến trình (IPC) được sử dụng nhiều nhất là hàng đợi (queue).

Có một vấn đề mà mình cũng đã nhắc tới trong bài trước đó là: khi hệ thống phải xử lý lượng lớn dữ liệu hoặc dữ liệu đến liên tục từ các thiết bị ngoại vi (như ADC, UART, SPI, hoặc cảm biến), người dùng thường gặp phải tình huống queue bị đầy hoặc dữ liệu bị xử lý chậm, gây mất dữ liệu hoặc phản hồi trễ.

Bài viết này sẽ phân tích nguyên nhân gây ra hiện tượng trên và đưa ra các giải pháp cụ thể nhằm cải thiện hiệu suất xử lý dữ liệu qua queue trong FreeRTOS.


1. Tổng quan về Queue trong FreeRTOS

Trong FreeRTOS, queue là một hàng đợi FIFO (First In First Out) dùng để truyền dữ liệu giữa các task hoặc giữa ISR (Interrupt Service Routine) và task.

Cách hoạt động:

  • ISR hoặc Task gửi dữ liệu vào queue bằng các hàm như xQueueSendFromISR() hoặc xQueueSend().
  • Một task khác sẽ nhận dữ liệu từ queue bằng xQueueReceive().

Vấn đề:

  • Nếu task nhận dữ liệu xử lý quá chậm, queue sẽ bị đầy.
  • ISR gửi dữ liệu sẽ không gửi được tiếp, hoặc mất dữ liệu nếu dùng timeout ngắn.

2. Tình huống ví dụ

Xét hệ thống đơn giản:

  • Một cảm biến gửi dữ liệu ADC mỗi 1ms vào ISR.
  • ISR gọi xQueueSendFromISR() để gửi dữ liệu vào hàng đợi.
  • Một task DataProcessingTask nhận và xử lý dữ liệu từ queue (ví dụ: lọc tín hiệu, ghi log, gửi MQTT,...).

Hiện tượng:

  • Sau vài giây chạy ổn định, hệ thống bắt đầu mất dữ liệu hoặc trễ phản hồi do queue bị đầy.

3. Nguyên nhân gây chậm trễ xử lý dữ liệu

Nguyên nhân Mô tả
Task xử lý quá chậm Thời gian xử lý mỗi phần tử trong task quá dài
Độ ưu tiên task chưa phù hợp Task xử lý queue bị preempt bởi task khác có độ ưu tiên cao hơn
Queue kích thước quá nhỏ Không đủ chỗ chứa dữ liệu dồn dập từ ISR
Không xử lý batch Mỗi lần xử lý chỉ lấy 1 phần tử, thay vì xử lý nhiều phần tử một lúc
ISR gửi quá nhanh ISR gửi quá thường xuyên, không giới hạn tốc độ
Không báo ISR yield đúng cách ISR không thông báo cho scheduler nếu task chờ queue có thể chạy ngay

4. Giải pháp

4.1 Tối ưu tốc độ xử lý của task

  • Phân tách xử lý nặng: Không nên thực hiện xử lý nặng (như tính toán số học phức tạp, giao tiếp chậm) trực tiếp trong task nhận queue.
  • Dùng buffer trung gian: Đọc nhanh từ queue, đẩy vào một buffer nội bộ, sau đó xử lý buffer khi rảnh.
while (1) { if (xQueueReceive(queue, &data, portMAX_DELAY)) { buffer[write_index++] = data; // xử lý sau }
}

4.2 Tăng độ ưu tiên của task xử lý queue

  • Đảm bảo task xử lý queue có độ ưu tiên cao hơn ISR hoặc task sinh dữ liệu nếu cần xử lý nhanh chóng.
xTaskCreate(DataProcessingTask, "Proc", 256, NULL, tskIDLE_PRIORITY + 3, NULL);

4.3 Tăng kích thước queue

  • Nếu dung lượng RAM cho phép, tăng kích thước queue sẽ giúp “đệm” được nhiều dữ liệu hơn khi task chưa xử lý kịp.
xQueueCreate(64, sizeof(sensor_data_t));

4.4 Xử lý batch nhiều phần tử mỗi lần

  • Đọc hết các phần tử trong queue (nếu có) thay vì đọc từng phần tử một.
while (uxQueueMessagesWaiting(queue) > 0) { xQueueReceive(queue, &data, 0); // xử lý data
}

4.5 ISR yield đúng cách

  • Trong ISR, nếu có task đang chờ queue, cần gọi portYIELD_FROM_ISR() để chuyển ngay sang task xử lý.
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(queue, &data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

4.6 Giới hạn tốc độ ISR hoặc task gửi dữ liệu

  • Nếu dữ liệu đến quá nhanh, cần "giảm tốc độ" gửi (debounce, timer pacing, downsampling,...).
// Ví dụ: gửi dữ liệu 1 lần mỗi 10 chu kỳ
static int count = 0;
if (++count >= 10) { count = 0; xQueueSendFromISR(queue, &data, &xHigherPriorityTaskWoken);
}

4.7 Sử dụng Stream Buffer hoặc Message Buffer thay vì Queue (khi phù hợp)

  • StreamBuffer: Dùng cho chuỗi byte (ví dụ UART), không cần cấu trúc cố định.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giải mã AUTOSAR: Kiến trúc tiêu chuẩn ngành Automotive

Giới thiệu. AUTOSAR có vẻ khá xa lạ đối với người làm về công nghệ thông tin, nhưng đối với những bạn làm về Embedded System, đặc biệt là trong lĩnh vực Automotive, thì cũng.

0 0 24

- vừa được xem lúc

Từ khoá volatile trong lập trình C

Trong lập trình nhúng chắc hẵn bạn đã từng gặp phải tình huống khi chương trình C của bạn cho ra kết quả không đúng, mặc dù mã code có vẻ đúng? Một nguyên nhân có thể gây ra vấn đề này là việc tối ưu

0 0 24

- vừa được xem lúc

Giải mã AUTOSAR: Kiến trúc để đời ngành Automotive - Phần 2

Updating. Trong bài viết này, ta cùng tìm hiểu kỹ hơn về lớp trên cùng của kiến trúc, chính là lớp Application Layer (ASW).

0 0 20

- vừa được xem lúc

Con Trỏ Hàm - C/C++

Hàm Con Trỏ (Function Pointer) là gì. . Con trỏ tới hàm (A pointer to a function): Đây là biến lưu trữ địa chỉ hàm mà sau này có thể gọi bằng con trỏ này. .

0 0 18

- vừa được xem lúc

Cơ bản về WatchDog Timer trong hệ thống nhúng (Embedded System)

Khái Niệm. Trong hệ thống nhúng, a WatchDog Timer (Bộ đếm thời gian giám sát) là một thành phần hoặc tính năng của phần cứng được thiết kế để giám sát hoạt động của hệ thống và thực hiện hành động khắ

0 0 14

- vừa được xem lúc

Tối ưu hóa thời gian khởi động Nvidia Xavier

1. Vai trò.

0 0 11