Tại sao cần phải sử dụng từ khóa dễ bay hơi trên các biến toàn cục khi xử lý các ngắt trong Arduino?


7

Tôi quen thuộc với từ khóa Volatileđang được sử dụng để khai báo các biến được chia sẻ giữa nhiều luồng trên một ứng dụng phần mềm (về cơ bản là trên một ứng dụng đa luồng).

Nhưng tại sao tôi cần phải khai báo một biến như Volatilekhi chạy mã trên một ngắt Arduino? Là mã chạy trong ngắt thực thi trên luồng khác nhau? Có điều gì khác đang xảy ra?


Bạn phải nói với trình biên dịch rằng biến tồn tại để các thuật toán tối ưu hóa không loại bỏ nó hoàn toàn khỏi thực thi của bạn.

"Tôi quen thuộc với từ khóa dễ bay hơi". Sau đó quên nó đi, và đọc lại.
Eugene Sh.

2
Hiệu quả xử lý ngắt là một luồng riêng biệt từ chính. Nếu không có khai báo dễ bay hơi, trình biên dịch không biết rằng main phải đọc lại toàn cục (có thể đã bị thay đổi bởi trình xử lý ngắt).
MarkU

Is the code running in the interrupt executing on different thread- cũng sắp xếp, chỉ ngược lại. Ngắt là cơ chế, kỹ thuật được sử dụng bởi các lập trình viên hệ điều hành, để thực hiện các luồng. Vì vậy, chúng ta cần sử dụng volatilemã đa luồng vì ngữ cảnh của chúng đã bị HĐH chuyển đổi trong một ngắt. Chúng ta cần sử dụng các volatilengắt đơn giản vì các ngắt chạy trong một ngữ cảnh khác.
slebetman

Câu trả lời:


11

Đầu tiên, volatilekhông phải vậy Volatile. Tuy nhiên, tôi trình bày các khái niệm này trong trang của mình về Ngắt để tránh đưa ra câu trả lời chỉ liên kết Tôi sẽ lặp lại các bit có liên quan.


Biến "dễ bay hơi" là gì?

Các biến được chia sẻ giữa các hàm ISR và các hàm bình thường nên được khai báo volatile. Điều này cho trình biên dịch biết rằng các biến đó có thể thay đổi bất cứ lúc nào và do đó trình biên dịch phải tải lại biến bất cứ khi nào bạn tham chiếu nó, thay vì dựa vào một bản sao mà nó có thể có trong một thanh ghi bộ xử lý.

Ví dụ:

volatile boolean flag;

// Interrupt Service Routine (ISR)
void isr ()
{
 flag = true;
}  // end of isr

void setup ()
{
  attachInterrupt (0, isr, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  if (flag)
    {
    // interrupt has occurred
    }
}  // end of loop

Phần quan trọng ... truy cập các biến dễ bay hơi

Có một số vấn đề tế nhị liên quan đến các biến được chia sẻ giữa các thói quen dịch vụ ngắt (ISR) và mã chính (nghĩa là mã không nằm trong ISR).

Vì ISR có thể kích hoạt bất cứ lúc nào khi ngắt được kích hoạt, bạn cần thận trọng khi truy cập các biến được chia sẻ đó, vì chúng có thể được cập nhật tại thời điểm bạn truy cập chúng.


Đầu tiên ... khi nào bạn sử dụng biến "dễ bay hơi"?

Một biến chỉ nên được đánh dấu dễ bay hơi nếu nó được sử dụng cả bên trong ISR và bên ngoài một biến.

  • Các biến chỉ được sử dụng bên ngoài ISR sẽ không biến động.
  • Các biến chỉ được sử dụng bên trong ISR sẽ không biến động.
  • Các biến được sử dụng cả bên trong và bên ngoài ISR sẽ không ổn định.

ví dụ.

volatile int counter;

Việc đánh dấu một biến là dễ bay hơi sẽ cho trình biên dịch không "lưu" nội dung của biến vào một thanh ghi bộ xử lý, nhưng luôn đọc nó từ bộ nhớ, khi cần. Điều này có thể làm chậm quá trình xử lý, đó là lý do tại sao bạn không làm cho mọi biến số biến động, khi không cần thiết.


Truy cập nguyên tử

Xem xét mã này:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }

void setup ()
  {
  }

void loop () 
  {
  count++;
  }

Mã được tạo cho count++(thêm 1 để đếm) là đây:

 14c:   80 91 00 02     lds r24, 0x0200
 150:   8f 5f           subi    r24, 0xFF     <<---- problem if interrupt occurs before this is executed
 152:   80 93 00 02     sts 0x0200, r24   <<---- problem if interrupt occurs before this is executed

(Lưu ý rằng nó thêm 1 bằng cách trừ -1)

Có hai điểm nguy hiểm ở đây, như được đánh dấu. Nếu ngắt kích hoạt sau lds (thanh ghi tải 24 với biến count), nhưng trước sts (lưu lại vào biến count) thì biến có thể bị thay đổi bởi ISR ​​(TIMER1_OVF_vect), tuy nhiên thay đổi này đã bị mất , vì biến này trong sổ đăng ký đã được sử dụng thay thế.

Chúng ta cần bảo vệ biến được chia sẻ bằng cách tắt nhanh các ngắt, như thế này:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }  // end of TIMER1_OVF_vect

void setup ()
  {
  }  // end of setup

void loop () 
  {
  noInterrupts ();
  count++;
  interrupts ();
  } // end of loop

Bây giờ việc cập nhật countđược thực hiện "nguyên tử" ... nghĩa là, nó không thể bị gián đoạn.


Biến nhiều byte

Hãy tạo countmột biến 2 byte và xem vấn đề khác:

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop () 
  {
  if (count > 20)
     digitalWrite (13, HIGH);
  } // end of loop

OK, chúng tôi sẽ không thay đổi countnữa, vì vậy vẫn còn một vấn đề? Đáng buồn thay, có. Hãy xem mã được tạo cho câu lệnh "if":

 172:   80 91 10 02     lds r24, 0x0210
 176:   90 91 11 02     lds r25, 0x0211  <<---- problem if interrupt occurs before this is executed
 17a:   45 97           sbiw    r24, 0x15   
 17c:   50 f0           brcs    .+20        

Hãy tưởng tượng đó countlà 0xFFFF và chuẩn bị "quấn quanh" về không. Chúng tôi tải 0xFF vào một thanh ghi, nhưng trước khi chúng tôi tải 0xFF thứ hai, biến thay đổi thành 0x00. Bây giờ chúng tôi nghĩ countlà 0x00FF không phải là giá trị trước đây (0xFFFF) hoặc bây giờ (0x0000).

Vì vậy, một lần nữa chúng ta phải "bảo vệ" quyền truy cập vào biến được chia sẻ, như thế này:

void loop () 
  {
  noInterrupts ();
  if (count > 20)
     digitalWrite (13, HIGH);
  interrupts ();
  }  // end of loop

Điều gì xảy ra nếu bạn không chắc chắn ngắt được bật hay tắt?

Có một "gotcha" cuối cùng ở đây. Điều gì xảy ra nếu ngắt có thể tắt? Sau đó bật chúng trở lại là một ý tưởng tồi.

Trong trường hợp đó, bạn cần lưu thanh ghi trạng thái bộ xử lý như thế này:

unsigned int getCount ()
  {
  unsigned int c;
  byte oldSREG = SREG;   // remember if interrupts are on or off

  noInterrupts ();   // turn interrupts off
  c = count;         // access the shared variable
  SREG = oldSREG;    // turn interrupts back on, if they were on before

  return c;          // return copy of shared variable
  }  // end of getCount

Mã "an toàn" này lưu trạng thái hiện tại của các ngắt, tắt chúng (chúng có thể đã tắt), biến biến được chia sẻ thành một biến tạm thời, bật lại các ngắt - nếu chúng được bật khi chúng ta nhập hàm - và sau đó trả về bản sao của biến được chia sẻ.


Tóm lược

Mã có thể "có vẻ hoạt động" ngay cả khi bạn không thực hiện các biện pháp phòng ngừa trên. Đó là bởi vì khả năng xảy ra gián đoạn tại thời điểm sai chính xác là khá thấp (có thể là 1 trên 100, tùy thuộc vào kích thước mã). Nhưng sớm hay muộn nó sẽ xảy ra vào thời điểm sai và mã của bạn sẽ bị sập hoặc đôi khi trả về kết quả sai.

Vì vậy, đối với mã đáng tin cậy, hãy chú ý đến việc bảo vệ quyền truy cập vào các biến được chia sẻ.


Tôi nghĩ rằng không ổn định sẽ xử lý các loại truy cập đọc và ghi nhiều byte trong vòng lặp chính và ISR! Vì vậy, tôi đã hoàn toàn sai và nên tắt các ngắt trước khi truy cập chúng trong vòng lặp chính, phải không?! ;-(
Shamim

1
Đúng rồi. volatilechỉ báo cho trình biên dịch không tối ưu hóa và giữ các biến trong các thanh ghi bộ xử lý. Nó không có bất kỳ tác dụng phụ nào khác (ngoài việc cho trình biên dịch biết các biến này có thể thay đổi bất cứ lúc nào, vì vậy các biến dễ bay hơi không bao giờ được tối ưu hóa vì chúng "không được sử dụng"). Vì vậy, có, như minh họa ở trên, "bảo vệ" truy cập vào các biến nhiều byte bằng cách tắt ngắt.
Nick Gammon

6

volatilekhông có gì để làm với đa luồng mỗi se, và nó dường như là một trong những từ khóa được sử dụng nhiều nhất trong khu vực đó. Nó không bao giờ thay thế cho đồng bộ hóa thích hợp.

Theo tiêu chuẩn C, volatilegọi hầu hết các hành vi được xác định là thực hiện việc xác định trình biên dịch không tối ưu hóa truy cập vào bộ nhớ mà một biến nằm trong đó, ngay cả khi trình biên dịch nghĩ rằng không thể có bất kỳ thứ gì khác.

Điều này thể hữu ích trong các tình huống sau đây khi trình biên dịch không biết rằng các phương tiện bên ngoài có thể thay đổi một biến:

  • bộ nhớ bản đồ phần cứng
  • các biến được truy cập từ các luồng khác nhưng được kiểm tra trong một vòng lặp chặt chẽ
  • các biến được truy cập từ các trình xử lý ngắt hoặc tín hiệu nhưng được kiểm tra trong một vòng lặp chặt chẽ
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.