Apakah ada cara yang baik untuk mengimplementasikan komunikasi antara ISR dan program lainnya untuk sistem embedded yang menghindari variabel global?
Tampaknya pola umum adalah memiliki variabel global yang dibagi antara ISR dan program lainnya dan digunakan sebagai bendera, tetapi penggunaan variabel global ini bertentangan dengan saya. Saya telah menyertakan contoh sederhana menggunakan ISR gaya avr-libc:
volatile uint8_t flag;
int main() {
...
if (flag == 1) {
...
}
...
}
ISR(...) {
...
flag = 1;
...
}
Saya tidak bisa melihat apa yang sebenarnya merupakan masalah pelingkupan; variabel apa saja yang dapat diakses oleh ISR dan program lainnya harus bersifat global, tentunya? Meskipun demikian, saya sering melihat orang mengatakan hal-hal seperti "variabel global adalah salah satu cara untuk menerapkan komunikasi antara ISR dan program lainnya" (penekanan tambang), yang tampaknya menyiratkan bahwa ada metode lain; jika ada metode lain, apa itu?
Jawaban:
Ada cara standar de facto untuk melakukan ini (dengan asumsi pemrograman C):
extern
kata kunci atau hanya karena kesalahan.static
. Variabel seperti itu tidak bersifat global tetapi terbatas pada file yang dideklarasikan.volatile
. Catatan: ini tidak memberikan akses atomik atau menyelesaikan re-entrancy!sumber
inline
menjadi usang, karena kompiler menjadi lebih pintar dan lebih pintar dalam mengoptimalkan kode. Saya akan mengatakan bahwa mengkhawatirkan overhead adalah "optimasi pra-matang" - dalam kebanyakan kasus overhead tidak masalah, jika itu sama sekali bahkan ada dalam kode mesin.Inilah masalah sebenarnya. Lupakan saja.
Sekarang sebelum lutut-jerker segera berteriak-teriak tentang bagaimana ini najis, izinkan saya sedikit memenuhi syarat itu. Tentu saja ada bahaya dalam menggunakan variabel global secara berlebihan. Tetapi, mereka juga dapat meningkatkan efisiensi, yang terkadang penting dalam sistem terbatas sumber daya yang kecil.
Kuncinya adalah memikirkan kapan Anda bisa menggunakannya secara masuk akal dan tidak akan membuat masalah, dibandingkan bug yang menunggu untuk terjadi. Selalu ada pengorbanan. Walaupun umumnya menghindari variabel global untuk mengkomunikasikan antara kode interrupt dan foreground adalah pedoman yang bisa dilakukan, mengambilnya, seperti kebanyakan pedoman lainnya, pada ekstrem agama adalah kontra-produktif.
Beberapa contoh di mana saya kadang-kadang menggunakan variabel global untuk menyampaikan informasi antara kode interrupt dan foreground adalah:
Saya memastikan kutu 1 ms, 10 ms, dan 100 ms memiliki ukuran kata yang dapat dibaca dalam satu operasi atom. Jika menggunakan bahasa tingkat tinggi, pastikan untuk memberi tahu kompiler bahwa variabel-variabel ini dapat berubah secara tidak sinkron. Dalam C, Anda menyatakan mereka sebagai volatile eksternal , misalnya. Tentu saja ini adalah sesuatu yang dimasukkan ke dalam file kaleng kalengan, jadi Anda tidak perlu mengingatnya untuk setiap proyek.
Saya kadang-kadang membuat penghitung centang 1 s penghitung waktu berlalu total, jadi buat lebar 32 bit. Itu tidak dapat dibaca dalam operasi atom tunggal pada banyak mikro kecil yang saya gunakan, sehingga tidak dibuat mendunia. Alih-alih, disediakan rutin yang membaca nilai multi-kata, menangani kemungkinan pembaruan di antara bacaan, dan mengembalikan hasilnya.
Tentu saja ada bisa menjadi rutinitas untuk mendapatkan yang lebih kecil 1 ms, 10 ms, dll, centang counter juga. Namun, itu benar-benar sangat sedikit untuk Anda, menambahkan banyak instruksi untuk membaca satu kata, dan menggunakan lokasi tumpukan panggilan lainnya.
Apa kekurangannya? Saya kira seseorang dapat membuat kesalahan ketik yang secara tidak sengaja menulis ke salah satu konter, yang kemudian dapat mengacaukan waktu lain dalam sistem. Menulis ke konter dengan sengaja tidak masuk akal, jadi bug semacam ini perlu sesuatu yang tidak disengaja seperti kesalahan ketik. Tampaknya sangat tidak mungkin. Saya tidak ingat pernah terjadi di lebih dari 100 proyek mikrokontroler kecil.
Misalnya, A / D mungkin membaca output 0 hingga 3 V dari pembagi tegangan untuk mengukur pasokan 24 V. Banyak bacaan dijalankan melalui beberapa pemfilteran, kemudian diskalakan sehingga nilai akhir dalam milivolt. Jika persediaan di 24.015 V, maka nilai akhirnya adalah 24015.
Sisanya dari sistem hanya melihat nilai pembaruan langsung yang menunjukkan tegangan suplai. Itu tidak tahu atau tidak perlu peduli kapan persisnya itu diperbarui, terutama karena itu diperbarui jauh lebih sering daripada waktu penyelesaian filter low pass.
Sekali lagi, rutin antarmuka dapat digunakan, tetapi Anda hanya mendapat sedikit manfaat dari itu. Hanya menggunakan variabel global setiap kali Anda membutuhkan tegangan catu daya jauh lebih sederhana. Ingatlah bahwa kesederhanaan bukan hanya untuk mesin, tetapi lebih sederhana juga berarti lebih sedikit kemungkinan kesalahan manusia.
sumber
extern int ticks10ms
denganinline int getTicks10ms()
akan sama sekali tidak membuat perbedaan dalam rakitan yang dikompilasi, sementara di sisi lain itu akan membuat sulit untuk secara tidak sengaja mengubah nilainya di bagian lain dari program, dan juga memungkinkan Anda untuk cara untuk "menghubungkan" ke panggilan ini (misalnya untuk mengejek waktu selama pengujian unit, untuk mencatat akses ke variabel ini, atau apa pun). Bahkan jika Anda berpendapat bahwa kesempatan seorang programmer san mengubah variabel ini menjadi nol, tidak ada biaya bagi pengambil inline.Setiap gangguan tertentu akan menjadi sumber daya global. Namun kadang-kadang, mungkin berguna untuk meminta beberapa interupsi berbagi kode yang sama. Misalnya, sistem mungkin memiliki beberapa UART, yang semuanya harus menggunakan logika kirim / terima yang serupa.
Pendekatan yang baik untuk menangani itu adalah menempatkan hal-hal yang digunakan oleh pengendali interupsi, atau petunjuk kepada mereka, dalam objek struktur, dan kemudian memiliki pengendali interrupt hardware sebenarnya menjadi sesuatu seperti:
Objek
uart1_info
,uart2_info
dll. Akan menjadi variabel global, tetapi mereka akan menjadi satu - satunya variabel global yang digunakan oleh pengendali interupsi. Segala hal lain yang akan disentuh pawang akan ditangani di dalamnya.Perhatikan bahwa apa pun yang diakses oleh interrupt handler dan oleh kode jalur utama harus memenuhi syarat
volatile
. Mungkin paling sederhana untuk menyatakan sebagaivolatile
segala sesuatu yang akan digunakan sama sekali oleh pengendali interupsi, tetapi jika kinerjanya penting seseorang mungkin ingin menulis kode yang menyalin informasi ke nilai sementara, mengoperasikannya, dan kemudian menulisnya kembali. Misalnya, alih-alih menulis:menulis:
Pendekatan yang pertama mungkin lebih mudah dibaca dan dipahami, tetapi akan kurang efisien daripada yang terakhir. Apakah itu masalah akan tergantung pada aplikasi.
sumber
Inilah tiga ide:
Deklarasikan variabel flag sebagai statis untuk membatasi cakupan ke satu file.
Jadikan variabel flag sebagai pribadi dan gunakan fungsi pengambil dan penyetel untuk mengakses nilai bendera.
Gunakan objek pensinyalan seperti semaphore dan bukan variabel flag. ISR akan mengatur / memposting semafor.
sumber
Interrupt (yaitu, vektor yang menunjuk ke handler Anda) adalah sumber daya global. Jadi, bahkan jika Anda menggunakan beberapa variabel di stack atau di heap:
atau kode berorientasi objek dengan fungsi 'virtual':
... langkah pertama harus melibatkan variabel global aktual (atau setidaknya statis) untuk mencapai data lainnya.
Semua mekanisme ini menambah tipuan, jadi ini biasanya tidak dilakukan jika Anda ingin memeras siklus terakhir dari interrupt handler.
sumber
Saya sedang mengkode Cortex M0 / M4 saat ini dan pendekatan yang kami gunakan dalam C ++ (tidak ada tag C ++, jadi jawaban ini mungkin di luar topik) adalah sebagai berikut:
Kami menggunakan kelas
CInterruptVectorTable
yang berisi semua rutinitas layanan interupsi yang disimpan dalam vektor interupsi aktual dari pengontrol:Kelas
CInterruptVectorTable
mengimplementasikan abstraksi vektor interupsi, sehingga Anda dapat mengikat berbagai fungsi ke vektor interupsi selama runtime.Antarmuka kelas itu terlihat seperti ini:
Anda perlu membuat fungsi-fungsi yang disimpan dalam tabel vektor
static
karena pengontrol tidak dapat memberikanthis
-poiner karena tabel vektor bukan objek. Jadi untuk mengatasi masalah itu kita memilikipThis
pointer- statis di dalamCInterruptVectorTable
. Setelah memasukkan salah satu fungsi interupsi statis, ia dapat mengaksespThis
-pointer untuk mendapatkan akses ke anggota satu objekCInterruptVectorTable
.Sekarang dalam program, Anda dapat menggunakan
SetIsrCallbackfunction
untuk menyediakan pointer fungsi kestatic
fungsi yang akan dipanggil ketika interupsi terjadi. Pointer disimpan diInterruptVectorTable_t virtualVectorTable
.Dan implementasi fungsi interupsi terlihat seperti ini:
Sehingga akan memanggil
static
metode kelas lain (yang bisaprivate
), yang kemudian dapat berisistatic
this
-pointer lain untuk mendapatkan akses ke variabel anggota objek itu (hanya satu).Saya kira Anda bisa membangun dan antarmuka suka
IInterruptHandler
dan menyimpan pointer ke objek, sehingga Anda tidak perlu-static
this
pointer di semua kelas tersebut. (mungkin kita mencobanya di iterasi arsitektur kita selanjutnya)Pendekatan lain berfungsi dengan baik bagi kami, karena satu-satunya objek yang diizinkan untuk mengimplementasikan pengendali interupsi adalah yang ada di dalam lapisan abstraksi perangkat keras, dan kami biasanya hanya memiliki satu objek untuk setiap blok perangkat keras, sehingga bekerja dengan-
static
this
pointer. Dan lapisan abstraksi perangkat keras menyediakan abstraksi lain untuk menyela, yang disebutICallback
yang kemudian diimplementasikan dalam lapisan perangkat di atas perangkat keras.Apakah Anda mengakses data global? Tentu Anda melakukannya, tetapi Anda dapat menjadikan sebagian besar data global yang diperlukan bersifat pribadi seperti
this
-poiner dan fungsi interupsi.Ini bukan antipeluru, dan itu menambah overhead. Anda akan kesulitan menerapkan tumpukan IO-Link menggunakan pendekatan ini. Tetapi jika Anda tidak terlalu ketat dengan pengaturan waktu, ini bekerja cukup baik untuk mendapatkan abstraksi fleksibel dari interupsi dan komunikasi dalam modul tanpa menggunakan variabel global yang dapat diakses dari mana saja.
sumber