Là con trỏ chức năng gán nguyên tử trong Arduino?


11

Các đoạn mã sau đây là từ mã nguồn thư viện TimerOne :

// TimerOne.h:
void (*isrCallback)();

// TimerOne.cpp:
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  Timer1.isrCallback();
}

// TimerOne.cpp:
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
  if(microseconds > 0) setPeriod(microseconds);
  isrCallback = isr; // register the user's callback with the real ISR
  TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
  resume();                                                                                            
}

Câu hỏi: nếu bộ định thời gian đang chạy và chương trình chính gọi attachInterrupt(), bộ ngắt thời gian có thể xảy ra ở đó trong quá trình gán con trỏ hàm isrCallback = isr;không? Sau đó, với thời gian may mắn, Timer1.isrCallback();con trỏ hàm sẽ bao gồm một phần của địa chỉ cũ và một phần của địa chỉ mới, khiến ISR nhảy đến một vị trí không có thật?

Tôi cho rằng đây có thể là trường hợp, vì các con trỏ hàm chắc chắn rộng hơn 1 byte và truy cập dữ liệu> 1 byte không phải là nguyên tử. Cách giải quyết có thể là:

  • Luôn gọi detachInterrupt()để đảm bảo bộ định thời không chạy, trước khi gọi attachInterrupt(), tức là làm rõ các tài liệu Timer1.
  • Hoặc, sửa đổi Timer1, vô hiệu hóa tạm thời ngắt tràn bộ đếm thời gian isrCallback = isr;

Điều này có ý nghĩa không, hoặc có một cái gì đó trong Timer1các nguồn hoặc các bài tập con trỏ hàm mà tôi đã bỏ lỡ?

Câu trả lời:


7

Hãy xem mã của Đính kèm () và detachInterrupt () trong /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(dù sao, đó là nơi họ đang ở trên máy Mac, dù sao đi nữa, cấu trúc tệp Arduino trên các hệ điều hành khác có thể trông tương tự ở các cấp thấp hơn của đường dẫn).

Dường như Đính kèm () giả định rằng ngắt trong câu hỏi chưa được kích hoạt vì nó ghi con trỏ hàm mà không thực hiện bất kỳ biện pháp phòng ngừa nào. Lưu ý rằng detachInterrupts () vô hiệu hóa ngắt đích trước khi ghi con trỏ NULL vào vectơ của nó. Vì vậy, ít nhất tôi nên sử dụng một detachInterrupt()/ attachInterrupt()cặp

Bản thân tôi muốn chạy bất kỳ mã nào như vậy trong một phần quan trọng. Nó xuất hiện theo cách đầu tiên của bạn (tách ra, sau đó đính kèm) sẽ hoạt động, mặc dù tôi không thể chắc chắn rằng nó không thể bỏ lỡ một ngắt thời gian không may. Bảng dữ liệu cho MCU của bạn có thể có nhiều điều để nói về điều đó. Nhưng tôi cũng không chắc chắn vào thời điểm này, rằng toàn cầu cli()/ sei()cũng sẽ không bỏ lỡ nó. Bảng dữ liệu ATMega2560, phần 6.8, cho biết "Khi sử dụng lệnh SEI để kích hoạt ngắt, lệnh sau SEI sẽ được thực thi trước bất kỳ ngắt đang chờ xử lý nào, như trong ví dụ này", dường như ngụ ý rằng nó có thể đệm một ngắt trong khi ngắt đang tắt


Nó thực sự hữu ích để đi sâu vào các nguồn :) Cơ chế ngắt / tách rời của TimerOne dường như được thực hiện tương tự như tiêu chuẩn (WInterrupt's) và do đó có cùng "tính năng".
Joonas Pulakka

0

Có vẻ như bạn có một điểm. Điều hợp lý cần làm là vô hiệu hóa các ngắt theo cách mà bạn không kích hoạt lại chúng nếu chúng bị vô hiệu hóa ngay từ đầu. Ví dụ:

  uint8_t oldSREG = SREG;  // remember if interrupts are on
  cli();                   // make the next line interruptible
  isrCallback = isr;       // register the user's callback with the real ISR
  SREG = oldSREG;          // turn interrupts back on, if they were on before

"Khi sử dụng lệnh SEI để kích hoạt các ngắt, lệnh sau SEI sẽ được thực thi trước bất kỳ ngắt đang chờ xử lý nào, như trong ví dụ này"

Mục đích của việc này là để cho bạn viết mã như thế này:

  sei ();         // enable interrupts
  sleep_cpu ();   // sleep

Nếu không có điều khoản đó, bạn có thể bị gián đoạn giữa hai dòng đó, và do đó ngủ vô thời hạn (vì sự gián đoạn sẽ đánh thức bạn đã xảy ra trước khi bạn ngủ). Việc cung cấp trong bộ xử lý rằng lệnh tiếp theo, sau khi ngắt được bật nếu chúng không được bật trước đó, luôn được thực thi, bảo vệ chống lại điều này.


Nó sẽ không hiệu quả hơn TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);? Nó bỏ qua một thanh ghi CPU và không đóng góp độ trễ ngắt.
Edgar Bonet

Còn các bit khác, như ICIE1, OCIE1B, OCIE1A thì sao? Tôi hiểu về độ trễ, nhưng các ngắt nên có thể đối phó với một vài chu kỳ đồng hồ không có sẵn. Có thể có một điều kiện cuộc đua. Ngắt có thể đã được kích hoạt (ví dụ: cờ trong CPU được đặt) và tắt TIMSK1 có thể không ngăn được xử lý. Bạn cũng có thể cần phải đặt lại TOV1 (bằng cách viết 1 vào TIFR1) để đảm bảo điều đó không xảy ra. Sự không chắc chắn ở đó khiến tôi nghĩ rằng tắt ngắt trên toàn cầu là khóa học an toàn hơn.
Nick Gammon
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.