[ESP32] 9. Timeouts và Interrupts

Timeouts và Interrupts là hai khía cạnh quan trọng trong lập trình nhúng và đa nhân, đặc biệt là trên các hệ thống như ESP32. Hiểu và áp dụng đúng các kỹ thuật xử lý timeout và interrupt sẽ giúp bạn tạo ra các ứng dụng ESP32 phản ứng nhanh, ổn định và hiệu quả trong môi trường đa nhân.

9.1. Software Timers

Giải thích chi tiết:
Software Timers trong FreeRTOS cho phép thực hiện các hành động sau một khoảng thời gian cụ thể mà không cần tạo một tác vụ riêng biệt.

Đặc điểm:

  • Không chiếm CPU khi không hoạt động.
  • Có thể là one-shot (chạy một lần) hoặc auto-reload (chạy lặp lại).
  • Chạy trong ngữ cảnh của một tác vụ hệ thống, không phải trong ngữ cảnh interrupt.

Ví dụ:

TimerHandle_t xTimer;

void vTimerCallback(TimerHandle_t xTimer) {
    // Hành động cần thực hiện khi timer hết hạn
    Serial.println("Timer expired!");
}

void setup() {
    Serial.begin(115200);
    
    // Tạo một timer
    xTimer = xTimerCreate(
        "MyTimer",           // Tên timer
        pdMS_TO_TICKS(1000), // Thời gian (1 giây)
        pdTRUE,              // Auto-reload
        (void *)0,           // Timer ID
        vTimerCallback       // Callback function
    );

    // Khởi động timer
    if (xTimer != NULL) {
        xTimerStart(xTimer, 0);
    }
}

9.2. Hardware Interrupts

Giải thích chi tiết:
Hardware Interrupts là cơ chế cho phép phần cứng ngắt quá trình thực thi bình thường của CPU để xử lý một sự kiện quan trọng.

Đặc điểm:

  • Phản ứng nhanh với các sự kiện bên ngoài.
  • Thực thi trong ngữ cảnh interrupt, không phải trong ngữ cảnh tác vụ.
  • Cần được xử lý nhanh chóng để không ảnh hưởng đến hiệu suất hệ thống.

Ví dụ:

const int interruptPin = 2;
volatile int interruptCounter = 0;

void IRAM_ATTR handleInterrupt() {
    interruptCounter++;
}

void setup() {
    Serial.begin(115200);
    pinMode(interruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);
}

void loop() {
    if (interruptCounter > 0) {
        Serial.printf("Interrupt occurred %d times\n", interruptCounter);
        interruptCounter = 0;
    }
    delay(1000);
}

9.3. Xử lý Interrupt an toàn trong môi trường đa nhân

Giải thích chi tiết:
Xử lý interrupt an toàn trong môi trường đa nhân đòi hỏi cẩn trọng để tránh xung đột với các tác vụ khác và đảm bảo tính nhất quán của dữ liệu.

Các nguyên tắc quan trọng:

  1. Giữ ISR (Interrupt Service Routine) ngắn gọn và nhanh chóng.
  2. Tránh sử dụng các hàm API của FreeRTOS không an toàn với interrupt trong ISR.
  3. Sử dụng kỹ thuật "deferred processing" để xử lý công việc dài hơn.

Ví dụ về xử lý interrupt an toàn:

const int interruptPin = 2;
volatile bool interruptOccurred = false;
SemaphoreHandle_t xSemaphore = NULL;

void IRAM_ATTR handleInterrupt() {
    interruptOccurred = true;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
    if (xHigherPriorityTaskWoken) {
        portYIELD_FROM_ISR();
    }
}

void interruptHandlerTask(void *pvParameters) {
    while (1) {
        if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
            if (interruptOccurred) {
                // Xử lý interrupt
                Serial.println("Interrupt processed");
                interruptOccurred = false;
            }
        }
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(interruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);
    
    xSemaphore = xSemaphoreCreateBinary();
    xTaskCreate(interruptHandlerTask, "InterruptHandler", 2048, NULL, 3, NULL);
}

Lưu ý quan trọng:

  1. Sử dụng xSemaphoreGiveFromISR()xTaskNotifyFromISR() thay vì các phiên bản thông thường trong ISR.
  2. Luôn kiểm tra và xử lý xHigherPriorityTaskWoken để đảm bảo lập lịch đúng.
  3. Sử dụng portENTER_CRITICAL()portEXIT_CRITICAL() để bảo vệ các đoạn code ngắn cần atomic access.
  4. Cân nhắc sử dụng các biến volatile cho dữ liệu được truy cập bởi cả ISR và tác vụ thông thường.
  5. Tránh sử dụng vTaskDelay() hoặc các hàm có thể block trong ISR.