Bagaimana interupsi bekerja pada Arduino Uno dan papan serupa?

11

Tolong jelaskan bagaimana interupsi bekerja pada Arduino Uno dan board terkait menggunakan prosesor ATmega328P. Papan seperti:

  • Uno
  • Mini
  • Nano
  • Pro Mini
  • Daun teratai

Secara khusus tolong diskusikan:

  • Untuk apa interupsi digunakan
  • Cara menulis Interrupt Service Rutin (ISR)
  • Masalah pengaturan waktu
  • Bagian kritis
  • Akses atom ke data

Catatan: ini adalah pertanyaan referensi .

Nick Gammon
sumber

Jawaban:

25

TL; DR:

Saat menulis Interrupt Service Rutin (ISR):

  • Tetap singkat
  • Jangan gunakan delay ()
  • Jangan lakukan pencetakan serial
  • Buat variabel dibagi dengan kode utama volatile
  • Variabel yang dibagikan dengan kode utama mungkin perlu dilindungi oleh "bagian kritis" (lihat di bawah)
  • Jangan coba mematikan atau mematikan interupsi

Apa itu interupsi?

Sebagian besar prosesor mengalami interupsi. Interupsi memungkinkan Anda merespons peristiwa "eksternal" sambil melakukan sesuatu yang lain. Misalnya, jika Anda memasak makan malam, Anda bisa memasak kentang selama 20 menit. Daripada menatap jam selama 20 menit, Anda mungkin mengatur timer, dan kemudian pergi menonton TV. Ketika timer berdering Anda "mengganggu" menonton TV Anda untuk melakukan sesuatu dengan kentang.


Contoh interupsi

const byte LED = 13;
const byte SWITCH = 2;

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  if (digitalRead (SWITCH) == HIGH)
    digitalWrite (LED, HIGH);
  else
    digitalWrite (LED, LOW);
}  // end of switchPressed

void setup ()
{
  pinMode (LED, OUTPUT);  // so we can update the LED
  pinMode (SWITCH, INPUT_PULLUP);
  attachInterrupt (digitalPinToInterrupt (SWITCH), switchPressed, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  // loop doing nothing
}

Contoh ini menunjukkan bagaimana, meskipun loop utama tidak melakukan apa-apa, Anda dapat menghidupkan atau mematikan pin 13, jika sakelar pin D2 ditekan.

Untuk mengujinya, cukup sambungkan kabel (atau sakelar) antara D2 dan Ground. Pullup internal (diaktifkan pada pengaturan) memaksa pin TINGGI secara normal. Ketika ground, itu menjadi RENDAH. Perubahan pin dideteksi oleh CHANGE interrupt, yang menyebabkan Interrupt Service Routine (ISR) dipanggil.

Dalam contoh yang lebih rumit, loop utama mungkin melakukan sesuatu yang bermanfaat, seperti mengambil pembacaan suhu, dan memungkinkan pengendali interupsi untuk mendeteksi tombol yang ditekan.


Mengubah angka pin untuk menginterupsi angka

Untuk menyederhanakan konversi angka interupsi vektor ke nomor pin Anda dapat memanggil fungsi digitalPinToInterrupt(), melewati nomor pin. Ini mengembalikan nomor interupsi yang sesuai, atau NOT_AN_INTERRUPT(-1).

Misalnya, pada Uno, pin D2 di papan adalah interrupt 0 (INT0_vect dari tabel di bawah).

Dengan demikian kedua garis ini memiliki efek yang sama:

  attachInterrupt (0, switchPressed, CHANGE);    // that is, for pin D2
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);

Namun yang kedua lebih mudah dibaca dan lebih portabel untuk berbagai jenis Arduino.


Interupsi yang tersedia

Di bawah ini adalah daftar interupsi, dalam urutan prioritas, untuk Atmega328:

 1  Reset
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

Nama internal (yang dapat Anda gunakan untuk mengatur panggilan balik ISR) ada dalam tanda kurung.

Peringatan: Jika Anda salah mengeja nama vektor interupsi, bahkan dengan hanya membuat kapitalisasi salah (hal yang mudah dilakukan) rutin interupsi tidak akan dipanggil , dan Anda tidak akan mendapatkan kesalahan kompilator.


Alasan menggunakan interupsi

Alasan utama Anda menggunakan interupsi adalah:

  • Untuk mendeteksi perubahan pin (mis. Rotary encoders, tombol ditekan)
  • Pengawas waktu (mis. Jika tidak ada yang terjadi setelah 8 detik, sela saya)
  • Timer interupsi - digunakan untuk membandingkan / meluap timer
  • Transfer data SPI
  • Transfer data I2C
  • Transfer data USART
  • Konversi ADC (analog ke digital)
  • EEPROM siap digunakan
  • Memori flash siap

"Transfer data" dapat digunakan untuk membiarkan program melakukan sesuatu yang lain ketika data sedang dikirim atau diterima pada port serial, port SPI, atau port I2C.

Bangun prosesor

Interupsi eksternal, interupsi perubahan pin, dan interupsi pengawas waktu, juga dapat digunakan untuk membangunkan prosesor. Ini bisa sangat berguna, karena dalam mode tidur prosesor dapat dikonfigurasikan untuk menggunakan daya yang jauh lebih sedikit (mis. Sekitar 10 microamps). Interupsi naik, turun, atau level rendah dapat digunakan untuk membangunkan gadget (mis. Jika Anda menekan tombol di atasnya), atau interupsi "watchdog timer" mungkin membangunkannya secara berkala (mis. Untuk memeriksa waktu atau suhu).

Interupsi penggantian pin dapat digunakan untuk membangunkan prosesor jika tombol ditekan pada keypad, atau serupa.

Prosesor juga dapat dibangunkan oleh penghenti waktu (mis. Penghitung waktu mencapai nilai tertentu, atau meluap) dan kejadian tertentu lainnya, seperti pesan I2C yang masuk.


Mengaktifkan / menonaktifkan interupsi

Interupsi "reset" tidak dapat dinonaktifkan. Namun interupsi lain dapat dinonaktifkan sementara dengan menghapus bendera interupsi global.

Aktifkan interupsi

Anda dapat mengaktifkan interupsi dengan pemanggilan fungsi "interupsi" atau "sei" seperti ini:

interrupts ();  // or ...
sei ();         // set interrupts flag

Nonaktifkan interupsi

Jika Anda perlu menonaktifkan interupsi, Anda dapat "menghapus" bendera interupsi global seperti ini:

noInterrupts ();  // or ...
cli ();           // clear interrupts flag

Salah satu metode memiliki efek yang sama, menggunakan interrupts/ noInterruptssedikit lebih mudah diingat di sekitar mereka.

Default di Arduino adalah agar interupsi diaktifkan. Jangan menonaktifkannya untuk waktu yang lama atau hal-hal seperti timer tidak akan berfungsi dengan baik.

Mengapa menonaktifkan interupsi?

Mungkin ada potongan kode waktu-kritis yang Anda tidak ingin terganggu, misalnya oleh penghenti waktu.

Juga jika bidang multi-byte sedang diperbarui oleh ISR maka Anda mungkin perlu menonaktifkan interupsi sehingga Anda mendapatkan data "secara atomis". Kalau tidak, satu byte dapat diperbarui oleh ISR saat Anda membaca yang lain.

Sebagai contoh:

noInterrupts ();
long myCounter = isrCounter;  // get value set by ISR
interrupts ();

Mematikan interupsi sementara untuk memastikan bahwa isrCounter (penghitung yang diatur di dalam ISR) tidak berubah saat kami mendapatkan nilainya.

Peringatan: jika Anda tidak yakin apakah interupsi sudah aktif atau belum, maka Anda harus menyimpan status saat ini dan mengembalikannya setelah itu. Misalnya, kode dari fungsi millis () melakukan ini:

unsigned long millis()
{
  unsigned long m;
  uint8_t oldSREG = SREG;    // <--------- save status register

  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  m = timer0_millis;
  SREG = oldSREG;            // <---------- restore status register including interrupt flag

  return m;
}

Perhatikan baris yang ditunjukkan menyimpan SREG saat ini (register status) yang mencakup bendera interrupt. Setelah kami memperoleh nilai timer (yang panjangnya 4 byte) kami mengembalikan status register kembali seperti semula.


Kiat

Nama fungsi

Fungsi cli/ seidan register SREG khusus untuk prosesor AVR. Jika Anda menggunakan prosesor lain seperti ARM, fungsinya mungkin sedikit berbeda.

Menonaktifkan secara global vs menonaktifkan satu interupsi

Jika Anda menggunakan, cli()Anda menonaktifkan semua interupsi (termasuk interupsi timer, interupsi serial, dll.).

Namun jika Anda hanya ingin menonaktifkan interupsi tertentu maka Anda harus menghapus tanda interrupt-enable untuk sumber interupsi tertentu. Misalnya, untuk gangguan eksternal, hubungi detachInterrupt().


Apa itu prioritas interupsi?

Karena ada 25 interupsi (selain reset), ada kemungkinan bahwa lebih dari satu peristiwa interupsi dapat terjadi sekaligus, atau setidaknya, terjadi sebelum yang sebelumnya diproses. Juga acara interupsi dapat terjadi saat interupsi dinonaktifkan.

Urutan prioritas adalah urutan di mana prosesor memeriksa peristiwa interupsi. Semakin tinggi daftar, semakin tinggi prioritas. Jadi, misalnya, Permintaan Interupsi Eksternal 0 (pin D2) akan dilayani sebelum Permintaan Interupsi Eksternal 1 (pin D3).


Dapatkah interupsi terjadi ketika interupsi dinonaktifkan?

Mengganggu acara (yaitu, memperhatikan peristiwa) dapat terjadi kapan saja, dan sebagian besar diingat dengan mengatur bendera "interrupt event" di dalam prosesor. Jika interupsi dinonaktifkan, maka interupsi itu akan ditangani ketika diaktifkan kembali, dalam urutan prioritas.


Bagaimana Anda menggunakan interupsi?

  • Anda menulis ISR (interupsi layanan rutin). Ini disebut ketika interupsi terjadi.
  • Anda memberi tahu prosesor saat ingin interupsi menyala.

Menulis ISR

Interrupt Service Routines adalah fungsi tanpa argumen. Beberapa perpustakaan Arduino dirancang untuk memanggil fungsi Anda sendiri, jadi Anda hanya menyediakan fungsi biasa (seperti dalam contoh di atas), misalnya.

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

Namun jika perpustakaan belum memberikan "kait" ke ISR, Anda dapat membuatnya sendiri, seperti ini:

volatile char buf [100];
volatile byte pos;

// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

Dalam hal ini Anda menggunakan makro "ISR", dan berikan nama vektor interupsi yang relevan (dari tabel sebelumnya). Dalam hal ini ISR ​​menangani penyelesaian transfer SPI. (Catatan, beberapa kode lama menggunakan SIGNAL dan bukan ISR, namun SIGNAL tidak digunakan lagi).

Menghubungkan ISR ke interupsi

Untuk gangguan yang sudah ditangani oleh perpustakaan, Anda cukup menggunakan antarmuka yang terdokumentasi. Sebagai contoh:

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
    {
    char c = Wire.receive ();
    // do something with the incoming byte
    }
}  // end of receiveEvent

void setup ()
  {
  Wire.onReceive(receiveEvent);
  }

Dalam hal ini perpustakaan I2C dirancang untuk menangani byte I2C yang masuk secara internal, dan kemudian memanggil fungsi yang disediakan Anda di akhir aliran data yang masuk. Dalam hal ini, acceptEvent bukan semata-mata ISR (itu memiliki argumen) tetapi disebut oleh ISR inbuilt.

Contoh lain adalah interupsi "pin eksternal".

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
}  // end of switchPressed

void setup ()
{
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);  // attach interrupt handler for D2
}  // end of setup

Dalam hal ini fungsi attachInterrupt menambahkan fungsi switchTekan ke tabel internal, dan sebagai tambahan mengkonfigurasi tanda interupsi yang sesuai pada prosesor.

Mengkonfigurasi prosesor untuk menangani interupsi

Langkah selanjutnya, setelah Anda memiliki ISR, adalah memberi tahu prosesor bahwa Anda ingin kondisi tertentu ini menimbulkan gangguan.

Sebagai contoh, untuk Interrupt Eksternal 0 (interupsi D2) Anda dapat melakukan sesuatu seperti ini:

EICRA &= ~3;  // clear existing flags
EICRA |= 2;   // set wanted flags (falling level interrupt)
EIMSK |= 1;   // enable it

Yang lebih mudah dibaca adalah menggunakan nama yang didefinisikan, seperti ini:

EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
EICRA |= bit (ISC01);    // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0);     // enable it

EICRA (External Interrupt Control Register A) akan diatur sesuai dengan tabel ini dari lembar data Atmega328. Itu mendefinisikan jenis interupsi yang Anda inginkan:

  • 0: Level INT0 yang rendah menghasilkan permintaan interupsi (LOW interrupt).
  • 1: Setiap perubahan logis pada INT0 menghasilkan permintaan interupsi (GANTI interupsi).
  • 2: Sisi jatuh dari INT0 menghasilkan permintaan interupsi (FALLING interrupt).
  • 3: Sisi naiknya INT0 menghasilkan permintaan interupsi (RISING interrupt).

EIMSK (External Interrupt Mask Register) sebenarnya memungkinkan interupsi.

Untungnya Anda tidak perlu mengingat angka-angka itu karena attachInterrupt melakukan itu untuk Anda. Namun itulah yang sebenarnya terjadi, dan untuk interupsi lain, Anda mungkin harus "secara manual" mengatur tanda interupsi.


ISR tingkat rendah vs. ISR perpustakaan

Untuk menyederhanakan hidup Anda, beberapa penangan interupsi yang sebenarnya ada di dalam kode perpustakaan (misalnya INT0_vect dan INT1_vect) dan kemudian antarmuka yang lebih ramah pengguna disediakan (mis. AttachInterrupt). Apa yang sebenarnya attachInterrupt lakukan adalah menyimpan alamat interrupt handler yang Anda inginkan ke dalam variabel, dan kemudian memanggilnya dari INT0_vect / INT1_vect saat dibutuhkan. Ini juga menetapkan bendera register yang sesuai untuk memanggil pawang bila diperlukan.


Apakah ISR dapat diinterupsi?

Singkatnya, tidak, tidak kecuali Anda menginginkannya.

Ketika ISR dimasukkan, interupsi dinonaktifkan . Tentu mereka harus diaktifkan sejak awal, jika tidak ISR tidak akan dimasukkan. Namun untuk menghindari ISR ​​sendiri terganggu, prosesor mati.

Ketika ISR keluar, maka interupsi diaktifkan lagi . Compiler juga menghasilkan kode di dalam ISR untuk menyimpan register dan flag status, sehingga apa pun yang Anda lakukan ketika interupsi terjadi tidak akan terpengaruh.

Namun Anda dapat mengaktifkan interupsi di dalam ISR jika Anda benar-benar harus, misalnya.

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
  interrupts ();  // allow more interrupts

}  // end of switchPressed

Biasanya Anda perlu alasan yang cukup bagus untuk melakukan ini, karena gangguan lain sekarang dapat menghasilkan panggilan rekursif ke pinChange, dengan hasil yang sangat tidak diinginkan.


Berapa lama untuk menjalankan ISR?

Menurut datasheet, jumlah minimal waktu untuk layanan interupsi adalah siklus 4 jam (untuk mendorong penghitung program saat ini ke stack) diikuti oleh kode yang sekarang mengeksekusi di lokasi vektor interrupt. Ini biasanya berisi lompatan ke tempat rutin interupsi sebenarnya, yang merupakan 3 siklus lainnya. Pemeriksaan kode yang dihasilkan oleh kompiler menunjukkan bahwa ISR yang dibuat dengan deklarasi "ISR" dapat memakan waktu sekitar 2,625 μs untuk dieksekusi, ditambah apa pun yang dilakukan oleh kode itu sendiri. Jumlah persisnya tergantung pada berapa banyak register yang perlu disimpan dan dipulihkan. Jumlah minimum adalah 1,1875 μs.

Interupsi eksternal (di mana Anda menggunakan attachInterrupt) melakukan sedikit lebih banyak dan memakan waktu sekitar 5,125 μs total (berjalan dengan clock 16 MHz).


Berapa lama sebelum prosesor mulai memasuki ISR?

Ini agak bervariasi. Angka-angka yang dikutip di atas adalah yang ideal di mana interupsi segera diproses. Beberapa faktor dapat menunda itu:

  • Jika prosesor tertidur, ada waktu yang disebut "bangun", yang bisa jadi beberapa milidetik, sementara jam digulung kembali ke kecepatan. Waktu ini akan tergantung pada pengaturan sekering, dan seberapa dalam tidurnya.

  • Jika rutin layanan interupsi sudah dijalankan, maka interupsi lebih lanjut tidak dapat dimasukkan sampai selesai, atau memungkinkan interupsi itu sendiri. Inilah sebabnya mengapa Anda harus menjaga setiap rutinitas layanan interupsi tetap pendek, karena setiap mikrodetik yang Anda habiskan dalam satu, Anda berpotensi menunda eksekusi yang lain.

  • Beberapa kode ternyata terputus. Misalnya, memanggil millis () secara singkat mematikan interupsi. Oleh karena itu waktu untuk interupsi akan dilayani akan diperpanjang dengan lamanya waktu interupsi dimatikan.

  • Interupsi hanya dapat dilayani pada akhir instruksi, jadi jika instruksi tertentu membutuhkan tiga siklus clock, dan baru saja dimulai, maka interupsi akan tertunda setidaknya beberapa siklus clock.

  • Suatu peristiwa yang menghidupkan kembali interupsi (mis. Kembali dari layanan rutin interupsi) dijamin untuk mengeksekusi setidaknya satu instruksi lagi. Jadi, bahkan jika ISR berakhir, dan interupsi Anda tertunda, ia masih harus menunggu satu instruksi lagi sebelum diperbaiki.

  • Karena interupsi memiliki prioritas, interupsi dengan prioritas lebih tinggi mungkin dilayani sebelum interupsi yang Anda minati.


Pertimbangan kinerja

Interupsi dapat meningkatkan kinerja dalam banyak situasi karena Anda dapat melanjutkan "pekerjaan utama" program Anda tanpa harus terus-menerus menguji untuk melihat apakah sakelar telah ditekan. Karena itu, biaya perbaikan interupsi, seperti dibahas di atas, sebenarnya akan lebih dari sekadar melakukan "pengetatan" polling pada satu port input. Anda hampir tidak bisa menanggapi suatu peristiwa dalam, katakanlah, satu mikrodetik. Dalam hal ini Anda dapat menonaktifkan interupsi (mis. Timer) dan hanya mencari pin untuk berubah.


Bagaimana interupsi dilakukan?

Ada dua macam interupsi:

  • Beberapa mengatur bendera dan mereka ditangani dalam urutan prioritas, bahkan jika acara yang menyebabkan mereka telah berhenti. Misalnya, naik, turun, atau berubah level mengganggu pada pin D2.

  • Yang lain hanya diuji jika sedang terjadi "sekarang". Misalnya, interupsi tingkat rendah pada pin D2.

Yang mengatur bendera dapat dianggap sebagai antrian, karena bendera interupsi tetap ditetapkan sampai waktu rutin interupsi dimasukkan, di mana saat itu prosesor membersihkan bendera. Tentu saja, karena hanya ada satu flag, jika kondisi interupsi yang sama terjadi lagi sebelum yang pertama diproses, itu tidak akan dilayani dua kali.

Yang perlu diperhatikan adalah bahwa flag-flag ini dapat diatur sebelum Anda memasang interrupt handler. Misalnya, ada kemungkinan naik atau turunnya level interupsi pada pin D2 menjadi "ditandai", dan kemudian segera setelah Anda melakukan attach, interupsi interupsi langsung menyala, bahkan jika peristiwa terjadi satu jam yang lalu. Untuk menghindari ini, Anda dapat menghapus bendera secara manual. Sebagai contoh:

EIFR = bit (INTF0);  // clear flag for interrupt 0
EIFR = bit (INTF1);  // clear flag for interrupt 1

Namun interupsi "level rendah" terus menerus diperiksa, jadi jika Anda tidak hati-hati mereka akan terus menembak, bahkan setelah interupsi dipanggil. Artinya, ISR akan keluar, dan kemudian interupsi akan segera menyala lagi. Untuk menghindari hal ini, Anda harus melakukan detachInterrupt segera setelah Anda tahu bahwa interupsi itu diaktifkan.


Petunjuk untuk menulis ISR

Singkatnya, buat mereka singkat! Sementara ISR mengeksekusi interupsi lain tidak dapat diproses. Jadi, Anda dapat dengan mudah kehilangan tombol tekan, atau komunikasi serial yang masuk, jika Anda mencoba melakukan terlalu banyak. Secara khusus, Anda tidak harus mencoba melakukan debug "mencetak" di dalam ISR. Waktu yang dibutuhkan untuk melakukan hal itu cenderung menyebabkan lebih banyak masalah daripada yang mereka pecahkan.

Suatu hal yang masuk akal untuk dilakukan adalah mengatur flag single-byte, dan kemudian mengujinya di fungsi loop utama. Atau, simpan byte yang masuk dari port serial ke buffer. Timer inbuilt menyela melacak waktu yang berlalu dengan menembakkan setiap kali timer internal meluap, dan dengan demikian Anda dapat menghitung waktu berlalu dengan mengetahui berapa kali timer meluap.

Ingat, di dalam interupsi ISR ​​dinonaktifkan. Dengan demikian berharap bahwa waktu yang dikembalikan oleh panggilan fungsi millis () akan berubah, akan menyebabkan kekecewaan. Adalah sah untuk mendapatkan waktu seperti itu, hanya perlu diketahui bahwa timer tidak bertambah. Dan jika Anda menghabiskan waktu terlalu lama di ISR ​​maka timer mungkin melewatkan acara meluap, yang mengarah ke waktu yang dikembalikan oleh millis () menjadi salah.

Sebuah tes menunjukkan bahwa, pada prosesor Atmega328 16 MHz, panggilan ke micros () membutuhkan 3,5625 μs. Panggilan ke millis () membutuhkan 1,9375 μs. Merekam (menyimpan) nilai timer saat ini adalah hal yang wajar untuk dilakukan dalam ISR. Menemukan milidetik yang berlalu lebih cepat daripada mikrodetik yang berlalu (hitungan milidetik hanya diambil dari variabel). Namun hitungan mikrodetik diperoleh dengan menambahkan nilai saat ini dari Timer 0 timer (yang akan terus bertambah) ke "Timer 0 overflow count" tersimpan.

Peringatan: Karena interupsi dinonaktifkan di dalam ISR, dan karena versi terbaru dari Arduino IDE menggunakan interupsi untuk membaca dan menulis serial, dan juga untuk menambah penghitung yang digunakan oleh "millis" dan "delay" Anda tidak boleh mencoba menggunakan fungsi-fungsi tersebut di dalam ISR. Dengan kata lain:

  • Jangan mencoba menunda, misalnya: delay (100);
  • Anda bisa mendapatkan waktu dari panggilan ke milis, namun itu tidak akan bertambah, jadi jangan mencoba untuk menunda dengan menunggu untuk meningkat.
  • Jangan lakukan pencetakan serial (mis. Serial.println ("ISR entered");)
  • Jangan mencoba membaca serial.

Perubahan pin terputus

Ada dua cara Anda dapat mendeteksi peristiwa eksternal pada pin. Yang pertama adalah pin "interupsi eksternal" khusus, D2 dan D3. Peristiwa interupsi diskrit umum ini, satu per pin. Anda bisa mendapatkannya dengan menggunakan attachInterrupt untuk setiap pin. Anda dapat menentukan kondisi naik, turun, berubah atau tingkat rendah untuk gangguan.

Namun ada juga "pin change" interupsi untuk semua pin (pada Atmega328, belum tentu semua pin pada prosesor lain). Ini bekerja pada kelompok pin (D0 ke D7, D8 ke D13, dan A0 ke A5). Mereka juga prioritas lebih rendah daripada gangguan acara eksternal. Namun mereka sedikit lebih fiddly untuk digunakan daripada interupsi eksternal karena mereka dikelompokkan menjadi beberapa kelompok. Jadi, jika api interupsi Anda harus bekerja dalam kode Anda sendiri persis pin mana yang menyebabkan interupsi.

Kode contoh:

ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 }  // end of PCINT2_vect


void setup ()
  {
  // pin change interrupt (example for D9)
  PCMSK0 |= bit (PCINT1);  // want pin 9
  PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13
  }

Untuk menangani interupsi penggantian pin, Anda harus:

  • Tentukan pin mana dalam grup. Ini adalah variabel PCMSKn (di mana n adalah 0, 1 atau 2 dari tabel di bawah). Anda dapat memiliki interupsi pada lebih dari satu pin.
  • Aktifkan grup interupsi yang sesuai (0, 1 atau 2)
  • Berikan interrupt handler seperti yang ditunjukkan di atas

Daftar pin -> pin ganti nama / topeng

D0    PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1    PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2    PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3    PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4    PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5    PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6    PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7    PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8    PCINT0  (PCMSK0 / PCIF0 / PCIE0)
D9    PCINT1  (PCMSK0 / PCIF0 / PCIE0)
D10   PCINT2  (PCMSK0 / PCIF0 / PCIE0)
D11   PCINT3  (PCMSK0 / PCIF0 / PCIE0)
D12   PCINT4  (PCMSK0 / PCIF0 / PCIE0)
D13   PCINT5  (PCMSK0 / PCIF0 / PCIE0)
A0    PCINT8  (PCMSK1 / PCIF1 / PCIE1)
A1    PCINT9  (PCMSK1 / PCIF1 / PCIE1)
A2    PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3    PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4    PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5    PCINT13 (PCMSK1 / PCIF1 / PCIE1)

Pemrosesan interrupt handler

Handler interrupt perlu mencari tahu pin mana yang menyebabkan interupsi jika topeng menentukan lebih dari satu (mis. Jika Anda ingin interupsi pada D8 / D9 / D10). Untuk melakukan ini, Anda perlu menyimpan status pin sebelumnya, dan bekerja (dengan melakukan digitalRead atau serupa) jika pin khusus ini telah berubah.


Anda mungkin menggunakan interupsi ...

Lingkungan Arduino yang "normal" sudah menggunakan interupsi, bahkan jika Anda tidak berusaha secara pribadi. Panggilan fungsi millis () dan micros () memanfaatkan fitur "timer overflow". Salah satu penghitung waktu internal (timer 0) diatur untuk menyela sekitar 1000 kali per detik, dan menambah penghitung internal yang secara efektif menjadi penghitung millis (). Ada sedikit lebih dari itu, karena penyesuaian dibuat untuk kecepatan clock yang tepat.

Juga pustaka serial perangkat keras menggunakan interupsi untuk menangani data serial yang masuk dan keluar. Ini sangat berguna karena program Anda dapat melakukan hal-hal lain ketika interupsi menyala, dan mengisi buffer internal. Kemudian ketika Anda memeriksa Serial.available () Anda dapat mengetahui apa, jika ada, yang telah ditempatkan di buffer itu.


Menjalankan instruksi selanjutnya setelah mengaktifkan interupsi

Setelah sedikit diskusi dan penelitian di forum Arduino, kami telah mengklarifikasi persis apa yang terjadi setelah Anda mengaktifkan interupsi. Ada tiga cara utama yang saya pikirkan yaitu Anda dapat mengaktifkan interupsi, yang sebelumnya tidak diaktifkan:

  sei ();  // set interrupt enable flag
  SREG |= 0x80;  // set the high-order bit in the status register
  reti  ;   // assembler instruction "return from interrupt"

Dalam semua kasus, prosesor menjamin bahwa instruksi berikutnya setelah interupsi diaktifkan (jika sebelumnya dinonaktifkan) akan selalu dieksekusi, bahkan jika acara interupsi sedang tertunda. (Dengan "berikutnya" yang saya maksud adalah yang berikutnya dalam urutan program, tidak harus yang secara fisik mengikuti. Misalnya, instruksi RETI melompat kembali ke tempat interupsi terjadi, dan kemudian mengeksekusi satu instruksi lagi).

Ini memungkinkan Anda menulis kode seperti ini:

sei ();
sleep_cpu ();

Jika bukan karena jaminan ini, interupsi mungkin terjadi sebelum prosesor tidur, dan kemudian mungkin tidak pernah terbangun.


Kosong menyela

Jika Anda hanya ingin interupsi untuk membangunkan prosesor, tetapi tidak melakukan apa pun secara khusus, Anda dapat menggunakan definisi EMPTY_INTERRUPT, mis.

EMPTY_INTERRUPT (PCINT1_vect);

Ini hanya menghasilkan instruksi "reti" (kembali dari interupsi). Karena tidak mencoba menyimpan atau mengembalikan register, ini akan menjadi cara tercepat untuk mendapatkan interupsi untuk membangunkannya.


Bagian kritis (akses variabel atom)

Ada beberapa masalah halus mengenai variabel yang dibagi antara interrupt service rutin (ISRs) dan kode utama (yaitu, kode tidak ada dalam ISR).

Karena ISR dapat diaktifkan kapan saja ketika interupsi diaktifkan, Anda perlu berhati-hati dalam mengakses variabel bersama tersebut, karena variabel tersebut dapat diperbarui pada saat Anda mengaksesnya.

Pertama ... kapan Anda menggunakan variabel "volatil"?

Variabel hanya boleh ditandai volatil jika digunakan baik di dalam ISR, dan di luar satu.

  • Variabel yang hanya digunakan di luar ISR tidak boleh volatil.
  • Variabel yang hanya digunakan di dalam ISR tidak boleh volatil.
  • Variabel yang digunakan baik di dalam maupun di luar ISR harus volatile.

misalnya.

volatile int counter;

Menandai sebuah variabel sebagai volatile memberitahu kompiler untuk tidak "cache" isi variabel ke dalam register prosesor, tetapi selalu membacanya dari memori, bila diperlukan. Ini dapat memperlambat pemrosesan, itulah sebabnya Anda tidak hanya membuat setiap variabel volatil, saat tidak diperlukan.

Matikan interupsi saat mengakses variabel volatil

Misalnya, untuk membandingkan countke beberapa nomor, matikan interupsi selama perbandingan jika satu byte counttelah diperbarui oleh ISR dan bukan byte lainnya.

volatile unsigned int count;

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

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

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

Baca lembar data!

Informasi lebih lanjut tentang interupsi, timer, dll. Dapat diperoleh dari lembar data untuk prosesor.

http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf


Contoh lebih lanjut

Pertimbangan ruang (batas ukuran posting) mencegah listing saya lebih banyak contoh kode. Untuk contoh kode lebih lanjut lihat halaman saya tentang interupsi .

Nick Gammon
sumber
Referensi yang sangat berguna - itu adalah jawaban yang sangat cepat.
Dat Han Bag
Itu pertanyaan referensi. Saya sudah menyiapkan jawabannya dan akan lebih cepat jika jawabannya tidak terlalu lama, jadi saya harus memangkasnya kembali. Lihat situs tertaut untuk detail lebih lanjut.
Nick Gammon
Tentang "mode tidur" apakah efisien untuk membuat Arduino tidur, katakanlah, 500 ms?
Dat Ha
@Nick Gammon Saya kira menyalakan atau mematikan daya (dengan otomatisasi atau tidak) untuk CPU dapat didefinisikan sebagai interupsi yang tidak konvensional- jika Anda ingin melakukan itu. "Aku sudah menyiapkan jawabannya" -kamu baru saja mengeluarkan semua keajaiban saat itu yang kupikir sudah.
Dat Han Bag
1
Saya khawatir itu tidak benar. Saya punya contoh yang menggunakan interupsi penggantian pin untuk bangun dari mode power-down. Juga seperti yang saya sebutkan di halaman saya tentang interupsi Atmel telah mengkonfirmasi bahwa interupsi eksternal akan membangunkan prosesor (mis. Naik / turun / berubah dan rendah).
Nick Gammon