[ESP32] 5. Tác vụ (Tasks) trong FreeRTOS

Trong FreeRTOS, tác vụ (task) là đơn vị cơ bản của chương trình thực thi. Mỗi tác vụ là một chương trình con độc lập, có thể chạy đồng thời với các tác vụ khác. Hiểu rõ về cách tạo, quản lý và lập lịch tác vụ trong FreeRTOS sẽ giúp bạn xây dựng các ứng dụng đa tác vụ hiệu quả trên ESP32, tận dụng tối đa khả năng đa nhân của nó.

5.1. Tạo và quản lý tác vụ

Giải thích chi tiết:

  • Tạo tác vụ: Sử dụng hàm xTaskCreate() để tạo một tác vụ mới.
  • Mỗi tác vụ có stack riêng và bộ đếm chương trình (program counter) riêng.
  • Tác vụ có thể ở một trong các trạng thái: Running, Ready, Blocked, Suspended.

Ví dụ đơn giản:

void ledTask(void *pvParameters) {
    while(1) {
        digitalWrite(LED_PIN, HIGH);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        digitalWrite(LED_PIN, LOW);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void setup() {
    pinMode(LED_PIN, OUTPUT);
    xTaskCreate(
        ledTask,    // Hàm tác vụ
        "LED Task", // Tên tác vụ
        1000,       // Kích thước stack (words)
        NULL,       // Tham số
        1,          // Ưu tiên
        NULL        // Handle tác vụ
    );
}

Trong ví dụ này, chúng ta tạo một tác vụ đơn giản để nhấp nháy LED.

5.2. Ưu tiên tác vụ

Giải thích chi tiết:

  • Mỗi tác vụ được gán một mức ưu tiên (0 đến (configMAX_PRIORITIES - 1)).
  • Tác vụ có ưu tiên cao hơn sẽ được thực thi trước.
  • FreeRTOS sử dụng thuật toán lập lịch ưu tiên với time-slicing.

Ví dụ đơn giản:

void highPriorityTask(void *pvParameters) {
    while(1) {
        Serial.println("High priority task running");
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void lowPriorityTask(void *pvParameters) {
    while(1) {
        Serial.println("Low priority task running");
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void setup() {
    Serial.begin(115200);
    xTaskCreate(highPriorityTask, "High Priority", 1000, NULL, 2, NULL);
    xTaskCreate(lowPriorityTask, "Low Priority", 1000, NULL, 1, NULL);
}

Trong ví dụ này, tác vụ có ưu tiên cao hơn sẽ được thực thi thường xuyên hơn nếu cả hai tác vụ đều sẵn sàng chạy.

5.3. Lập lịch tác vụ

Giải thích chi tiết:

  • FreeRTOS sử dụng bộ lập lịch để quyết định tác vụ nào sẽ chạy tiếp theo.
  • Lập lịch dựa trên ưu tiên và trạng thái của tác vụ.
  • Có thể sử dụng các API như vTaskDelay(), vTaskDelayUntil() để kiểm soát thời gian chạy của tác vụ.

Ví dụ đơn giản:

void periodicTask(void *pvParameters) {
    TickType_t xLastWakeTime;
    const TickType_t xPeriod = pdMS_TO_TICKS(2000);  // 2 giây

    xLastWakeTime = xTaskGetTickCount();

    while(1) {
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
        Serial.println("Periodic task running");
    }
}

void setup() {
    Serial.begin(115200);
    xTaskCreate(periodicTask, "Periodic Task", 1000, NULL, 1, NULL);
}

Trong ví dụ này, chúng ta sử dụng vTaskDelayUntil() để tạo một tác vụ chạy chính xác mỗi 2 giây, bất kể thời gian xử lý của tác vụ là bao nhiêu.

Lưu ý quan trọng:

  • Khi tạo tác vụ, cần cân nhắc kỹ về kích thước stack để tránh tràn stack.
  • Tránh sử dụng vòng lặp vô hạn mà không có delay, vì điều này có thể ngăn các tác vụ khác chạy.
  • Sử dụng ưu tiên một cách thận trọng để tránh tình trạng "starvation" (tác vụ ưu tiên thấp không bao giờ được chạy).
  • Khi sử dụng vTaskDelay() hoặc vTaskDelayUntil(), nhớ rằng thời gian delay là số tick tối thiểu, không phải là thời gian chính xác.