Apakah fungsi penunjuk fungsi atom di Arduino?

11

Cuplikan berikut ini dari kode sumber pustaka 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();                                                                                            
}

Pertanyaannya: jika penghitung waktu sudah berjalan, dan program utama memanggil attachInterrupt(), dapatkah penghentian waktu terjadi di sana selama penugasan fungsi pointer isrCallback = isr;? Kemudian, dengan lucky timing, Timer1.isrCallback();function pointer akan terdiri dari sebagian dari yang lama dan sebagian dari alamat baru, menyebabkan lompatan ISR ke lokasi palsu?

Saya kira ini mungkin terjadi, karena fungsi pointer tentu lebih lebar dari 1 byte, dan mengakses data> 1 byte bukanlah atom. Solusi yang bisa dilakukan adalah:

  • Selalu panggil detachInterrupt()untuk memastikan timer tidak berjalan, sebelum memanggil attachInterrupt(), yaitu memperjelas Timer1 docs.
  • Atau, ubah Timer1, nonaktifkan timer overflow untuk sementara waktu sesaat sebelumnya isrCallback = isr;

Apakah ini masuk akal, atau ada sesuatu dalam Timer1penugasan fungsi sumber atau fungsi yang saya lewatkan?

Joonas Pulakka
sumber

Jawaban:

7

Lihat kode untuk attachInterrupt () dan detachInterrupt () di /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(well, di situlah mereka berada di Mac, struktur file Arduino pada OS lain 'mungkin terlihat serupa di tingkat yang lebih rendah dari jalur).

Tampaknya attachInterrupt () mengasumsikan bahwa interupsi yang dimaksud belum diaktifkan karena ia menulis pointer fungsi tanpa mengambil tindakan pencegahan apa pun. Perhatikan bahwa detachInterrupts () menonaktifkan interupsi target sebelum menulis pointer-NULL ke vektornya. Jadi saya setidaknya menggunakan a detachInterrupt()/ attachInterrupt()pair

Saya ingin menjalankan kode semacam itu di bagian kritis, sendiri. Tampaknya cara pertama Anda (lepaskan, lalu lampirkan) akan berfungsi, meskipun saya tidak yakin itu tidak dapat melewatkan interupsi yang sayangnya waktunya. Lembar data untuk MCU Anda mungkin memiliki lebih banyak bicara tentang itu. Tapi baik aku yakin pada saat ini, bahwa global cli()/ sei()tidak akan melewatkannya baik. Lembar data ATMega2560, bagian 6.8, mengatakan "Saat menggunakan instruksi SEI untuk mengaktifkan interupsi, instruksi berikut SEI akan dieksekusi sebelum interupsi yang tertunda, seperti yang ditunjukkan dalam contoh ini", tampaknya menyiratkan bahwa ia dapat buffer interupsi sementara interupsi tidak aktif.

JRobert
sumber
Ini memang berguna untuk menyelam ke sumber :) Mekanisme interupsi attach / detach TimerOne tampaknya dilakukan sama seperti standar (WInterrupt) dan memiliki "fitur" yang sama.
Joonas Pulakka
0

Sepertinya Anda ada benarnya. Hal logis yang harus dilakukan adalah menonaktifkan interupsi sedemikian rupa sehingga Anda tidak mengaktifkannya kembali jika dinonaktifkan terlebih dahulu. Sebagai contoh:

  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

"Saat menggunakan instruksi SEI untuk mengaktifkan interupsi, instruksi SEI berikut akan dieksekusi sebelum interupsi yang tertunda, seperti yang ditunjukkan dalam contoh ini"

Maksudnya adalah agar Anda menulis kode seperti ini:

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

Tanpa ketentuan itu, Anda mungkin mendapat gangguan di antara dua baris itu, dan karenanya tidur tanpa batas waktu (karena gangguan yang akan membangunkan Anda telah terjadi sebelum Anda tidur). Ketentuan dalam prosesor bahwa instruksi berikutnya, setelah interupsi menjadi diaktifkan jika mereka tidak diaktifkan sebelumnya, selalu dieksekusi, penjaga terhadap ini.

Nick Gammon
sumber
Bukankah lebih efisien TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);? Itu menyimpan satu register CPU dan tidak berkontribusi mengganggu latensi.
Edgar Bonet
Bagaimana dengan bit lainnya, seperti ICIE1, OCIE1B, OCIE1A? Saya mengerti tentang latensi, tetapi interupsi harus dapat mengatasi beberapa siklus clock yang tidak tersedia. Mungkin ada kondisi balapan. Interupsi mungkin sudah dipicu (mis. Bendera di CPU diatur) dan mematikan TIMSK1 mungkin tidak menghentikannya ditangani. Anda mungkin juga perlu mengatur ulang TOV1 (dengan menulis 1 di TIFR1) untuk memastikan itu tidak terjadi. Ketidakpastian di sana membuat saya berpikir bahwa mematikan interupsi secara global adalah jalan yang lebih aman.
Nick Gammon