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

Phân biệt các loại Delay khi sử dụng FreeRTOS

0 0 1

Người đăng: delinux

Theo Viblo Asia

Giới thiệu nhanh

Trong quá trình phát triển phần mềm nhúng, đặc biệt là khi bước vào lãnh địa của RTOS, có một "vùng mờ" mà không ít người đã từng... lạc lối – đó là sự khác biệt giữa các loại hàm "delay".

Các bạn có thể đã từng dùng delay(), đã từng thử vTaskDelay(), và một số bạn còn mạo hiểm hơn khi tiếp cận vTaskDelayUntil(). Nhưng các bạn có thật sự hiểu chúng hoạt động như thế nào chưa? Và tại sao cùng là "delay", mà hệ thống lại có thể phản ứng hoàn toàn khác nhau?

Mình sẽ cùng các bạn dạo qua từng loại delay, mổ xẻ chúng, và để lại một vài “bí mật mở” mà có thể phải tới lần thứ hai đọc lại, các bạn mới chạm được vào chiều sâu thực sự.


1. delay() – Hồi ức của thời đơn luồng

Trước khi bước chân vào thế giới RTOS, mình thường sử dụng delay(ms) – hàm rất quen thuộc trong Arduino:

void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(1000); // chờ 1 giây digitalWrite(LED_BUILTIN, LOW); delay(1000);
}

Nhìn đơn giản, nhưng thực chất delay() là một vòng lặp bận (busy wait). ESP32 sẽ ngồi yên chờ cho tới khi hết thời gian – CPU không làm gì khác trong thời gian đó.

Trong hệ thống có RTOS như ESP32 với FreeRTOS, việc sử dụng delay() có thể làm nghẽn CPU, vì nó không nhường quyền cho task khác. Nghĩa là delay() không "hợp tác", mà chỉ... lì đòn.


2. vTaskDelay() – bước chân đầu tiên vào thế giới RTOS

Khi làm việc với FreeRTOS, vTaskDelay(tick) là công cụ đầu tiên các bạn sẽ gặp. Ví dụ đơn giản:

void blinkTask(void *pvParameters) { while (1) { digitalWrite(LED_BUILTIN, HIGH); vTaskDelay(pdMS_TO_TICKS(1000)); // chờ 1 giây digitalWrite(LED_BUILTIN, LOW); vTaskDelay(pdMS_TO_TICKS(1000)); }
}

Ở đây, task tự nguyện ngủ, nhường CPU cho các task khác. Đến lúc hệ thống tick đến, nó sẽ được "đánh thức" trở lại. Nhưng có một điểm tinh tế:

vTaskDelay(x) khiến task bị block tối thiểux tick, nhưng không đảm bảo chính xác thời điểm nó được thực thi trở lại.

Sự trễ có thể bị "trượt" nếu scheduler bận. Và đây là lúc bí mật bắt đầu hé lộ...


3. vTaskDelayUntil() – nghệ thuật giữ nhịp thời gian

vTaskDelayUntil() là một phiên bản tinh chỉnh hơn, cho phép task thực hiện một vòng lặp định kỳ có kiểm soát, gần giống như một nhạc sĩ giữ nhịp với metronome.

Ví dụ:

void stableBlinkTask(void *pvParameters) { TickType_t lastWakeTime = xTaskGetTickCount(); const TickType_t period = pdMS_TO_TICKS(1000); while (1) { digitalWrite(LED_BUILTIN, HIGH); vTaskDelayUntil(&lastWakeTime, period / 2); // bật 500ms digitalWrite(LED_BUILTIN, LOW); vTaskDelayUntil(&lastWakeTime, period); // tắt 500ms nữa }
}

Ở đây, lastWakeTime chính là chiếc đồng hồ gõ nhịp. Task sẽ canh chuẩn thời điểm để thực hiện mỗi chu kỳ, dù các lần thực thi có bị trễ chút ít do context switch. Sự ổn định này là điều vTaskDelay() không thể có.


4. So sánh trực quan

Hàm Hành vi Ứng dụng phù hợp
delay() Chặn CPU, không nhường Đơn luồng, demo
vTaskDelay() Ngủ mềm, nhường CPU Task tự chạy định kỳ
vTaskDelayUntil() Giữ nhịp định kỳ chuẩn Sensor sampling, log dữ liệu

Một sơ đồ dòng thời gian nhỏ:

Timeline (ticks): Task A: | Run |------delay--------| Run |------delay--------| ...
Task B: | Run | Run | Run ... ↑ ↑ vTaskDelay() vTaskDelay() Task C: | Run |------delay_until(1000)--------| Run |---delay_until(2000)---| ... ↑ ↑ vTaskDelayUntil() (Duy trì nhịp 1 giây)

5. Thí nghiệm nhỏ: delay() vs vTaskDelay() vs vTaskDelayUntil()

Giả sử các bạn chạy 3 task cùng nhấp nháy LED với 3 loại delay khác nhau trên ESP32:

void taskA(void *) { pinMode(2, OUTPUT); while (1) { digitalWrite(2, !digitalRead(2)); delay(500); // BLOCKING delay }
} void taskB(void *) { pinMode(4, OUTPUT); while (1) { digitalWrite(4, !digitalRead(4)); vTaskDelay(pdMS_TO_TICKS(500)); // cooperative delay }
} void taskC(void *) { pinMode(5, OUTPUT); TickType_t lastWake = xTaskGetTickCount(); while (1) { digitalWrite(5, !digitalRead(5)); vTaskDelayUntil(&lastWake, pdMS_TO_TICKS(500)); // rhythmic }
}

Kết quả:

  • LED 2 có thể làm trễ cả hệ thống (taskA chiếm CPU).
  • LED 4 chớp bình thường nhưng không đều đặn nếu hệ thống bận.
  • LED 5 chớp đều, đúng chu kỳ, bền bỉ như một tay trống chuyên nghiệp.
  • Các bạn có thể dùng analyzer để kiểm chứng 😄

6. Một số lưu ý nhỏ

vTaskDelayUntil() nghe có vẻ hoàn hảo, nhưng:

  • phụ thuộc vào độ chính xác của Tick, nên với ESP32, nếu bạn cấu hình configTICK_RATE_HZ không phù hợp (mặc định 100Hz), độ phân giải thời gian sẽ bị hạn chế (10ms/tick).
  • Khi task bị block quá lâu vì các task ưu tiên cao hơn, thời gian bị trượt nhịp có thể xảy ra.
  • Cần hiểu rằng nó không can thiệp được vào các tác vụ mang tính realtime cực cao – lúc này bạn sẽ phải dùng interrupt hoặc timer hardware.

7. Kết luận: delay không chỉ là chờ đợi

Qua bài viết này, hy vọng các bạn đã có được một bức tranh ban đầu về "ba loại delay" – như ba phong cách sống của một task:

  • Một người không quan tâm ai khác (delay)
  • Một người biết nhường nhịn (vTaskDelay)
  • Và một nghệ sĩ sống theo nhịp điệu riêng (vTaskDelayUntil)

Nhưng cũng như trong cuộc sống, hiểu một con người cần thời gian, hiểu cơ chế hoạt động sâu của delay trong RTOS cũng vậy. Mình chỉ mới mở ra cánh cửa đầu tiên cho các bạn – phần còn lại, các bạn phải bước vào thử nghiệmchạm tay vào lỗi để thật sự nắm vững.

Và biết đâu, trong lần tới, khi các bạn tự viết một scheduler tối ưu, chính kiến thức hôm nay sẽ là viên gạch nền đầu tiên.

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 28

- 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 28

- 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 23

- 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 20

- 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 18

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

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

1. Vai trò.

0 0 16