Khi làm việc với bất kì hệ điều hành nào, việc tạo Task và lập lịch (Scheduling) là công việc đầu tiên và bắt buộc. Trong bài hôm nay chúng ta sẽ học cách tạo Task trên FreeRTOS với STM32 sử dụng thư viện HAL.
Bài đầu tiên về RTOS trong Serie: Học STM32 từ A tới Z
Task là gì
Task là đơn vị công việc nhỏ nhất mà hệ điều hành có thể quản lý và thực thi. Task tương tự như một chương trình nhỏ hoặc một tiến trình con trong hệ điều hành máy tính, nhưng có một số đặc điểm riêng dành cho các hệ thống nhúng và thời gian thực. Bạn có thể hình dung mỗi Task giống như một nhân viên trong công ty: ai cũng có bàn làm việc riêng (stack riêng), có mức độ quan trọng khác nhau, và người quản lý (scheduler) sẽ quyết định lúc nào ai được làm việc.
Đặc điểm của Task:
- Độc lập: mỗi task hoạt động một cách độc lập và có vùng ngăn xếp (stack) riêng của mình.
- Độ ưu tiên: mỗi task được gán một độ ưu tiên, xác định mức độ quan trọng của task đó so với các task khác.
- Vòng đời: task có thể chạy, bị tạm dừng, tiếp tục chạy, hoặc bị xóa bởi hệ điều hành.
- Trạng thái: task có thể ở nhiều trạng thái như sẵn sàng chạy (Ready), đang chạy (Running), bị chặn (Blocked), hoặc bị treo (Suspended).
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 2 Task States](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-1-711x400.jpeg)
Vai trò của Task trong FreeRTOS
Trong FreeRTOS, task là cách chính để thực hiện đa nhiệm (multitasking). Mỗi task có một chức năng cụ thể và hệ điều hành quản lý việc chuyển đổi giữa các task dựa trên độ ưu tiên và trạng thái của chúng.
Ví dụ, trong một ứng dụng nhúng:
- Một task có thể xử lý việc đọc dữ liệu từ cảm biến.
- Một task khác có thể xử lý giao tiếp với một thiết bị ngoại vi.
- Một task khác nữa có thể xử lý giao tiếp mạng.
Scheduling là gì?
Scheduling (lập lịch) là quá trình quản lý và quyết định thứ tự thực thi của các tác vụ (task) trong hệ thống. Scheduler (bộ lập lịch) là thành phần của RTOS chịu trách nhiệm phân bổ thời gian CPU cho các task dựa trên các thuật toán và tiêu chí nhất định.
Scheduling đóng vai trò quan trọng trong việc:
- Đảm bảo tính đáp ứng thời gian thực: đảm bảo các task được thực hiện đúng thời gian yêu cầu.
- Tối ưu hóa việc sử dụng tài nguyên: phân phối tài nguyên hệ thống một cách hiệu quả giữa các task.
- Đảm bảo tính công bằng: đảm bảo rằng tất cả các task có cơ hội được thực thi.
Các Thuật Toán Scheduling trong FreeRTOS
FreeRTOS hỗ trợ nhiều thuật toán scheduling khác nhau, chủ yếu bao gồm:
- Preemptive Scheduling: hệ điều hành có thể tạm dừng một task đang chạy để chuyển sang task có độ ưu tiên cao hơn ngay khi task đó sẵn sàng chạy.
- Cooperative Scheduling: task hiện tại sẽ chạy cho đến khi nó tự nguyện nhường quyền điều khiển lại cho hệ điều hành bằng cách gọi các hàm như
vTaskDelayhoặctaskYIELD.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 3 What are “co-operative” and “pre-emptive” scheduling algorithms?](https://khuenguyencreator.com/wp-content/uploads/2024/07/what-are-co-operative-and-pre-emptive-scheduli.png)
Cơ chế Scheduling trong FreeRTOS
Trong FreeRTOS, scheduling dựa trên độ ưu tiên của các task. Mỗi task được gán một độ ưu tiên (priority) và scheduler sẽ luôn chọn task có độ ưu tiên cao nhất đang sẵn sàng (Ready) để thực thi. Nếu có nhiều task cùng độ ưu tiên, chúng sẽ được thực hiện theo cơ chế Round-Robin (mỗi task chạy hết 1 khoảng thời gian rồi nhường cho task khác).
Trên STM32 chúng ta thường sử dụng thuật toán Preemptive Scheduling để lập lịch cho các Task. Hãy lưu ý về độ ưu tiên của các Task để cấu hình cho phù hợp.
Độ ưu tiên của các Task
Dưới đây là một số loại task thường được ưu tiên cao trong các ứng dụng nhúng:
1. Task Xử lý Ngắt (Interrupt Handling Task)
- Mô tả: task này xử lý tiếp các sự kiện do ngắt phần cứng báo về.
- Lý do ưu tiên: ngắt thường báo hiệu các sự kiện quan trọng như dữ liệu mới từ cảm biến hoặc thông tin truyền thông.
- Ví dụ: đọc dữ liệu từ cảm biến, xử lý dữ liệu nhận được từ giao tiếp UART hoặc SPI.
2. Task Xử lý Dữ liệu Thời Gian Thực (Real-Time Data Processing Task)
- Mô tả: task này xử lý dữ liệu yêu cầu đáp ứng trong thời gian thực.
- Lý do ưu tiên: dữ liệu thời gian thực cần được xử lý ngay lập tức để đảm bảo hệ thống hoạt động chính xác.
- Ví dụ: xử lý dữ liệu cảm biến trong hệ thống điều khiển tự động, tính toán điều khiển trong hệ thống nhúng.
3. Task Giao tiếp (Communication Task)
- Mô tả: task này xử lý giao tiếp giữa các module hoặc thiết bị.
- Lý do ưu tiên: giao tiếp yêu cầu độ tin cậy và tốc độ để đảm bảo dữ liệu được truyền nhận kịp thời.
- Ví dụ: task gửi và nhận dữ liệu qua mạng (Ethernet, Wi-Fi), task xử lý giao tiếp CAN bus trong ô tô.
4. Task Điều khiển (Control Task)
- Mô tả: task này thực hiện các thuật toán điều khiển cho hệ thống.
- Lý do ưu tiên: các hệ thống điều khiển yêu cầu đáp ứng nhanh và chính xác để duy trì hoạt động ổn định.
- Ví dụ: điều khiển động cơ, điều khiển nhiệt độ trong hệ thống HVAC.
5. Task Giám sát (Monitoring Task)
- Mô tả: task này giám sát trạng thái và hiệu suất của hệ thống.
- Lý do ưu tiên: giám sát liên tục giúp phát hiện và xử lý kịp thời các vấn đề có thể gây hỏng hóc hệ thống.
- Ví dụ: giám sát điện áp và nhiệt độ, kiểm tra trạng thái hoạt động của hệ thống.
6. Task Bảo trì (Maintenance Task)
- Mô tả: task này thực hiện các chức năng bảo trì hệ thống định kỳ.
- Lý do ưu tiên: bảo trì đúng thời gian giúp duy trì hiệu suất và độ tin cậy của hệ thống.
- Ví dụ: dọn dẹp bộ nhớ, lưu trữ dữ liệu log.
Tạo Task và Scheduling Task trên STM32 sử dụng Cube MX
Cấu hình FreeRTOS trên CubeMX
Trong phần “Pinout & Configuration”, chuyển đến mục “Middleware”, chọn FREERTOS và chọn Interface là CMSIS_V2.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 4 Enable freeRTOS stm32](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-description-automatica.png)
Trong phần Config Parameters hãy cùng tìm hiểu các tham số:
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 5 RTOS config](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-4.png)
USE_PREEMPTION: bật cơ chế lập lịch chiếm quyền theo độ ưu tiên của Task (Preemptive).
TICK_RATE_HZ: tick rate là số lần ngắt tick hệ thống (system tick) xảy ra mỗi giây. Trong ví dụ trên, tick rate được thiết lập là 1000 Hz, nghĩa là hệ thống sẽ tạo ra 1000 ngắt tick mỗi giây (mỗi 1ms một ngắt).
MINIMAL_STACK_SIZE: kích thước stack nhỏ nhất cho mỗi Task (tính theo word, mỗi task có thể dùng stack lớn nhỏ khác nhau).
Các thông số khác các bạn có thể để mặc định, ý nghĩa tương ứng với tên.
Tạo Task trên Cube MX
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 6 Tạo Task trên STM32 RTOS](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-5.png)
Khi chuyển sang tab Tasks and Queues, chúng ta sẽ thấy có một task mặc định (defaultTask) được tạo sẵn. Để tạo task mới, ta ấn nút Add, sau đó điền các tham số của Task:
Task Name: đặt tên của task.
Priority: độ ưu tiên của Task. Chúng ta có nhiều mức ưu tiên đi từ thấp đến cao như sau:
- Idle, Low: mức ưu tiên thấp nhất, thường dùng cho các task không quan trọng
- BelowNormal, Normal, AboveNormal: dành cho các task có độ ưu tiên bình thường
- High và Realtime: dành cho các tác vụ cần xử lý ngay lập tức
Stack Size: là kích thước stack mà Task sử dụng (tính theo word). Nên tính toán hợp lý với bộ nhớ RAM của chip nếu có nhiều task.
Entry Function: hàm sẽ được thực thi khi Task chạy.
Chúng ta sẽ tạo thêm 2 task là myTask02 với độ ưu tiên Low
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 7 Tạo task có độ ưu tiên khác nhau](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-task-description-autom.png)
Và myTask03 với độ ưu tiên High
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 8 Tạo task có độ ưu tiên khác nhau](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-task-description-autom-1.png)
Trong Advanced Settings, enable USE_NEWLIB_REENTRANT.
USE_NEWLIB_REENTRANT là một macro cấu hình dùng để kích hoạt chế độ reentrant (an toàn khi nhiều luồng cùng gọi) cho thư viện C Newlib của trình biên dịch GCC (STM32CubeIDE). Khi bật, mỗi task sẽ có vùng dữ liệu riêng cho các hàm như printf, malloc… thay vì dùng chung dữ liệu toàn cục.
Điều này giúp tránh các lỗi tranh chấp dữ liệu khi nhiều task cùng gọi các hàm thư viện C.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 9 word image 10968 8](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-8.png)
Bật USART1 để truyền dữ liệu về máy tính, xem Log task nào được khởi chạy.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 10 A screenshot of a computer Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-description-automatica-1.png)
Trong mục SYS, phần Timebase Source chúng ta chọn TIM1 thay cho SysTick. Lý do là khi dùng FreeRTOS, SysTick đã được FreeRTOS dùng làm tick cho hệ điều hành, còn thư viện HAL cũng cần một bộ đếm thời gian riêng cho các hàm như HAL_GetTick(), HAL_Delay() và các hàm timeout. Dùng chung SysTick cho cả hai sẽ gây xung đột, CubeMX cũng sẽ cảnh báo khi Gen code.
Vì vậy chúng ta cần nhường SysTick cho FreeRTOS và chọn 1 timer bất kì (không dùng vào việc khác) làm timebase cho HAL.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 11 A screenshot of a computer Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-description-automatica-2.png)
Sau đó đặt tên project và sinh code.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 12 A screenshot of a computer Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-description-automatica-3-800x275.png)
Trường hợp tạo Task có độ ưu tiên khác nhau
Trong bài viết này mình sẽ sử dụng STM32CubeIDE để viết code. Chúng ta mở project vừa tạo ra, CubeMX đã tự sinh code.
Giải thích các code tự sinh
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 13 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-program-description-au.png)
Phần osThreadAttr_t là các thuộc tính (Attributes) của Task bao gồm: tên, stack size và mức ưu tiên.
Trong main, chúng ta sẽ thấy các hàm tạo Task và hàm osKernelStart() để bắt đầu khởi chạy RTOS.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 14 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-program-description-au-1.png)
Hàm osThreadNew nhận vào: tên function được gọi khi Task thực thi, tham số truyền vào (ở đây là NULL) và các thuộc tính của Task.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 15 A white background with black text Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-white-background-with-black-text-description-au.png)
Thêm chương trình thực thi cho các Task
Kéo xuống phía dưới chúng ta sẽ thấy phần Implement (thực thi) cho các task. Đây là nơi chúng ta sẽ viết code cho các Task của mình.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 16 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-program-description-au-2.png)
Trong phần thực thi, mỗi task in ra UART một chuỗi tương ứng.
Lưu ý: Các hàm printf/sprintf dùng khá nhiều stack. Nếu task gọi các hàm này mà để Stack Size mặc định quá nhỏ (128 word), chương trình rất dễ bị tràn stack, dẫn tới in ra kí tự rác hoặc HardFault ngẫu nhiên. Hãy tăng Stack Size cho các task in log (ví dụ 256 – 512 word) và bật CHECK_FOR_STACK_OVERFLOW trong cấu hình FreeRTOS để phát hiện lỗi sớm.
Kết quả
Nhấn Project – Build All để build rồi nhấn Run để nạp chương trình vào mạch.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 17 A screenshot of a computer Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-description-automatica-4.png)
Cắm bộ chuyển USB to UART và mở Hercules Terminal để xem kết quả.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 18 A black screen with white dots Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-black-screen-with-white-dots-description-automa.png)
Chúng ta có thể thấy, Task 3 sẽ được khởi chạy đầu tiên do có độ ưu tiên cao nhất, sau đó đến defaultTask và cuối cùng đến Task 2.
Vì mỗi task đều chờ cùng 1 khoảng thời gian bằng hàm osDelay(1000), nên quá trình này lặp đi lặp lại.
Trường hợp có chung độ ưu tiên
Trong trường hợp này chúng ta sẽ để cả 2 Task có độ ưu tiên bằng nhau là osPriorityLow.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 19 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screenshot-of-a-computer-program-description-au-3.png)
Khi hai task có cùng độ ưu tiên trong FreeRTOS, việc quản lý và chuyển đổi giữa các task đó sẽ tuân theo cơ chế Round-Robin Scheduling.
Round-Robin Scheduling
Round-Robin Scheduling là một cơ chế phân phối CPU theo vòng tròn giữa các task có cùng độ ưu tiên. Cụ thể:
- Các task có cùng độ ưu tiên sẽ được xếp vào một hàng đợi.
- Mỗi task sẽ được cấp phát CPU trong một khoảng thời gian nhất định gọi là time slice hoặc time quantum.
- Sau khi hết time slice, nếu task vẫn còn công việc phải làm, nó sẽ bị đặt lại vào cuối hàng đợi, và CPU sẽ được cấp phát cho task tiếp theo trong hàng đợi.
Giống như xếp hàng mua vé: mỗi người chỉ được đứng ở quầy 1 phút, chưa xong thì quay xuống cuối hàng đứng tiếp.
Time Slice trong FreeRTOS
Trong FreeRTOS, time slice bằng đúng 1 chu kỳ tick, được xác định bởi macro configTICK_RATE_HZ trong tệp cấu hình FreeRTOS.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 20 word image 10968 19](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-19.png)
Với giá trị này, mỗi tick sẽ xảy ra mỗi 1ms. FreeRTOS sẽ chuyển đổi giữa các task cùng độ ưu tiên đang ở trạng thái Ready mỗi khi một tick xảy ra (khi configUSE_TIME_SLICING = 1, mặc định).
Ảnh hưởng đến Task 2 và Task 3
Khi tạo Task 2 và Task 3 có cùng độ ưu tiên, chúng sẽ được thực thi luân phiên dựa trên cơ chế Round-Robin Scheduling. Giả sử cả hai task đều có độ ưu tiên osPriorityLow và đều sẵn sàng để chạy, chúng sẽ luân phiên được cấp phát CPU.
Kết quả
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 21 A screen shot of a computer Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-screen-shot-of-a-computer-description-automatic.png)
defaultTask (ưu tiên Normal) sẽ in thông báo trước, mỗi giây một lần.
myTask02 và myTask03 (cùng ưu tiên thấp) sẽ luân phiên in thông báo mỗi giây một lần sau khi defaultTask chạy. Trong lần chạy này, Task 2 in ra trước Task 3. Thứ tự giữa các task cùng độ ưu tiên phụ thuộc vào thứ tự tạo task và vị trí của chúng trong danh sách Ready, nên khi thiết kế chương trình các bạn không nên dựa vào thứ tự này.
Trường hợp các task có thời gian xử lý khác nhau
Chúng ta giữ nguyên độ ưu tiên của Task 2, Task 3 là Low.
Tuy nhiên sửa thời gian đợi của Task 2 là 200ms, Task 3 là 500ms.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 22 word image 10968 21](https://khuenguyencreator.com/wp-content/uploads/2024/07/word-image-10968-21.png)
Kết quả như sau:
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 23 A black screen with white dots Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/07/a-black-screen-with-white-dots-description-automa-1.png)
Giải thích: defaultTask có độ ưu tiên Normal nên sẽ chạy đầu tiên, Task 2 và 3 chạy ngay sau đó. Sau đó Task 2 cứ 200ms in 1 lần, Task 3 cứ 500ms in 1 lần, nên trong khoảng thời gian Task 3 in 1 dòng thì Task 2 in được khoảng 2 – 3 dòng.
Kết luận
Việc tạo Task và hiểu cơ chế Scheduling rất quan trọng khi học hệ điều hành RTOS, chúng giúp chúng ta có cái nhìn chính xác về quá trình khởi tạo và chạy các Task.
Để đảm bảo các Task được chạy một cách tối ưu, thông thường người ta sẽ thiết kế Timing diagram cho chương trình của mình. Điều này sẽ giúp các luồng được quản lý chặt chẽ hơn.
![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 24 Accurate, High Resolution Absolute Timing on the PC Platform © 2001 ...](https://khuenguyencreator.com/wp-content/uploads/2024/07/accurate-high-resolution-absolute-timing-on-the-p.gif)
Hi vọng sau bài này, các bạn đã hiểu cách tạo Task và lập lịch cho Task, hãy cùng chuyển sang bài tiếp theo trong Serie này nhé.

![[STM32 - RTOS] Bài 1: Tạo Task và Scheduling task sử dụng STM32 HAL 1 STM32 RTOS Tạo Task và Scheduling Task](https://khuenguyencreator.com/wp-content/uploads/2024/07/STM32-RTOS-Tao-Task-va-Scheduling-Task.jpg)