Mã nguồn ví dụ#
git clone https://github.com/esp8266vn/esp-rtos-basic-task.git
Makefile#
Cấu trúc của Makefile trong ví dụ này cũng tương tự Makefile cho dự án phức tạp sử dụng NONOS-SDK. Tuy nhiên, để sử dụng cho RTOS SDK thì một số biến trong Makefile cần thay đổi như sau:
# Đường dẫn tới RTOS-SDK
SDK_BASE ?= C:/Espressif/ESP8266_RTOS_SDK
...
# Thư viện sử dụng
SDK_LIBS = gcc hal phy pp net80211 wpa crypto main freertos lwip minic smartconfig
...
# Thư mục đưa vào include
SDK_INC = extra_include include include/espressif include/json include/udhcp include/lwip include/lwip/lwip include/lwip/ipv4 include/lwip/ipv6
...
# Cờ khi biên dịch C
CFLAGS = -g -save-temps -std=gnu90 -Os -Wpointer-arith -Wundef -Werror \
-Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals \
-mno-serialize-volatile -D__ets__ -DICACHE_FLASH -DBUID_TIME=\"$(DATETIME)\"
...
# Comment-out dòng này:
# ifeq ("$(USE_OPENSDK)","yes")
# CFLAGS += -DUSE_OPENSDK
# else
# CFLAGS += -D_STDINT_H
# endif
Cấu trúc chương trình user_main.c
#
Chương trình có nhiệm vụ tạo ra 2 task riêng biệt, một dùng để nháy LED có chu kỳ 200 ticks
, task còn lại để in thông tin ra UART0 với chu kỳ 1000 ticks
.
Muốn vậy, trước hết phải tạo ra 2 hàm con, tuân theo tiền khai báo kiểu TaskFunction_t
có dạng void vTaskCode( void * pvParameters )
cho task LED và UART:
void task_led(void *pvParameters)
{
for(;;){
vTaskDelay(100);
GPIO_OUTPUT_SET(LED_GPIO, led_state);
led_state ^=1;
}
}
void task_printf(void *pvParameters)
{
for(;;){
printf("task_printf\n");
vTaskDelay(500);
}
}
Lưu ý
- Bên trong hàm con phải thực hiện vòng lặp vô tận (infinite loop), không được return.
- Ngoài ra, task có thể tự hủy (delete) chính nó khi cần (bằng hàm
vTaskDelete( TaskHandle_t xTask )
- xem ví dụ bên dưới)
Tick
Tick
là hành động khi timer ngắt định kỳ dùng trong nhân FreeRTOS để MCU thực hiện chuyển ngữ cảnh (context) khi chuyển qua lại giữa các task với nhau, khái niệm thực hiện tác vụ song song, đồng thời, cùng lúc chỉ mang tính tương đối, vì RTOS thực hiện điều này 1 cách tuần tự.- Giá trị của
tick
không phải lúc nào cũng là 1ms, tùy thuộc vào cấu hình khi port FreeRTOS lên từng MCU khác nhau (trong trường hợp,configTICK_RATE_HZ
của RTOS-SDK có giá trị là 100). Có thể kiểm tra chính xác chu kỳ ms củatick
bằng macroportTICK_RATE_MS
–> Để delay chính xáct(ms)
thì tham số truyền chovTaskDelay
làt/portTICK_RATE_MS
Trong hàm user_init()
của ESP8266, sau khi khởi tạo các giá trị cần thiết cho UART và chân GPIO Ouput để nháy LED, sử dụng hàm xTaskCreate
trong FreeRTOS để tạo task thực thi 2 hàm con này, cú pháp xTaskCreate
:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
Trong đó:
pvTaskCode
: trỏ đến hàm con cần thực hiện khi tạo taskpcName
: chuỗi mô tả tên của task nàyusStackDepth
: độ lớn của con trỏ ngăn xếp, chọn sao cho lớn hơn độ lớn của con trỏ ngăn xếp khi thực hiện hàm con, ví dụ như khi hàm con gọi càng nhiều hàm khác bên trong lồng nhau, khi đó độ lớn này càng tăng.pvParameters
: trỏ đến tham số cần truyền vào hàm con khi task khởi tạo.uxPriority
: mức độ ưu tiên của task.pxCreatedTask
: trỏ đến biến kiểuTaskHandle_t
, biến sẽ được gán sau khi gọixTaskCreate
thành công, xem như ID để phân biệt các task với nhau, và được sử dụng cho nhiều mục đích, ví dụ như xóa task (dùng hàmvTaskDelete( TaskHandle_t xTask )
) Sử dụngxTaskCreate
để tạo task LED và UART như sau:
xTaskCreate(task_led, "task_led", 256, NULL, 2, NULL);
xTaskCreate(task_printf, "task_printf", 256, NULL, 2, NULL);
Biên dịch và chạy chương trình#
make clean
make
make flash
Nâng cao một chút#
- In vài thông tin cơ bản về
portTICK_RATE_MS
vàconfigMAX_PRIORITIES
- Ví dụ về
vTaskDelete()
chotask_printf
git checkout task_delete
make clean
make
make flash
Giới thiệu về tác vụ (task) trong FreeRTOS#
Các trạng thái (states) của task#
Có 4 trạng thái: Running, Ready, Blocked và Suspended
- Running: là trạng thái task đang được MCU thực thi thực sự, vì thế trong một thời điểm chỉ có duy nhất một task ở trạng thái running.
- Ready: là trạng thái task đã sẵn sàng để thực thi (không bị blocked hoặc suspended) nhưng đang không được MCU thực thi bởi vì MCU đang thực thi một task khác.
- Blocked: task đang bị blocked nếu nó đang đợi sự kiện (event) bên ngoài hoặc sự kiện thời gian. Ví dụ: khi task gọi hàm
vTaskDelay()
thì nó sẽ bị blocked cho đến khi hết thời gian delay (sự kiện thời gian). Hoặc task có thể bị blocked để đợi một hàng đợi (queue), semaphore, notification… nào đó. Thông thường, task bị blocked trong khoảng thòi gian quá hạntimeout
cho trước, vì thế task sẽ luôn được unblocked (nếu có sự kiện bên ngoài) hoặctimeout
nếu hết thời gian chờ. - Suspended: Cũng giống như trạng thái blocked nhưng không có thời gian
timeout
, vì vậy chỉ có thểenter
hoặcexit
khỏi trạng thái suspended bởi hàm gọi từ bên ngoài tương ứng làvTaskSuspend()
vàxTaskResume()
Các mức ưu tiên (priorities) của task#
Mỗi task được tạo ra với mức ưu tiên được gán từ 0 đến giá trị (configMAX_PRIORITIES - 1)
, với configMAX_PRIORITIES
là giá trị được định nghĩa trong FreeRTOSConfig.h
(với bản RTOS-SDK v1.4 đang sử dụng, configMAX_PRIORITIES
là 15)
Task đang ở trạng thái ready có ưu tiên cao hơn sẽ được chọn để thực thi (chuyển sang running) trong mỗi lần tick
Nếu các task có cùng mức ưu tiên? Trong trường hợp RTOS sẽ chia đều ra xử lý (do giá trị configUSE_TIME_SLICING
đã được định nghĩa là 1 trong bản RTOS-SDK này)