Membuat firmware responsif dengan interrupts: model exception ARM Cortex-M dan NVIC, prioritas dan preemption, menulis ISR yang singkat dan tidak memblokir, interrupt-safe code dengan volatile, critical section, serta pola berbagi data antara ISR dan main loop.

Setelah di episode 5 firmware kita berkomunikasi dengan peripheral secara polling, pada episode ini kita berhenti "menunggu" dan mulai "dipanggil". Interrupt mengubah cara kerja firmware: alih-alih CPU menunggu data datang, hardware menghentikan pekerjaan saat ini sejenak, menjalankan handler, lalu kembali. Ini fondasi sistem real-time yang akan kita bangun di episode 7 (RTOS) dan 16 (real-time constraints).
Mengapa ini penting? Karena polling membuang CPU dan memperlambat respons. Sebuah button yang harus di-poll jutaan kali per detik bisa, dengan interrupt, memicu handler dalam mikrodetik. Hampir semua firmware profesional — driver, RTOS, komunikasi — bergantung pada interrupt yang ditulis dengan benar.
ARM Cortex-M punya dua jenis "interupsi": system exceptions (internal) dan external interrupts (IRQ dari peripheral). Beberapa yang penting:
| Exception | Sumber | Prioritas |
|---|---|---|
| Reset | Power-on / reset | -3 (tertinggi) |
| NMI | Non-maskable | -2 |
| HardFault | Error tak tertangani | -1 |
| SysTick | Sistem timer | Konfigurabel |
| PendSV | Penjadwalan konteks (RTOS) | Konfigurabel |
| IRQ0..IRQ79 | External (GPIO, UART, timer) | Konfigurabel |
Alamat handler berada di vector table (episode 2). Saat interrupt terjadi, CPU menyimpan konteks (register) ke stack, memuat alamat handler dari vector table, menjalankannya, lalu memulihkan konteks — semua otomatis oleh hardware.
NVIC (Nested Vectored Interrupt Controller) adalah unit yang mengelola interrupt pada Cortex-M. Konsep kuncinya:
// contoh: IRQ UART2 diaktifkan di NVIC
NVIC_SetPriority(USART2_IRQn, 2); // prioritas preempt = 2
NVIC_EnableIRQ(USART2_IRQn); // aktifkanKesalahan klasik: dua interrupt berbagi data tanpa kontrol prioritas. Aturan praktis: buat daftar prioritas di satu tempat (misal header), dan jangan mengubah prioritas setelah desain setuju — ubah di satu tempat = mudah diverifikasi.
ISR (Interrupt Service Routine) punya aturan emas: sesingkat mungkin dan jangan pernah memblokir.
// BURUK: kerja berat di ISR
void USART2_IRQHandler(void) {
while (!(USART2->SR & USART_SR_RXNE)); // blokir!
process_full_packet(); // komputasi lama
transmit_reply(); // menunggu TX
}
// BAIK: ISR hanya set flag + simpan data, kerja di main
volatile bool uart_data_ready = false;
volatile uint8_t last_byte = 0;
void USART2_IRQHandler(void) {
if (USART2->SR & USART_SR_RXNE) {
last_byte = (uint8_t)USART2->DR;
uart_data_ready = true; // beri sinyal ke main loop
}
}Karena ISR tidak bisa "dipause" oleh tugas biasa, semua pekerjaan berat dipindah ke main loop atau task RTOS. Pola "ISR mengisi, main memproses" adalah desain paling umum di firmware.
Tiga senjata untuk melindungi data yang dibagi antara ISR dan main:
volatile untuk Data ISRVariabel yang ditulis ISR dan dibaca main harus volatile — seperti kita pelajari di episode 3. Tanpa itu, kompiler bisa menyimpan nilai di register dan membaca versi basi.
Untuk operasi multi-langkah (misal baca 32-bit dari variabel 8-bit, atau update counter), matikan interrupt sebentar:
__disable_irq(); // matikan semua IRQ (PRIMASK)
// ... operasi yang tidak boleh disela ...
__enable_irq(); // nyalakan lagi__disable_irq/__enable_irq (intrinsik CMSIS) memanipulasi register PRIMASK. Waktu dalam critical section harus sangat singkat — semakin lama IRQ mati, semakin besar latensi yang dirasakan sistem real-time.
Baca/tulis 32-bit di Cortex-M3/M4 adalah atomik oleh hardware. Untuk flag sederhana (bool), ini sering cukup tanpa critical section — tetapi kalau main loop juga menulis, gunakan critical section atau atomik eksplisit.
Desain ISR untuk button dengan debounce sederhana (lakukan debounce di main, bukan di ISR):
volatile bool btn_pressed = false;
void EXTI0_IRQHandler(void) {
if (EXTI->PR & EXTI_PR_PR0) { // cek interrupt line 0
btn_pressed = true; // cukup tandai
EXTI->PR = EXTI_PR_PR0; // clear pending bit (WAJIB)
}
}
int main(void) {
// ... inisialisasi GPIO & EXTI ...
while (1) {
if (btn_pressed) {
btn_pressed = false; // konsumsi event
delay_ms(20); // debounce: tunggu goncangan mereda
// baca ulang level pin; jika masih aktif -> proses
}
// kerja lain berjalan bebas...
}
}Perhatikan dua detail kritis: selalu clear pending bit di ISR (kalau tidak, interrupt menyala terus), dan ISR hanya memberi sinyal — kerja berat di main loop.
Warning
Jangan pernah memanggil fungsi "berat" dari ISR: printf, malloc, mutex biasa, atau delay panjang. ISR bukan tempat kerja — ia tempat memberi tahu. Melanggar ini menyebabkan latensi sistem memburuk, deadlock, atau watchdog reset di lapangan.
volatile — flag berubah tapi main tidak melihatnya.Inti yang harus dibawa pulang:
volatile, critical section (__disable_irq), dan operasi atomik.Di episode 7 selanjutnya kita naik level ke RTOS Dasar (FreeRTOS) — mengubah firmware single-loop menjadi sistem multitasking dengan task, semaphore, queue, dan scheduler. Interrupt yang kalian kuasai sekarang akan menjadi otot di belakang semua task!