Xử lý ngắt trong vi điều khiển và ví dụ FSM


9

Câu hỏi ban đầu

Tôi có một câu hỏi chung về việc xử lý các ngắt trong vi điều khiển. Tôi đang sử dụng MSP430, nhưng tôi nghĩ câu hỏi có thể được mở rộng cho các uC khác. Tôi muốn biết có hay không thực hành tốt để bật / tắt các ngắt thường xuyên dọc theo mã. Ý tôi là, nếu tôi có một phần mã sẽ không nhạy cảm với các ngắt (hoặc thậm chí tệ hơn, không được nghe các ngắt, vì bất kỳ lý do nào), tốt hơn là:

  • Vô hiệu hóa các ngắt trước và sau đó kích hoạt lại chúng sau phần quan trọng.
  • Đặt cờ bên trong ISR tương ứng và (thay vì vô hiệu hóa ngắt), đặt cờ thành sai trước phần quan trọng và đặt lại thành đúng, ngay sau đó. Để ngăn chặn mã của ISR được thực thi.
  • Cả hai, vì vậy đề nghị được chào đón!

Cập nhật: ngắt và biểu đồ trạng thái

Tôi sẽ cung cấp một tình huống cụ thể. Giả sử chúng ta muốn thực hiện một biểu đồ trạng thái, bao gồm 4 khối:

  1. Chuyển đổi / Hiệu ứng.
  2. Điều kiện thoát hiểm.
  3. Hoạt động nhập cảnh.
  4. Làm hoạt động.

Đây là những gì một giáo sư đã dạy chúng tôi tại trường đại học. Có lẽ, không phải cách tốt nhất để làm điều đó là bằng cách làm theo sơ đồ này:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Chúng ta cũng giả sử rằng, từ, STATE_Atôi muốn nhạy cảm với một ngắt đến từ một tập hợp các nút (với hệ thống gỡ lỗi, v.v.). Khi ai đó nhấn một trong các nút này, một ngắt được tạo và cờ liên quan đến cổng đầu vào được sao chép vào một biến buttonPressed. Nếu phần gỡ lỗi được đặt thành 200 ms theo một cách nào đó (bộ đếm thời gian theo dõi, bộ đếm thời gian, bộ đếm, ...) chúng tôi chắc chắn rằng buttonPressedkhông thể cập nhật với giá trị mới trước 200 ms. Đây là những gì tôi đang hỏi bạn (và bản thân tôi :) tất nhiên)

Tôi có cần kích hoạt ngắt trong hoạt động DO STATE_Avà vô hiệu hóa trước khi rời đi không?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

Theo cách mà tôi chắc chắn rằng lần sau khi tôi thực hiện khối 1 (chuyển tiếp / hiệu ứng) ở lần lặp tiếp theo, tôi chắc chắn rằng các điều kiện được kiểm tra dọc theo các chuyển đổi không đến từ một ngắt tiếp theo đã ghi đè lên giá trị trước đó của buttonPressedtôi cần (mặc dù điều này không thể xảy ra vì 250 ms phải trôi qua).


3
Thật khó để đưa ra một đề nghị mà không biết thêm về tình huống của bạn. Nhưng đôi khi cần phải vô hiệu hóa các ngắt trong các hệ thống nhúng. Tốt nhất là giữ cho các ngắt bị vô hiệu hóa chỉ trong một khoảng thời gian ngắn để các ngắt không bị bỏ sót. Có thể chỉ vô hiệu hóa các ngắt cụ thể chứ không phải tất cả các ngắt. Tôi không nhớ là đã từng sử dụng kỹ thuật ISR bên trong như bạn đã mô tả nên tôi nghi ngờ liệu đó có phải là giải pháp tốt nhất của bạn không.
kkrambo

Câu trả lời:


17

Chiến thuật đầu tiên là kiến ​​trúc phần sụn tổng thể sao cho việc gián đoạn xảy ra bất cứ lúc nào cũng được. Phải tắt các ngắt để mã tiền cảnh có thể thực hiện một chuỗi nguyên tử nên được thực hiện một cách tiết kiệm. Thường có một cách kiến ​​trúc xung quanh nó.

Tuy nhiên, máy có mặt để phục vụ bạn chứ không phải cách khác. Quy tắc chung là chỉ để giữ cho các lập trình viên xấu viết mã thực sự xấu. Tốt hơn hết là hiểu chính xác cách thức hoạt động của máy và sau đó kiến ​​trúc sư một cách tốt để khai thác các khả năng đó để thực hiện nhiệm vụ mong muốn.

Hãy nhớ rằng trừ khi bạn thực sự chặt chẽ về chu kỳ hoặc vị trí bộ nhớ (chắc chắn có thể xảy ra), nếu không bạn muốn tối ưu hóa cho sự rõ ràng và khả năng duy trì của mã. Ví dụ: nếu bạn có một máy 16 bit cập nhật bộ đếm 32 bit trong ngắt đồng hồ, bạn cần đảm bảo rằng khi mã nền trước đọc bộ đếm thì hai nửa của nó là nhất quán. Một cách là tắt ngắt, đọc hai từ và sau đó bật lại ngắt. Nếu độ trễ ngắt không quan trọng, thì điều đó hoàn toàn chấp nhận được.

Trong trường hợp bạn phải có độ trễ ngắt thấp, ví dụ, bạn có thể đọc từ cao, đọc từ thấp, đọc lại từ cao và lặp lại nếu nó thay đổi. Điều này làm chậm mã tiền cảnh một chút, nhưng không thêm độ trễ ngắt nào cả. Có nhiều thủ thuật nhỏ khác nhau. Một cách khác có thể là đặt cờ trong thói quen ngắt cho biết bộ đếm phải được tăng lên, sau đó thực hiện điều đó trong vòng lặp sự kiện chính trong mã nền trước. Điều đó hoạt động tốt nếu tốc độ ngắt bộ đếm đủ chậm để vòng lặp sự kiện sẽ thực hiện tăng trước khi cờ được đặt lại.

Hoặc, thay vì cờ sử dụng bộ đếm một từ. Mã nền trước giữ một biến riêng biệt chứa bộ đếm cuối cùng mà nó đã cập nhật hệ thống. Nó thực hiện phép trừ không dấu của bộ đếm trực tiếp trừ đi giá trị đã lưu để xác định số lượng bọ tại một thời điểm nó phải xử lý. Điều này cho phép mã tiền cảnh bỏ lỡ tối đa 2 N -1 sự kiện tại một thời điểm, trong đó N là số bit trong một từ gốc mà ALU có thể xử lý nguyên tử.

Mỗi phương pháp có tập hợp ưu điểm và nhược điểm riêng. Không có câu trả lời đúng duy nhất. Một lần nữa, hãy hiểu cách thức hoạt động của máy, sau đó bạn sẽ không cần quy tắc ngón tay cái.


7

Nếu bạn cần một phần quan trọng, bạn phải chắc chắn rằng hoạt động bảo vệ phần quan trọng của bạn là nguyên tử và không thể bị gián đoạn.

Do đó, vô hiệu hóa các ngắt, thường được xử lý bởi một lệnh của bộ xử lý (và được gọi là sử dụng hàm nội tại của trình biên dịch), là một trong những cược an toàn nhất bạn có thể thực hiện.

Tùy thuộc vào hệ thống của bạn, có thể có một số vấn đề với điều đó, như một ngắt có thể bị bỏ lỡ. Một số bộ vi điều khiển đặt cờ bất kể trạng thái kích hoạt ngắt toàn cầu và sau khi rời khỏi phần quan trọng, các ngắt được thực thi và chỉ bị trì hoãn. Nhưng nếu bạn có một ngắt xảy ra ở tốc độ cao, bạn có thể bỏ lỡ lần thứ hai xảy ra ngắt nếu bạn chặn các ngắt trong một thời gian quá dài.

Nếu phần quan trọng của bạn chỉ yêu cầu một ngắt không được thực thi, nhưng phần khác nên được thực thi, thì cách tiếp cận khác có vẻ khả thi.

Tôi thấy mình lập trình các thói quen dịch vụ ngắt càng ngắn càng tốt. Vì vậy, họ chỉ cần đặt một cờ sau đó được kiểm tra trong các chương trình bình thường. Nhưng nếu bạn làm điều đó, hãy cẩn thận với các điều kiện cuộc đua trong khi chờ cờ đó được thiết lập.

Có rất nhiều lựa chọn và chắc chắn không có một câu trả lời đúng nào cho vấn đề này, đây là một chủ đề đòi hỏi thiết kế cẩn thận và xứng đáng với một chút suy nghĩ hơn những thứ khác.


5

Nếu bạn đã xác định rằng một phần mã phải chạy không bị gián đoạn, ngoại trừ trong trường hợp bất thường, bạn nên tắt các ngắt trong khoảng thời gian tối thiểu có thể để hoàn thành nhiệm vụ và bật lại chúng sau.

Đặt cờ bên trong ISR tương ứng và (thay vì vô hiệu hóa ngắt), đặt cờ thành sai trước phần quan trọng và đặt lại thành đúng, ngay sau đó. Để ngăn chặn mã của ISR được thực thi.

Điều này vẫn sẽ cho phép một ngắt xảy ra, nhảy mã, kiểm tra, sau đó trả lại. Nếu mã của bạn có thể xử lý nhiều gián đoạn này, thì có lẽ bạn chỉ cần thiết kế ISR để đặt cờ thay vì thực hiện kiểm tra - nó sẽ ngắn hơn - và xử lý cờ trong thói quen mã thông thường của bạn. Điều này nghe có vẻ như ai đó đã đặt quá nhiều mã vào ngắt và họ đang sử dụng ngắt để thực hiện các hành động dài hơn sẽ diễn ra trong mã thông thường.

Nếu bạn đang xử lý mã trong đó các ngắt bị dài, một cờ như bạn đề xuất có thể giải quyết vấn đề, nhưng sẽ tốt hơn nếu bạn vô hiệu hóa các ngắt nếu bạn không thể xác định lại mã để loại bỏ mã quá mức trong ngắt .

Vấn đề chính khi thực hiện theo cách cờ là bạn hoàn toàn không thực hiện ngắt - điều này có thể có hậu quả sau này. Hầu hết các bộ vi điều khiển sẽ theo dõi các cờ ngắt ngay cả khi các ngắt bị vô hiệu hóa trên toàn cầu và sau đó sẽ thực hiện ngắt khi bạn kích hoạt lại các ngắt:

  • Nếu không có ngắt xảy ra trong phần quan trọng, không có ngắt được thực hiện sau.
  • Nếu một ngắt xảy ra trong phần quan trọng, một được thực hiện sau.
  • Nếu nhiều ngắt xảy ra trong phần quan trọng, chỉ có một được thực hiện sau.

Nếu hệ thống của bạn phức tạp và phải theo dõi các ngắt hoàn toàn hơn, bạn sẽ phải thiết kế một hệ thống phức tạp hơn để theo dõi các ngắt và vận hành tương ứng.

Tuy nhiên, nếu bạn luôn thiết kế các ngắt của mình để thực hiện công việc tối thiểu cần thiết để đạt được chức năng của chúng và trì hoãn mọi thứ khác để xử lý thông thường, thì các ngắt sẽ hiếm khi tác động tiêu cực đến mã khác của bạn. Có chức năng thu thập hoặc giải phóng dữ liệu nếu cần hoặc đặt / đặt lại đầu ra, v.v. nếu cần, sau đó có đường dẫn mã chính chú ý đến cờ, bộ đệm và các biến mà ảnh hưởng đến việc xử lý dài có thể được thực hiện trong vòng lặp chính, thay vì ngắt quãng.

Điều này sẽ loại bỏ tất cả trừ một, rất ít tình huống mà bạn có thể cần một phần mã không bị gián đoạn.


Tôi đã cập nhật bài đăng để giải thích rõ hơn về tình huống tôi đang làm việc :)
Umberto D.

1
Trong mã ví dụ của bạn, tôi sẽ xem xét việc vô hiệu hóa nút cụ thể khi không cần thiết và bật nó khi cần. Làm điều này thường xuyên không phải là một vấn đề của thiết kế. Để lại các ngắt toàn cầu để bạn có thể thêm các ngắt khác vào mã sau nếu cần. Thay phiên, chỉ cần đặt lại cờ khi bạn đi đến trạng thái A và nếu không thì bỏ qua nó. Nếu nhấn nút và đặt cờ, ai quan tâm? Bỏ qua nó cho đến khi bạn quay trở lại trạng thái A.
Adam Davis

Đúng! Đó có thể là một giải pháp vì trong thiết kế thực tế, tôi thường xuyên sử dụng LPM3 (MSP430) với các ngắt toàn cầu được bật và tôi thoát khỏi LPM3, tiếp tục thực hiện, ngay khi phát hiện thấy ngắt. Vì vậy, một giải pháp là giải pháp mà bạn nghĩ đã được báo cáo trong phần thứ hai của mã: kích hoạt ngắt ngay khi tôi bắt đầu thực hiện hoạt động do trạng thái cần và vô hiệu hóa trước khi chuyển sang khối chuyển tiếp. Có thể một giải pháp khả thi khác là vô hiệu hóa ngắt ngay trước khi rời khỏi "Khối hoạt động" và bật lại sau đó (khi nào?) Sau?
Umberto D.

1

Đặt một lá cờ bên trong ISR như bạn mô tả có thể sẽ không hoạt động vì về cơ bản bạn đang bỏ qua sự kiện gây ra sự gián đoạn. Vô hiệu hóa các ngắt trên toàn cầu thường là một lựa chọn tốt hơn. Như những người khác đã nói, bạn không nên làm điều này rất thường xuyên. Hãy nhớ rằng bất kỳ việc đọc hoặc viết nào được thực hiện thông qua một lệnh đơn lẻ đều không cần phải được bảo vệ vì lệnh này được thực thi hoặc không.

Rất nhiều phụ thuộc vào loại tài nguyên bạn đang cố gắng chia sẻ. Nếu bạn đang cung cấp dữ liệu từ ISR cho chương trình chính (hoặc ngược lại), bạn có thể thực hiện một cái gì đó giống như bộ đệm FIFO. Hoạt động nguyên tử duy nhất sẽ là cập nhật các con trỏ đọc và ghi, giúp giảm thiểu thời gian bạn sử dụng với các ngắt bị vô hiệu hóa.


0

Có một sự khác biệt tinh tế bạn cần phải tính đến. Bạn có thể chọn "trì hoãn" việc xử lý ngắt hoặc "coi thường" và ngắt.

Thông thường chúng ta nói trong mã mà chúng ta vô hiệu hóa ngắt. Điều có thể sẽ xảy ra, do các chức năng phần cứng, là một khi chúng ta kích hoạt các ngắt, nó sẽ kích hoạt. Điều này theo một cách nào đó là trì hoãn sự gián đoạn. Để hệ thống hoạt động đáng tin cậy, chúng tôi cần biết thời gian tối đa chúng tôi có thể trì hoãn việc xử lý các ngắt này. Và sau đó đảm bảo rằng tất cả các trường hợp ngắt bị vô hiệu hóa sẽ được kết thúc trong thời gian ngắn hơn.

Đôi khi chúng ta muốn bỏ qua các ngắt. Cách tốt nhất có thể là chặn ngắt ở mức phần cứng. Thường có một bộ điều khiển ngắt hoặc tương tự trong đó chúng ta có thể nói đầu vào nào sẽ tạo ra các ngắt.

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.