Một số lỗi thường gặp khi sử dụng ngắt trong lập trình nhúng

Trong chương trình nhúng, Interrupt là quá trình dừng chương trình chính đang chạy để ưu tiên thực hiện một chương trình khác, chương trình này được gọi là chương trình phục vụ ngắt (ISR – Interrupt Service Routine). Ngắt là một chức năng quan trọng, tiên quyết cho việc phát hiện và xử lý nhanh các sự kiện theo thời gian thực. Nhờ có ngắt mà vi điều khiển, có thể phát hiện được các sự kiện như thay đổi mức tín hiệu logic từ việc nhất nút, cảm biến, truyền nhận các gói tin, các giao động… Tuy nhiên, cũng vì những lợi thế của ngắt mà khi lập trình ngắt, nếu bất cẩn thì chúng ta sẽ phải đối mặt với những lỗi, những tình huống không ai mong muốn.

1. Vấn đề không có quá trình bảo vệ biến giữa ngắt và chương trình chính

Một ví dụ cho việc không bảo vệ biến giữa ngắt và chương trình chính:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a = 0;
void main() {
    //Do somethings
    while(1) {
        if (a == 3) {
            Process(a);
        }
    //Do somethings
    }
}
void ISR_Process() {
    a++;
}
   Trong chương trình trên ISR_Process là hàm xử lý ngắt cho một ngắt bất kỳ nào đó trong hệ thống của bạn. Trong trường hợp sau khi check giá trị biến a bằng 3 xong, bạn expect xử lý một trạng thái của hệ thống qua hàm Process(), giữa 2 sự kiện này xảy ra ngắt, và hàm xử lý ngắt làm tăng giá trị biến lên 1 (a++). Sau khi quay trở lại từ ngắt, hệ thống gọi hàm Process(a), nhưng giá trị a lúc này thực tế đã là 4, điều này làm các thao tác xử lý trong hàm Process mà ta expect khi đó giá trị đầu vào là 3 (thực tế đã là 4) bị sai.
=> Lỗi loại này bạn có thể khắc phục bằng bằng việc thêm vào các câu lệnh bảo vệ vô hiệu hóa ngắt ISR_Process trước câu lệnh check (a == 3), sau khi xử lý xong hàm Process mới enable ngắt trở lại hoặc sử dụng kết hợp giữa biến toàn cục và biến cục bộ. Các bạn tham khảo thêm 02 ví dụ so sánh sau cho cả hai cách khắc phục:
Phương pháp bật tắt ngắt:
Phương pháp sử dụng kết hợp biến toàn cục và biến cục bộ:
Vấn đề ngắt chồng ngắt
Khi các bạn sử dụng nhiều ngắt như ngắt ngoài, ADC, I2C, UART, Timer… trong một chương trình và không lường trước được vấn đề interrupt nesting (ngắt chồng ngắt – đang ngắt thì có một ngắt khác), dẫn đến không có các biện pháp bảo vệ cần thiết cho biến, hay cho luồng xử lý của các ISR có quyền ưu tiên thấp. Để khắc phục được vấn đề này, các bạn cần nắm rõ các ưu tiên ngắt của vi điều khiển trong interrupt Vector Table, áp dụng bật tắt ngắt để bảo vệ biến, trong một số trường hợp, bạn có thể thay đổi thứ tự ưu tiên ngắt nếu vi điều khiển có hỗ trợ.
Vấn đề thời gian chiếm dụng
Các xử lý trong ngắt cần lưu ý về thời gian chiếm dụng, thời gian chiếm dụng càng lâu thì nguy cơ bị các vấn đề về interrupt nesting, các vấn đề về overload CPU càng lớn, vì vậy nếu có thể hãy để các xử lý ra hết ngoài chương trình chính, ngắt chỉ là nơi để thông báo cần phải xử lý một sự kiện (signal ra chương trình chính).
Vấn đề chờ trong ngắt
Dead-lock đơn giản được hiểu là quá trình chờ đợi các state khác của hệ thống, các thay đổi giá trị trong hệ thống phù hợp cho xử lý, trong trường hợp như vậy rất có thể đoạn chương trình đang chờ thực hiện sẽ không bao giờ được thực hiện.
Sưu tầm
Nhật Thương