Các hàm task
Task (tác vụ) là những hàm được viết bằng C. Các task phải tuân theo prototype như dưới đây. Prototype này định nghĩa một hàm có một tham số là con trỏ void (a void pointer parameter) và trả về void.
void vATaskFunction( void * pvParameters );
Code Block 1.1. prototype của một hàm task
Mỗi task là một chương trình (program) có quyền riêng của nó. Thông thường, task có một đầu vô, sau đó sẽ chạy vĩnh viễn trong một vòng lặp vô tận và không thoát ra (exit). Code Block 1.2 diễn tả cấu trúc của một task thông thường.
void vATaskFunction( void * pvParameters )
{
/*
* Stack-allocated variables can be declared normally when inside a function.
* Each instance of a task created using this example function will have its
* own separate instance of lStackVariable allocated on the task's stack.
*/
long lStackVariable = 0;
/*
* In contrast to stack allocated variables, variables declared with the
`static`
* keyword are allocated to a specific location in memory by the linker.
* This means that all tasks calling vATaskFunction will share the same
* instance of lStaticVariable.
*/
static long lStaticVariable = 0;
for( ;; )
{
/* The code to implement the task functionality will go here. */
}
/*
* If the task implementation ever exits the above loop, then the task
* must be deleted before reaching the end of its implementing function.
* When NULL is passed as a parameter to the vTaskDelete() API function,
* this indicates that the task to be deleted is the calling (this) task.
*/
vTaskDelete( NULL );
}
Code Block 1.2. Cấu trúc của một task thông thường
Một FreeRTOS task phải là một hàm không được phép trả về (return). Nó không được chứa lệnh return và không được phép thực thi vượt khỏi phạm vi hàm của nó. Nếu một task không cần sử dụng nữa, nó nên xóa một cách tường minh như được mô tả trong Code block 1.2.
Một định nghĩa (definition) hàm task có thể được xài để tạo ra nhiều task, với mỗi task được tạo ra là một instance thực thi riêng biệt. Mỗi instance đều sở hữu một stack riêng và đồng thời sở hữu bản sao của mọi biến tự động (automatic variable) được định nghĩa bên trong task đó.
Các Trạng Thái Task Cấp Cao (Top Level Task States)
Một ứng dụng thường có nhiều task. Nếu bộ xử lý (processor) chạy ứng dụng chỉ có một lõi (core) thì chỉ một task được thực thi trong một khoảng thời gian cho phép. Có nghĩa là mỗi task có thể tồn tại ở hai trạng thái: Running và Not Running. Model đơn giản hóa này được xem xét đầu tiên. Sau đó chúng ta sẽ nói về một số trạng thái con (sub-state) của trạng thái Not Running.
Một task ở trạng thái Running khi bộ xử lý đang thực thi code của task. Khi task ở trạng thái Not Running, thì task được tạm dừng (pause) và trạng thái của nó được lưu (save) để nó có thể khôi phục lại (resume) sự thực thi ở lần tiếp theo, khi scheduler quyết định nó được vô (enter) trạng thái Running. Khi task resume sự thực thi, nó tiếp tục tại dòng lệnh mà nó sắp thực thi trước khi thoát khỏi trạng thái Running.

Hình 1.1. Trạng thái task cấp cao và sự chuyển đổi
Sự chuyển đổi task từ trạng thái Not Running sang Running được gọi là “switch in” hay ”swap in”. Ngược lại, quá trình task chuyển từ trạng thái Running sang Not Running được gọi là “switch out” hay “swap out”. FreeRTOS scheduler là entity duy nhất có thể điều khiển chuyển trạng thái một task vào và ra trạng thái Running.
Tạo Task (Task Creation)
Hàm API xTaskCreate()
Task được tạo bằng hàm API xTashCreate() của FreeRTOS. Đây có vẻ là hàm API phức tạp nhất, không may nó lại là hàm phải học đầu tiên. Task là phần cơ bản quan trọng nhất trong một hệ thống đa tác vụ (multitasking system) nên cần hiểu về nó đầu tiên.
Prototype của hàm API xTaskCreate() như sau.
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
configSTACK_DEPTH_TYPE usStackDepth,
void * pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pxCreatedTask );
Code Block 1.3. Cấu trúc của một task thông thường
Tham số và giá trị trả về của xTaskCreate():
pvTaskCode
Task là những hàm viết bằng C, những hàm này không bao giờ kết thúc, chúng thường được viết bằng một vòng lặp vô tận. Tham số pvTaskCode chỉ là một pointer trỏ tới hàm viết task (chỉ cần tên của hàm)
pcName
Tên mô tả task. FreeRTOS không cần dùng tham số này, thực ra nó giúp việc debug dễ hơn. Định danh task bằng một cái tên mà con người đọc được thì dễ nhận thấy hơn bằng handle của nó (là một con số).
Macro configMAX_TASK_NAME_LEN định nghĩa chiều dài lớn nhất của tên task, bao gồm ký tự kết thúc NULL. Đưa vô một string dài hơn giá trị lớn nhất sẽ khiến cho string tự động bị cắt mất.
usStackDepth
Mỗi task sở hữu một vùng stack riêng biệt được cấp bởi kernel khi task được tạo. Giá trị usStackDepth báo cho kernel cần tạo stack lớn bao nhiêu.
Giá trị này bằng số word mà stack có thể giữ, chứ không phải số byte. Ví dụ, nếu stack rộng 32-bit và usStackDepth là 128, thì xTaskCreate() cấp 512 byte của không gian stack (128 * 4).
configSTACK_DEPTH_TYPE là macro cho phép nhà phát triển chỉ định kiểu dữ liệu để giữ kích thước stack. configSTACK_DEPTH_TYPE mặc định là uint16_t nếu không được định nghĩa, do đó #define configSTACK_DEPTH_TYPE cho unsigned long hoặc size_t trong FreeRTOSConfig.h nếu stack depth nhân với stack width là lớn hơn 65535 (số 16-bit lớn nhất).
Stack của Ilde task có kích thước được định nghĩa bằng macro configMINIMAL_STACK_SIZE. Đây cũng là định nghĩa cho giá trị stack nhỏ nhất. Nếu task của bạn dùng nhiều không gian hơn, bạn nên gán một giá trị lớn hơn.
Không có cách xác định đúng không gian stack cho task dễ dàng. Người ta có thể tính toán, nhưng hầu hết thường chỉ gán đại một giá trị mà họ nghĩ là phù hợp, sau đó chạy thử các chức năng của FreeRTOS để xem không gian có được cấp phát đủ không và RAM không bị lãng phí vô nghĩa.
pvParameters
Hàm viết cho task chấp nhận một tham số void pointer (void *). pvParameters là giá trị được truyền (pass) vô task sử dụng tham số đó.
uxPriority
Định nghĩa độ ưu tiên mà task sẽ thực thi. Độ ưu tiên có thể gán từ 0, là độ ưu tiên thấp nhất, tới (configMAX_PRIORITIES - 1), là độ ưu tiên cao nhất.
configMAX_PRIORITIES là một macro dành cho người dùng định nghĩa. Không có giới hạn số lượng mức độ ưu tiên khả dĩ, ngoại trừ giới hạn do kiểu dữ liệu được dùng và RAM có sẵn trong vi điều khiển của bạn. Nhưng bạn nên dùng số mức độ ưu tiên thấp nhất để tránh lãng phí RAM.
Nếu truyền giá trị uxPriority lớn hơn (configMAX_PRIORITIES - 1) sẽ khiến độ ưu tiên được gán cho task bị giới hạn một cách âm thầm ở giá trị tối đa hợp lệ.
pxCreatedTask
Pointer trỏ tới một vùng lưu một handle của task được tạo. Handle này có thể được dùng bởi các lệnh gọi API tương lai, ví dụ như đổi độ ưu tiên của task hoặc xóa task.
pxCreatedTask là một tham số tùy chọn và được set NULL nếu handle của task không cần thiết.
- Các giá trị trả về
pdPASS
Task đã được tạo thành công.
pdFAIL
Không có đủ bộ nhớ heap để tạo task.
Ví dụ 1.1 Tạo task
Ví dụ dưới đây mô tả các bước để tạo hai task đơn giản và bắt đầu chạy (start) những task mới này. Các task chỉ đơn giản là in ra một chuỗi ký tự định kỳ, sử dụng một vòng lặp null thô lỗ (a crude null loop) để tạo khoảng thời gian trễ (delay). Cả hai task đều được tạo với cùng độ ưu tiên và nội dung giống nhau ngoại trừ chuỗi ký tự được in ra.
void vTask1( void * pvParameters )
{
/* ulCount is declared volatile to ensure it is not optimized out. */
volatile unsigned long ulCount;
for( ;; )
{
/* Print out the name of the current task task. */
vPrintLine( "Task 1 is running" );
/* Delay for a period. */
for( ulCount = 0; ulCount < mainDELAY_LOOP_COUNT; ulCount++ )
{
/*
* This loop is just a very crude delay implementation. There is
* nothing to do in here. Later examples will replace this crude* loop with a proper delay/sleep function.
*/
}
}
}
Code Block 1.4. Implementation của task thứ nhất dùng trong Ví dụ 1.1
void vTask2( void * pvParameters )
{
/* ulCount is declared volatile to ensure it is not optimized out. */
volatile unsigned long ulCount;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintLine( "Task 2 is running" );
/* Delay for a period. */
for( ulCount = 0; ulCount < mainDELAY_LOOP_COUNT; ulCount++ )
{
/*
* This loop is just a very crude delay implementation. There is
* nothing to do in here. Later examples will replace this crude
* loop with a proper delay/sleep function.
*/
}
}
}
Code Block 1.5. Implementation của task thứ hai dùng trong Ví dụ 1.1
Hàm main() tạo hai task trước khi bắt đầu chạy scheduler – xem Code Block 1.6 là nội dung của hàm này.
int main( void )
{
/*
* Variables declared here may no longer exist after starting the FreeRTOS
* scheduler. Do not attempt to access variables declared on the stack used
* by main() from tasks.
*/
/*
* Create one of the two tasks. Note that a real application should check
* the return value of the xTaskCreate() call to ensure the task was
* created successfully.
*/
xTaskCreate( vTask1, /* Pointer to the function that implements the task.*/
"Task 1",/* Text name for the task. */
1000, /* Stack depth in words. */
NULL, /* This example does not use the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* This example does not use the task handle. */
/* Create the other task in exactly the same way and at the same priority.*/
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();
/*
* If all is well main() will not reach here because the scheduler will now
* be running the created tasks. If main() does reach here then there was
* not enough heap memory to create either the idle or timer tasks
* (described later in this book).
*/
for( ;; );
}
Code Block 1.6. Bắt đầu chạy task của Ví dụ 1.1
Thực thi đoạn code ví dụ cho ra output như Hình 1.2.
C:\Temp>rtosdemo Task 1 is running Task 2 is running Task 1 is running Task 2 is running Task 1 is running Task 2 is running Task 1 is running Task 2 is running Task 1 is running Task 2 is running Task 1 is running Task 2 is running Task 1 is running Task 2 is running
Hình 1.2. Output khi thực thi Ví dụ 1.1
Kết trên cho thấy mỗi task thay phiên nhau in ra một dòng tin nhắn đầy đủ trước khi task tiếp theo thực thi. Đây là một kết quả mô phỏng chạy trên FreeRTOS Windows. Bộ mô phỏng trên Windows không thật sự là real-time. Ghi ra màn hình console của Windows tương đối lâu hơn vì phải qua một chuỗi lệnh gọi hệ thống Windows. Thực thi code tương tự trên một hệ thống nhúng thật với hàm print nhanh và không chặn (non-blocking) sẽ cho kết quả là mỗi task in ra dòng tin nhắn của nó nhiều lần trước khi bị switch out để cho task khác chạy.
Hình 1.2 cho thấy hai task xuất hiện thực thi đồng thời. Tuy nhiên, cả hai task đều thực thi trên cùng core của vi xử lý, nên thật ra không phải như vậy. Cả hai task đều đi vào và thoát ra trạng thái Running rất nhanh. Chúng đều chạy với độ ưu tiên bằng nhau và do đó chia sẻ khoảng thời gian bằng nhau trên cùng một core vi xử lý. Hình 1.3 dưới đây vẽ chính xác đồ thị thực thi của chúng.
Mũi tên dài dưới đáy Hình 1.3 biểu thị thời gian trôi từ t1 trở đi. Đường thẳng màu biểu thị task nào đang thực thi tại mỗi thời điểm. Ví dụ như task 1 thực thi giữa khoảng thời gian t1 và t2.
Chỉ có duy nhất một task ở trạng thái Running ở bất kỳ thời điểm nào. Do đó, khi một task đi vô trạng thái Running (task được switch in) thì các task còn lại đi vô trạng thái Not Running (task được switch out).

Hình 1.3. Đồ thị quá trình thực thi của hai task trong Ví dụ 1.1
Ví dụ 1.1 tạo hai task từ bên trong hàm main(), trước khi bắt đầu scheduler. Người ta còn có thế tạo task từ bên trong một task khác. Ví dụ, Task 2 có thể được tạo bên trong Task 1, như đoạn code dưới đây.
void vTask1( void * pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile unsigned long ul; /* volatile to ensure ul is not optimized away. */
/*
* If this task code is executing then the scheduler must already have* been started. Create the other task before entering the infinite loop.
*/
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
for( ;; )
{
/* Print out the name of this task. */
vPrintLine( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/*
* This loop is just a very crude delay implementation. There is
* nothing to do in here. Later examples will replace this crude
* loop with a proper delay/sleep function.
*/
}
}
}
Code Block 1.7. Tạo một task từ bên trong một task khác sau khi scheduler đã bắt đầu
Ví dụ 1.2 Sử dụng tham số của task (task parameter)
Hai task đã tạo ở Ví dụ 1.1 là hầu như giống nhau, chỉ có chuỗi ký tự in ra là khác nhau. Nếu bạn tạo hai instance của một task implementation, và sử dụng task parameter để truyền string vào mỗi instance, thì sẽ bỏ được sự trùng lặp.
Ví dụ 1.2 thay thế hai hàm task dùng ở Ví dụ 1.1 bằng một hàm task gọi là vTaskFunction(), như ở Code Block 1.8. Chú ý cách task parameter được ép kiểu sang một con trỏ tới char để nhận được string mà task sẽ in ra.
void vTaskFunction( void * pvParameters )
{
char *pcTaskName;
volatile unsigned long ul; /* volatile to ensure ul is not optimized away. */
/*
* The string to print out is passed in via the parameter. Cast this to a
* character pointer.
*/
pcTaskName = ( char * ) pvParameters;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintLine( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/*
* This loop is just a very crude delay implementation. There is
* nothing to do in here. Later exercises will replace this crude
* loop with a proper delay/sleep function.
*/
}
}
}
Code Block 1.8. Hàm task dùng để tạo hai task cho Ví dụ 1.2
Code Block 1.9 tạo hai instance của task được implement bằng hàm vTaskFunction(), sử dụng task parameter để truyền vô chuỗi ký tự khác nhau cho mỗi task. Cả hai task đều thực thi độc lập dưới sự điều khiển của FreeRTOS scheduler và với stack riêng của chúng, mỗi stack chứa một bản sao riêng biệt của biến pcTaskName và ul.
/*
* Define the strings that will be passed in as the task parameters. These are
* defined const and not on the stack used by main() to ensure they remain
* valid when the tasks are executing.
*/
static const char * pcTextForTask1 = "Task 1 is running";
static const char * pcTextForTask2 = "Task 2 is running";
int main( void )
{
/*
* Variables declared here may no longer exist after starting the FreeRTOS
* scheduler. Do not attempt to access variables declared on the stack used
* by main() from tasks.
*/
/* Create one of the two tasks. */
xTaskCreate( vTaskFunction, /* Pointer to the function that
implements the task. */
"Task 1", /* Text name for the task. This is to
facilitate debugging only. */
1000, /* Stack depth - small
microcontrollers
will use much less stack than
this.*/
( void * ) pcTextForTask1, /* Pass the text to be printed into
the task using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* The task handle is not used in
this example. */
/*
* Create the other task in exactly the same way. Note this time that
* multiple tasks are being created from the SAME task implementation
* (vTaskFunction). Only the value passed in the parameter is different.
* Two instances of the same task definition are being created.
*/
xTaskCreate( vTaskFunction,
"Task 2",
1000,
( void * ) pcTextForTask2,
1,
NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();
/*
* If all is well main() will not reach here because the scheduler will
* now be running the created tasks. If main() does reach here then there
* was not enough heap memory to create either the idle or timer tasks
* (described later in this book). Chapter 3 provides more information on
* heap memory management.
*/
for( ;; )
{
}
}
Code Block 1.9. Hàm main() cho Ví dụ 1.2
Output của Ví dụ 1.2 là giống hệt như của Ví dụ 1 ở Hình 1.2.
Độ Ưu Tiên Task (Task Priorities)
FreeRTOS scheduler luôn luôn đảm bảo task có độ ưu tiên cao nhất (the highest priority task) là task được chọn đi vô trạng thái Running. Những task có độ ưu tiên bằng nhau thì được chuyển vô và ra khỏi trạng thái Running thay phiên nhau.
Tham số uxPriority của hàm API tạo task dùng để cài đặt giá trị độ ưu tiên ban đầu cho task đó. Hàm API vTaskPrioritySet() dùng để thay đổi độ ưu tiên của một task sau khi nó được tạo.
Macro configMAX_PRIORITIES là hằng số cấu hình khi biên dịch (compile-time) cài đặt số mức độ ưu tiên có thể sử dụng. Giá trị số nhỏ biểu thị độ ưu tiên thấp, với độ ưu tiên 0 nghĩa là độ ưu tiên thấp nhất, độ ưu tiên có giá trị từ 0 tới (configMAX_PRIORITIES – 1). Mọi task đều có thể có độ ưu tiên bằng nhau.
FreeRTOS scheduler có implement hai thuật toán dùng để chọn trạng thái Running của task và giá trị configMAX_PRIORITIES lớn nhất có thể cho phép (the maximum allowable value) phụ thuộc theo loại implement được chọn:
Bộ Lập Lịch Phổ Biến (Generic Scheduler)
Generic scheduler được viết bằng C và sử dụng trong tất cả bản port kiến trúc FreeRTOS. FreeRTOS không áp đặt giới hạn trên configMAX_PRIORITIES. Thông thường, lời khuyên là nên tối thiểu configMAX_PRIORITIES vì càng cần nhiều độ ưu tiên thì càng cần nhiều RAM, dẫn đến kết quả là thời gian thực thi trong trường hợp xấu nhất dài hơn.
Bộ Lập Lịch Kiến Trúc Tối Ưu Hóa (Architecture-Optimized Scheduler)
Kiến trúc tối ưu hóa được viết bằng assembly code đặc trưng theo kiến trúc vi xử lý và có hiệu suất cao hơn cách viết bằng C, thời gian thực thi trong trường hợp xấu nhất là giống nhau với mọi giá trị configMAX_PRIORITIES.
Kiến trúc tối ưu hóa áp đặt giá trị cực đại configMAX_PRIORITIES là 32 cho kiến trúc 32-bit và 64 cho kiến trúc 64-bit. Giống như phương pháp generic, lời khuyên là giữ configMAX_PRIORITIES ở giá trị tối thiểu thực tế bởi vì giá trị cao hơn cần nhiều RAM hơn.
Đặt configUSE_PORT_optimized_TASK_SELECTION lên 1 trong FreeRTOSConfig.h để sử dụng kiến trúc tối ưu hóa, hoặc 0 để sử dụng loại phổ biến. Không phải tất cả bản FreeRTOS port đều có thực hiện kiến trúc tối ưu hóa. Những trường hợp đó mặc định macro này là 1 nếu nó không được define. Còn những trường hợp không có, mặc định macro này là 0 nếu nó không được define.
Đo Thời Gian và Tick Interrupt
(đang viết)