Điều kiện giấc ngủ vi điều khiển


11

Cho một vi điều khiển đang chạy đoạn mã sau:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Giả sử if(has_flag)điều kiện ước lượng là sai và chúng ta sắp thực hiện lệnh ngủ. Ngay trước khi chúng tôi thực hiện lệnh ngủ, chúng tôi nhận được một ngắt. Sau khi chúng tôi rời khỏi ngắt, chúng tôi thực hiện hướng dẫn ngủ.

Trình tự thực hiện này là không mong muốn bởi vì:

  • Bộ vi điều khiển đã đi ngủ thay vì thức dậy và gọi process().
  • Bộ vi điều khiển có thể không bao giờ thức dậy nếu sau đó không nhận được ngắt.
  • Cuộc gọi đến process()bị hoãn cho đến khi ngắt tiếp theo.

Làm thế nào mã có thể được viết để ngăn chặn điều kiện cuộc đua này xảy ra?

Biên tập

Một số bộ vi điều khiển, chẳng hạn như tại ATMega, có bit kích hoạt chế độ Ngủ để ngăn tình trạng này xảy ra (cảm ơn bạn Kvegaoro đã chỉ ra điều này). JRoberts cung cấp một triển khai ví dụ minh họa cho hành vi này.

Các micros khác, chẳng hạn như PIC18, không có bit này và vấn đề vẫn xảy ra. Tuy nhiên, các micrô này được thiết kế sao cho các ngắt vẫn có thể đánh thức lõi bất kể liệu bit kích hoạt ngắt toàn cầu có được thiết lập hay không (cảm ơn bạn supercat vì đã chỉ ra điều này). Đối với các kiến ​​trúc như vậy, giải pháp là vô hiệu hóa các ngắt toàn cầu ngay trước khi đi ngủ. Nếu một ngắt kích hoạt ngay trước khi thực hiện lệnh ngủ, trình xử lý ngắt sẽ không được thực thi, lõi sẽ thức dậy và một khi các ngắt toàn cầu được kích hoạt lại, trình xử lý ngắt sẽ được thực thi. Trong mã giả, việc triển khai sẽ như thế này:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Đây là câu hỏi thực tế hay lý thuyết?
AndrejaKo

về lý thuyết, bạn sử dụng bộ hẹn giờ đánh thức bạn một lần mỗi lần (nhập giá trị chấp nhận được) và sau đó quay lại ngủ nếu không có gì cần làm.
Người chơi Grady

1
Tôi sẽ làm interrupt_flagnhư một int, và tăng nó mỗi khi có gián đoạn. Sau đó thay đổi if(has_flag)sang while (interrupts_count)rồi ngủ. Tuy nhiên, gián đoạn có thể xảy ra sau khi bạn thoát khỏi vòng lặp while. Nếu đây là một vấn đề, sau đó làm việc xử lý trong chính nó?
angelatlarge

1
cũng tùy thuộc vào loại micro bạn đang chạy .. Nếu đó là ATmega328, bạn có thể vô hiệu hóa chế độ ngủ trên ngắt, vì vậy nếu điều kiện cuộc đua bạn mô tả xảy ra thì chức năng ngủ sẽ bị ghi đè, lặp lại lần nữa và bạn sẽ xử lý sự gián đoạn với độ trễ nhỏ. Nhưng cũng sử dụng bộ hẹn giờ để thức dậy ở một khoảng thời gian bằng hoặc ít hơn độ trễ tối đa của bạn cũng sẽ là một giải pháp tuyệt vời
Kvegaoro

1
@TRISAbits: Trên PIC 18x, cách tiếp cận tôi đã mô tả trong câu trả lời của mình chỉ hoạt động tốt (đó là thiết kế bình thường của tôi khi sử dụng phần đó).
supercat

Câu trả lời:


9

Thường có một số loại hỗ trợ phần cứng cho trường hợp này. Ví dụ: seihướng dẫn của AVR để kích hoạt ngắt làm chậm kích hoạt cho đến khi hoàn thành hướng dẫn sau. Với nó người ta có thể làm:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

Ngắt mà đã bị bỏ lỡ trong ví dụ này trong trường hợp này sẽ bị tắt cho đến khi bộ xử lý hoàn thành chuỗi giấc ngủ.


Câu trả lời chính xác! Thuật toán bạn cung cấp thực sự hoạt động thực sự tốt trên một máy AVR. Cám ơn vì sự gợi ý.
TRIS chấp nhận

3

Trên nhiều bộ vi điều khiển, ngoài việc có thể bật hoặc tắt các nguyên nhân ngắt cụ thể (thường là trong mô-đun bộ điều khiển ngắt), có một cờ chính trong lõi CPU xác định xem các yêu cầu ngắt có được chấp nhận hay không. Nhiều bộ vi điều khiển sẽ thoát khỏi chế độ ngủ nếu yêu cầu ngắt đến lõi, cho dù lõi có sẵn sàng thực sự chấp nhận hay không.

Trên một thiết kế như vậy, một cách tiếp cận đơn giản để đạt được hành vi giấc ngủ đáng tin cậy là kiểm tra vòng lặp chính xóa cờ và sau đó kiểm tra xem nó có biết bất kỳ lý do nào mà bộ xử lý nên thức không. Bất kỳ gián đoạn nào xảy ra trong thời gian đó có thể ảnh hưởng đến bất kỳ lý do nào trong số những lý do đó nên đặt cờ. Nếu vòng lặp chính không tìm thấy bất kỳ nguyên nhân nào để tỉnh táo và nếu cờ không được đặt, vòng lặp chính sẽ vô hiệu hóa các ngắt và kiểm tra lại cờ [có lẽ sau một vài lệnh NOP nếu có thể một ngắt bị chờ xử lý trong khi lệnh vô hiệu hóa ngắt có thể được xử lý sau khi tìm nạp toán hạng liên quan đến lệnh sau đã được thực hiện]. Nếu cờ vẫn không được đặt, thì hãy đi ngủ.

Theo kịch bản này, một ngắt xảy ra trước khi vòng lặp chính vô hiệu hóa các ngắt sẽ đặt cờ trước thử nghiệm cuối cùng. Một ngắt bị chờ xử lý quá muộn để được phục vụ trước khi lệnh ngủ sẽ ngăn bộ xử lý đi ngủ. Cả hai tình huống đều tốt.

Sleep-on-exit đôi khi là một mô hình tốt để sử dụng, nhưng không phải tất cả các ứng dụng thực sự "phù hợp" với nó. Ví dụ: một thiết bị có màn hình LCD tiết kiệm năng lượng có thể được lập trình dễ dàng nhất với mã trông giống như:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

Nếu không có nút nào được ấn và không có gì khác xảy ra, không có lý do gì hệ thống không nên ngủ trong khi thực hiện get_keyphương thức. Mặc dù có thể có các khóa kích hoạt ngắt và quản lý tất cả các tương tác giao diện người dùng thông qua máy trạng thái, mã như trên thường là cách hợp lý nhất để xử lý các luồng giao diện người dùng có chế độ cao điển hình của các bộ vi điều khiển nhỏ.


Cảm ơn supercat cho câu trả lời tuyệt vời. Vô hiệu hóa các ngắt và sau đó đi ngủ là một giải pháp tuyệt vời với điều kiện là lõi sẽ thức dậy từ bất kỳ nguồn ngắt nào bất kể liệu bit ngắt toàn cầu có được thiết lập / xóa hay không. Tôi đã xem sơ đồ phần cứng ngắt PIC18 và giải pháp này sẽ hoạt động.
TRIS chấp

1

Chương trình vi để đánh thức trên ngắt.

Các chi tiết cụ thể sẽ thay đổi tùy thuộc vào micro bạn đang sử dụng.

Sau đó sửa đổi thường trình main ():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}

1
Kiến trúc Wake-on-interrupt được giả định trong câu hỏi. Tôi không nghĩ câu trả lời của bạn giải quyết được câu hỏi / vấn đề.
angelatlarge

@eachatlarge Điểm được chấp nhận. Tôi đã thêm một ví dụ mã mà tôi nghĩ giúp.
jwygralak67

@ jwygralak67: Cảm ơn bạn đã gợi ý, nhưng mã bạn cung cấp chỉ đơn thuần chuyển vấn đề sang thói quen process (), mà bây giờ phải kiểm tra xem có xảy ra gián đoạn trước khi thực hiện phần thân process () không.
TRIS chấp nhận

2
Nếu sự gián đoạn đã không xảy ra, tại sao chúng ta thức dậy?
JRobert

1
@JRobert: Chúng tôi có thể tỉnh táo từ một ngắt trước đó, hoàn thành quy trình () và khi chúng tôi hoàn thành bài kiểm tra if (has_flag) và ngay trước khi ngủ, chúng tôi nhận được một ngắt khác, điều này gây ra sự cố tôi đã mô tả trong câu hỏi
TRIS chấp nhận
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.