Đầu tiên, volatile
khô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 count
mộ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 count
nữ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 đó count
là 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ĩ count
là 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ẻ.