Interrupt Arduino: attachInterrupt, Aturan ISR, dan Debounce
Cara memakai interrupt Arduino: attachInterrupt, pin 2 dan 3 di Uno, RISING/FALLING/CHANGE, volatile, debounce, contoh encoder dan RPM, plus ESP32 dan Pico.
Interrupt Arduino membuat prosesor meninggalkan apa pun yang sedang dikerjakannya begitu perubahan yang kamu tunggu terjadi di sebuah pin (atau ketika event timer muncul), lalu menjalankan fungsi khusus yang kamu tulis. Singkatnya, alih-alih terus-menerus bertanya “tombolnya sudah ditekan belum?” di dalam loop(), kamu bilang ke Arduino “kabari aku kalau sudah ditekan”. Caranya dengan fungsi attachInterrupt(); di Arduino Uno, pin interrupt-nya adalah D2 dan D3, sedangkan di ESP32 dan Raspberry Pi Pico hampir semua GPIO bisa memicu interrupt.
Interrupt sangat penting untuk menghitung pulsa yang sangat cepat tanpa ada yang terlewat (encoder, sensor kecepatan, sensor aliran air), menangkap event singkat di tengah tugas yang panjang, dan membangunkan board dari sleep. Di panduan ini kamu akan melihat langkah demi langkah cara kerja interrupt di hardware, pin mana yang bisa dipakai di tiap board, perbedaan mode RISING, FALLING, dan CHANGE, aturan menulis interrupt service routine (ISR), cara men-debounce tombol, serta contoh untuk tombol, encoder, dan penghitung RPM. Di akhir, kamu juga akan belajar tentang IRAM_ATTR di ESP32 dan cara memakai irq dengan MicroPython di Pico.

Apa itu interrupt dan bagaimana cara kerjanya?
Bayangkan bel pintu: tanpa bel, kamu harus berjalan ke pintu setiap menit untuk mengecek apakah ada orang yang datang. Itu disebut polling, dan membaca pin dengan digitalRead() di dalam loop() persis seperti itu. Bel pintu adalah interrupt: bel berbunyi ketika kamu sedang sibuk dengan hal lain, kamu berhenti, mengecek pintu, lalu kembali ke pekerjaanmu. Mikrokontroler bekerja dengan cara yang sama. Ketika perubahan yang kamu pilih terjadi di pin, hardware menyalakan sebuah flag interrupt; prosesor menyelesaikan instruksi yang sedang berjalan, menyimpan register-nya, melompat lewat vektor interrupt ke ISR (Interrupt Service Routine) milikmu, dan begitu ISR selesai, melanjutkan loop() tepat di tempat ia berhenti. Di Uno, perpindahan ini hanya butuh beberapa mikrodetik.
| Fitur | Polling | Interrupt |
|---|---|---|
| Cara kerja | loop() membaca pin berulang-ulang | Hardware menangkap perubahan dan menjalankan ISR |
| Waktu respons | Makin lambat loop(), makin besar jedanya | Beberapa mikrodetik |
| Jika ada delay() di kode | Event bisa terlewat | Event tetap tertangkap |
| Pulsa yang sangat cepat | Terlewat | Dihitung satu per satu |
| Kerumitan kode | Sederhana | Butuh volatile dan pembacaan atomik |
| Paling cocok untuk | Tombol, sensor yang lambat | Encoder, sensor kecepatan dan aliran, bangun dari sleep |
Pin mana yang mendukung interrupt? Arduino, ESP32, dan Pico
| Board | Pin interrupt eksternal | Catatan |
|---|---|---|
| Arduino Uno R3, Nano, Pro Mini | D2, D3 | D2 = INT0, D3 = INT1 |
| Arduino Uno R4 (Minima / WiFi) | D2, D3 | Pin yang tercantum di referensi resmi Arduino |
| Arduino Mega 2560 | 2, 3, 18, 19, 20, 21 | 20 dan 21 tidak bisa dipakai untuk interrupt selama dipakai untuk I2C |
| Arduino Leonardo, Micro | 0, 1, 2, 3, 7 | 0 dan 1 juga merupakan pin port serial |
| Arduino Nano Every | Semua pin digital | Prosesor ATmega4809 |
| ESP32 | Semua GPIO | GPIO 34–39 hanya input dan tidak punya pull-up internal |
| ESP8266 (NodeMCU) | Semua kecuali GPIO 16 (D0) | ISR wajib ditandai IRAM_ATTR |
| Raspberry Pi Pico / Pico W | Semua GP0–GP28 | Pin bekerja di 3,3 V, jangan pernah beri 5 V |
Di kodemu, jangan berikan nomor pin secara langsung; ubah dulu menjadi nomor interrupt dengan digitalPinToInterrupt(pin), karena nomor interrupt berbeda-beda di tiap board: di Uno D2 adalah interrupt 0 dan D3 adalah interrupt 1, sedangkan di Leonardo interrupt 0 adalah D3 dan interrupt 1 adalah D2. Kalau kamu butuh lebih dari dua interrupt di Uno, pin change interrupt (PCINT) bekerja di hampir semua pin; tapi interrupt ini hanya memberi tahu bahwa “ada yang berubah”, dan semua pin di port yang sama berbagi satu ISR. Library PinChangeInterrupt dan EnableInterrupt membuatnya lebih mudah dipakai. Peta pin lengkap board-mu ada di alat Pinout, dan fungsi pin Uno ada di artikel Pinout Arduino Uno.
Memakai attachInterrupt dan mode interrupt
Sintaksnya adalah attachInterrupt(digitalPinToInterrupt(pin), ISR, mode). Parameter kedua adalah nama fungsi yang dijalankan ketika interrupt terpicu; fungsi ini tidak menerima parameter dan tidak mengembalikan apa pun (void). Parameter ketiga menentukan event apa yang memicu interrupt. Pakai detachInterrupt(digitalPinToInterrupt(pin)) untuk mematikan interrupt, noInterrupts() untuk menjeda semua interrupt sebentar, dan interrupts() untuk menyalakannya kembali.
| Mode | Kapan terpicu? | Penggunaan umum |
|---|---|---|
| LOW | Berulang-ulang selama pin bernilai LOW | Membangunkan dari sleep |
| CHANGE | Setiap ada perubahan (kedua tepi) | Encoder, mengukur panjang pulsa, debounce |
| RISING | Ketika berubah dari LOW ke HIGH (tepi naik) | Awal pulsa sensor, tombol dengan pull-down |
| FALLING | Ketika berubah dari HIGH ke LOW (tepi turun) | Menekan tombol dengan pull-up, sensor open-collector |
| HIGH | Selama pin bernilai HIGH | Hanya di Due, Zero, dan MKR1000; ONHIGH di ESP32 |
Pilih mode yang sesuai dengan rangkaianmu. Kalau tombolmu dipasang dengan INPUT_PULLUP, pin normalnya HIGH dan turun ke LOW ketika ditekan, jadi FALLING yang menangkap tekanannya. Pakai mode LOW dengan hati-hati: selama pin tetap LOW, ISR berjalan lagi dan lagi sehingga loop() nyaris tidak bergerak. Karena itu mode ini biasanya hanya dipakai untuk membangunkan board dari sleep; untuk membangunkan Uno dari mode sleep terdalamnya (power-down), datasheet menyarankan interrupt level LOW atau pin change interrupt. Di ESP32, konstanta LOW dan HIGH tidak bekerja sebagai mode interrupt seperti yang kamu kira: LOW sama sekali tidak mengaktifkan interrupt, dan HIGH berperilaku seperti tepi naik. Untuk interrupt level di sana, kamu menulis ONLOW dan ONHIGH.
Contoh pertama: menghitung tekanan tombol dengan interrupt
- Hubungkan satu kaki tombol ke D2 dan kaki lainnya ke GND. Resistor eksternal tidak diperlukan; kita akan memakai pull-up internal.
- Upload kode di bawah dan buka Serial Monitor di 9600 baud.
- Tekan tombol beberapa kali dan lihat penghitungnya bertambah. Meski ada delay(500) di dalam loop(), tidak ada satu pun tekanan yang terlewat.
- Jangan kaget kalau angkanya kadang langsung melompat 2 atau 3: itu pantulan kontak (contact bounce), dan sebentar lagi kita akan memperbaikinya.
1const byte BUTTON_PIN = 2; // kesme pini D2 (INT0) / interrupt pin D2 (INT0)
2volatile unsigned int pressCount = 0; // ISR ile paylaşılan sayaç / counter shared with the ISR
3
4void onPress() { // ISR: kısa ve hızlı / ISR: short and fast
5 pressCount++;
6}
7
8void setup() {
9 Serial.begin(9600);
10 pinMode(BUTTON_PIN, INPUT_PULLUP); // dahili pull-up, buton GND'ye / internal pull-up, button to GND
11 attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onPress, FALLING);
12}
13
14void loop() {
15 noInterrupts(); // 2 baytlık değeri güvenle kopyala / copy the 2-byte value safely
16 unsigned int count = pressCount;
17 interrupts();
18
19 Serial.print(F("Basis / Presses: "));
20 Serial.println(count);
21 delay(500); // loop yavaş olsa da basış kaçmaz / presses are not missed even if loop is slow
22}Ada tiga poin penting di kode ini. Pertama, onPress() hanya menambahkan satu ke penghitung lalu langsung selesai; tugas ISR adalah mencatat event, bukan mengerjakan pekerjaannya. Kedua, penghitung dideklarasikan sebagai volatile, sehingga compiler tidak menyimpannya di register lalu membaca nilai yang sudah basi, tetapi mengambil nilai terbaru dari memori setiap kali. Ketiga, loop() mematikan interrupt sebentar selama membaca penghitung. unsigned int berukuran 2 byte di Uno, dan prosesor 8-bit membacanya dalam dua langkah. Ketika penghitung berubah dari 255 ke 256, yaitu dari 0x00FF ke 0x0100, loop() lebih dulu membaca byte rendah (0xFF); kalau interrupt terpicu tepat pada momen itu dan menaikkan penghitung, loop() lalu membaca byte tinggi (0x01) dan hasilnya 0x01FF, atau 511. Karena bug ini sangat jarang muncul, bug ini juga sulit dilacak. Di board AVR, makro ATOMIC_BLOCK(ATOMIC_RESTORESTATE) dari util/atomic.h memberi perlindungan yang sama; variabel satu byte seperti bool dan byte tidak punya masalah ini.
Aturan ISR: buat singkat, pakai volatile, tanpa delay
- Buat singkat dan cepat: selama ISR berjalan, interrupt lain menunggu. Di Uno, penghitung millis() dan port serial juga bergantung pada interrupt, jadi ISR yang panjang mengacaukan timing dan bisa membuatmu kehilangan data serial yang masuk. ISR yang ideal selesai dalam beberapa mikrodetik.
- Jangan pakai delay(): delay() mengukur waktu dengan penghitung yang diperbarui oleh interrupt Timer0; di dalam ISR interrupt sedang mati, jadi delay() tidak bekerja. Kamu bisa membaca millis(), tapi nilainya tidak bertambah selama ISR, dan micros() mulai mengembalikan nilai yang salah setelah 1–2 ms. Kalau kamu benar-benar butuh jeda yang sangat singkat, delayMicroseconds() bisa dipakai.
- Jangan pakai Serial.print(): mengirim data serial juga bergantung pada interrupt; ini membuat ISR lebih panjang dan akan macet kalau buffer-nya penuh. Serahkan urusan mencetak ke loop().
- Jadikan variabel bersama volatile: setiap variabel yang dipakai di ISR sekaligus di loop() wajib dideklarasikan volatile.
- Baca variabel multi-byte secara atomik: di loop(), salin variabel int, long, float, dan unsigned long di antara noInterrupts() dan interrupts().
- Tanpa parameter, tanpa nilai kembalian: ISR harus void dan tidak menerima parameter; pertukaran data dilakukan lewat variabel global.
- Jauhkan pekerjaan berat dari ISR: menulis ke layar I2C, menyimpan ke kartu SD, atau memanggil library yang sendirinya butuh interrupt, seperti Wire, akan membuat program macet di dalam ISR.
Membaca tombol dengan interrupt dan men-debounce-nya
Ketika kamu menekan tombol mekanis, kontak logamnya tidak langsung diam; kontak itu membuka dan menutup beberapa kali selama kira-kira 0,1 sampai 10 milidetik. loop() mungkin terlalu lambat untuk menyadarinya, tetapi interrupt menangkap setiap tepi, sehingga satu tekanan bisa menghasilkan 3–5 interrupt. Lebih dari itu, pantulan terjadi bukan hanya ketika kamu menekan tetapi juga ketika kamu melepas, jadi kode yang hanya memakai FALLING dengan kunci waktu bisa menghitung dua kali ketika kamu menahan tombol agak lama lalu melepasnya. Kode di bawah memakai mode CHANGE dan hanya menerima tekanan ketika pin bernilai LOW dan jalurnya sudah tenang setidaknya 50 ms sebelumnya. Cara ini menyaring pantulan, baik ketika ditekan maupun ketika dilepas.
1const byte BUTTON_PIN = 2; // kesme pini / interrupt pin
2const byte LED_PIN = 13; // dahili LED / onboard LED
3const unsigned long QUIET_MS = 50; // gereken sessizlik süresi / required quiet time
4
5volatile bool ledState = false; // ISR ile paylaşılır / shared with the ISR
6
7void onButtonChange() { // iki kenarda da çalışır / runs on both edges
8 static unsigned long lastEdgeMs = 0;
9 unsigned long now = millis(); // ISR'de okunur ama artmaz / readable in an ISR but does not advance
10 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
11
12 if (pressed && now - lastEdgeMs > QUIET_MS) { // hat sessizdi: gerçek basış / line was quiet: real press
13 ledState = !ledState;
14 }
15 lastEdgeMs = now; // her kenar süreyi yeniden başlatır / every edge restarts the timer
16}
17
18void setup() {
19 pinMode(LED_PIN, OUTPUT);
20 pinMode(BUTTON_PIN, INPUT_PULLUP);
21 attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButtonChange, CHANGE);
22}
23
24void loop() {
25 digitalWrite(LED_PIN, ledState ? HIGH : LOW); // bool tek bayt, okuması atomik / bool is one byte, atomic read
26 // burada uzun işler olsa da basış kaçmaz / long tasks here will not make you miss a press
27}Selain lewat software, kamu juga bisa meredam pantulan dengan hardware: pasang kapasitor 100 nF di antara kedua kaki tombol dan resistor pull-up 10 kΩ di pin, maka konstanta waktunya menjadi τ = R × C = 10 kΩ × 100 nF = 1 ms, yang menghaluskan pantulan singkat; Schmitt trigger di input Uno juga ikut membersihkan tepinya. Kamu bisa menghitung nilai lain dengan alat konstanta waktu RC. Jujur saja, untuk tombol yang ditekan dengan tangan, kamu sering kali sama sekali tidak butuh interrupt; kalau loop() cepat, polling sudah cukup. Untuk pull-up, pull-down, dan debounce dengan millis(), lihat Cara Menghubungkan Push Button ke Arduino. Simpan interrupt untuk sinyal yang cepat dan singkat.
Membaca rotary encoder dengan interrupt
Ketika diputar, rotary encoder (seperti KY-040) menghasilkan gelombang kotak di dua pin bernama CLK dan DT yang saling bergeser. Kalau kamu memutarnya dengan cepat, pulsanya menjadi lebih pendek dari satu milidetik dan polling gampang melewatkan langkah. Metode paling sederhana adalah menghubungkan CLK ke pin interrupt dan mengecek level DT tepat ketika CLK turun: kalau DT HIGH, encoder berputar ke satu arah, kalau LOW ke arah sebaliknya. Versi ini menghitung satu langkah per klik; kalau arahnya terbalik, tukar baris ++ dan --. Encoder murah juga memantul, jadi menambahkan kapasitor 10–100 nF di antara CLK dan GND serta di antara DT dan GND memberi perbedaan yang jelas. Untuk versi yang memasang kedua pin ke interrupt dan memakai tabel state agar tidak pernah kehilangan langkah, lihat panduan rotary encoder (KY-040).
1const byte PIN_CLK = 2; // kesme pini / interrupt pin
2const byte PIN_DT = 4; // normal dijital giriş / regular digital input
3
4volatile long position = 0; // 4 baytlık konum / 4-byte position
5
6void onClkFall() {
7 if (digitalRead(PIN_DT) == HIGH) { // yön DT seviyesinden / direction from the DT level
8 position++;
9 } else {
10 position--;
11 }
12}
13
14void setup() {
15 Serial.begin(115200);
16 pinMode(PIN_CLK, INPUT_PULLUP);
17 pinMode(PIN_DT, INPUT_PULLUP);
18 attachInterrupt(digitalPinToInterrupt(PIN_CLK), onClkFall, FALLING);
19}
20
21void loop() {
22 static long lastPos = 0;
23
24 noInterrupts(); // atomik kopya / atomic copy
25 long pos = position;
26 interrupts();
27
28 if (pos != lastPos) {
29 Serial.println(pos);
30 lastPos = pos;
31 }
32}Menghitung putaran: mengukur RPM motor
Mengukur kecepatan motor atau roda adalah penggunaan klasik interrupt. Sebagai sensornya, kamu bisa memakai sensor kecepatan optik bercelah dengan LM393 dan piringan encoder 20 celah, atau sensor Hall seperti A3144 dengan magnet yang ditempel di poros. Setiap celah atau setiap kali magnet lewat menghasilkan satu pulsa. Dari jumlah pulsa yang dihitung dalam satu detik, kecepatannya adalah: RPM = pulsa per detik × 60 / pulsa per putaran. Misalnya, kalau piringan 20 celah memberi 100 pulsa per detik, motor berputar pada 100 × 60 / 20 = 300 RPM.
1const byte SENSOR_PIN = 3; // kesme pini D3 (INT1) / interrupt pin D3 (INT1)
2const byte PULSES_PER_REV = 20; // diskteki yarık sayısı / slots on the encoder disc
3
4volatile unsigned long pulseCount = 0;
5
6void onPulse() {
7 pulseCount++;
8}
9
10void setup() {
11 Serial.begin(9600);
12 pinMode(SENSOR_PIN, INPUT_PULLUP);
13 attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), onPulse, FALLING);
14}
15
16void loop() {
17 static unsigned long lastMs = 0;
18
19 if (millis() - lastMs >= 1000) { // her saniye hesapla / compute once per second
20 lastMs += 1000;
21
22 noInterrupts(); // kopyala ve sıfırla / copy and reset
23 unsigned long pulses = pulseCount;
24 pulseCount = 0;
25 interrupts();
26
27 float rpm = pulses * 60.0 / PULSES_PER_REV;
28 Serial.print(F("RPM: "));
29 Serial.println(rpm, 1);
30 }
31}Di sini loop() menyalin dan me-reset penghitung setiap detik; keduanya terjadi di dalam blok noInterrupts() yang sama supaya tidak ada pulsa yang hilang kalau interrupt datang di antara keduanya. Dengan piringan 20 celah, resolusinya 3 RPM. Kalau kamu ingin hasil yang lebih presisi di kecepatan rendah, alih-alih menghitung pulsa, ukur waktu di antara dua pulsa di dalam ISR dengan micros() dan pakai RPM = 60.000.000 / (periode_µs × pulsa per putaran). Metode yang sama berlaku untuk sensor aliran air YF-S201, pengukuran kecepatan dengan sensor Hall, dan kecepatan roda motor DC bergigi.
Catatan singkat tentang interrupt timer
Interrupt tidak hanya datang dari pin. Timer di dalam mikrokontroler juga bisa menghasilkan interrupt pada interval tetap, sehingga sebuah fungsi berjalan tepat setiap 1 ms atau setiap 500 ms, apa pun yang sedang dikerjakan loop(). Uno punya tiga timer, dan sebagian di antaranya sudah dipakai oleh fungsi bawaan Arduino:
| Timer | Ukuran | Dipakai oleh | Pin PWM |
|---|---|---|---|
| Timer0 | 8-bit | millis(), delay(), micros() (fungsi waktu) | 5, 6 |
| Timer1 | 16-bit | Library Servo, TimerOne | 9, 10 |
| Timer2 | 8-bit | tone(), IRremote | 3, 11 |
Di Uno, cara termudah adalah library TimerOne: Timer1.initialize(500000) mengatur periode dalam mikrodetik dan Timer1.attachInterrupt(fungsi) memasang ISR yang berjalan setiap 500 ms. Karena memakai Timer1, library ini bentrok dengan library Servo dan dengan analogWrite() di pin 9 dan 10. Di ESP32 dengan Arduino core 3.x, interrupt timer disiapkan dengan timerBegin(1000000), timerAttachInterrupt(), dan timerAlarm(); timerAlarmWrite() dan timerAlarmEnable() dari versi 2.x sudah dihapus, jadi contoh-contoh lama gagal dengan error “was not declared in this scope”. Di Pico, Timer(period=500, mode=Timer.PERIODIC, callback=fungsi) dari MicroPython menyiapkan interrupt periodik dalam satu baris. Untuk hubungan timer dengan PWM, lihat Apa Itu PWM?
Interrupt di ESP32: IRAM_ATTR dan portMUX
Di ESP32 kamu tidak perlu mencari pin khusus; setiap GPIO bisa memicu interrupt. Hanya GPIO 34–39 yang tidak punya pull-up internal, jadi tombol di sana butuh pull-up eksternal 10 kΩ; errata Espressif juga mencatat bahwa GPIO 36 dan 39 bisa menghasilkan interrupt palsu selama ADC dipakai. Perbedaan sebenarnya di ESP32 adalah atribut IRAM_ATTR yang ditulis di depan ISR. Atribut ini menempatkan fungsi di RAM internal yang cepat (IRAM), bukan di flash. Selama menulis ke flash (Preferences, SPIFFS, OTA), cache dimatikan; tergantung konfigurasinya, interrupt yang menyentuh kode di flash pada momen itu bisa me-reset board dengan “Guru Meditation Error: Core 1 panic'ed (Cache disabled but cached memory region accessed)”. ESP8266 bahkan lebih ketat: ISR tanpa IRAM_ATTR menghentikan board dengan pesan “ISR not in IRAM!” begitu attachInterrupt dipanggil.
1const int BUTTON_PIN = 27; // her GPIO kesme verebilir / any GPIO can interrupt
2const int LED_PIN = 2; // çoğu DevKit'te dahili LED / onboard LED on most DevKits
3const uint32_t QUIET_MS = 50; // gereken sessizlik süresi / required quiet time
4
5volatile uint32_t pressCount = 0;
6portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // paylaşılan veri kilidi / lock for shared data
7
8void IRAM_ATTR onButtonChange() { // ISR IRAM'de çalışır / ISR runs from IRAM
9 static uint32_t lastEdgeMs = 0;
10 uint32_t now = millis();
11 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
12
13 if (pressed && now - lastEdgeMs > QUIET_MS) {
14 portENTER_CRITICAL_ISR(&mux);
15 pressCount++;
16 portEXIT_CRITICAL_ISR(&mux);
17 }
18 lastEdgeMs = now;
19}
20
21void setup() {
22 Serial.begin(115200);
23 pinMode(LED_PIN, OUTPUT);
24 pinMode(BUTTON_PIN, INPUT_PULLUP);
25 attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButtonChange, CHANGE);
26}
27
28void loop() {
29 static uint32_t lastCount = 0;
30
31 portENTER_CRITICAL(&mux); // loop tarafında kilit / lock on the loop side
32 uint32_t count = pressCount;
33 portEXIT_CRITICAL(&mux);
34
35 if (count != lastCount) {
36 lastCount = count;
37 digitalWrite(LED_PIN, (count % 2) ? HIGH : LOW); // her basışta LED değişir / LED toggles on every press
38 Serial.printf("Basis / Press: %lu\n", (unsigned long)count);
39 }
40}Karena ESP32 menjalankan FreeRTOS dan punya dua inti, variabel bersama dilindungi dengan kunci portMUX_TYPE, bukan dengan noInterrupts() seperti di Uno: pakai portENTER_CRITICAL_ISR() di dalam ISR dan portENTER_CRITICAL() di loop(). Membaca variabel 32-bit di ESP32 memang sudah satu langkah, tetapi kunci ini wajib untuk hal-hal seperti membaca lalu me-reset sebuah nilai atau memperbarui beberapa variabel sekaligus. Kalau kamu ingin memasang fungsi yang sama ke beberapa pin, attachInterruptArg() memungkinkanmu mengirim parameter, misalnya nomor pin, ke ISR. Untuk dasar-dasar ESP32, lihat Apa Itu ESP32?, dan untuk task dan inti, lihat FreeRTOS dan dua inti ESP32.
Interrupt di Raspberry Pi Pico: irq MicroPython
Di Raspberry Pi Pico, setiap pin dari GP0 sampai GP28 bisa memicu interrupt. Di MicroPython, kamu menyiapkannya dengan metode irq() milik objek pin: untuk trigger kamu berikan Pin.IRQ_FALLING, Pin.IRQ_RISING, atau gabungan keduanya, dan handler adalah fungsi yang berjalan ketika interrupt terpicu; fungsi ini menerima pin pemicunya sebagai parameter. Python tidak punya volatile; cukup deklarasikan variabel yang kamu ubah di ISR sebagai global. Untuk mengukur waktu, pakai time.ticks_ms() dan time.ticks_diff() yang aman terhadap overflow. Kode di bawah membawa logika waktu tenang dari contoh Arduino ke Pico:
1from machine import Pin
2import time
3
4led = Pin("LED", Pin.OUT) # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
5button = Pin(14, Pin.IN, Pin.PULL_UP) # buton GP14 ile GND arasında / button between GP14 and GND
6
7QUIET_MS = 50 # gereken sessizlik süresi / required quiet time
8presses = 0
9last_edge = time.ticks_ms()
10
11def on_change(pin): # handler tetikleyen pini alır / handler receives the pin
12 global presses, last_edge
13 now = time.ticks_ms()
14 if pin.value() == 0 and time.ticks_diff(now, last_edge) > QUIET_MS:
15 presses += 1
16 led.toggle()
17 last_edge = now
18
19button.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=on_change)
20
21while True:
22 print("Basis / Presses:", presses) # ana döngü uyurken bile basışlar sayılır / presses are counted while the loop sleeps
23 time.sleep(1)Di Pico, irq secara default berjalan sebagai interrupt soft: MicroPython mengantrekan fungsinya dan menjalankannya secepat mungkin, jadi memakai print atau membuat list di dalamnya tidak menimbulkan masalah, tetapi latensinya sedikit naik. Kalau kamu memberikan hard=True, latensi turun ke hitungan mikrodetik; sebagai gantinya, kamu tidak bisa mengalokasikan memori di dalam ISR, jadi list baru, string, bahkan perhitungan float bisa memunculkan “MemoryError”. Kalau kamu memprogram Pico dari Arduino IDE (core Earle Philhower), attachInterrupt() bekerja sama persis di setiap GPIO. Untuk instalasi, lihat Apa Itu Raspberry Pi Pico?, dan untuk perintah dasarnya, lihat dasar-dasar MicroPython.
Ide proyek yang memakai interrupt
- Pengukur waktu reaksi: ukur berapa lama kamu menekan tombol setelah LED menyala, dengan micros() di dalam ISR untuk akurasi di bawah satu milidetik.
- Speedometer sepeda: pasang magnet di roda dan sensor Hall di rangka; hitung kecepatan dan jarak dari waktu per putaran.
- Meteran air: hitung pulsa sensor aliran YF-S201 dan tampilkan berapa liter air yang terpakai.
- Mobil robot yang berjalan lurus: ukur kecepatan kedua motor dengan encoder roda lalu samakan.
- Kontrol menu: jelajahi menu di layar dan ubah pengaturan dengan rotary encoder.
- Alarm bertenaga baterai: biarkan output sensor PIR membangunkan board selagi board sedang sleep; untuk ESP32, lihat artikel deep sleep.
Kesalahan umum dan cara memperbaikinya
| Gejala | Penyebab | Solusi |
|---|---|---|
| Interrupt tidak pernah terpicu | attachInterrupt(2, ...) diberi nomor pin, atau dipilih pin yang tidak mendukung interrupt | Pakai digitalPinToInterrupt(pin); di Uno hubungkan ke D2 atau D3 |
| Satu tekanan menaikkan penghitung 2–5 | Pantulan kontak | Debounce lewat software dan/atau tambahkan kapasitor 100 nF |
| Variabel tidak pernah berubah di loop() | Lupa memakai volatile | Deklarasikan variabel bersama sebagai volatile |
| Sesekali muncul nilai yang besarnya tidak masuk akal | Variabel multi-byte tidak dibaca secara atomik | Salin di antara noInterrupts() dan interrupts() |
| Program macet atau millis() bergeser | Ada delay(), Serial, atau tugas panjang di dalam ISR | Di ISR, cukup perbarui flag atau penghitung |
| Interrupt terpicu tanpa tombol disentuh | Pin mengambang (floating), tanpa pull-up | Pakai INPUT_PULLUP atau resistor eksternal 10 kΩ |
| ISR langsung berjalan sekali setelah attachInterrupt dipasang ulang | Ada tepi yang datang ketika interrupt mati dan menyalakan flag (AVR) | Di Uno, tulis EIFR = bit(INTF0); sebelum attachInterrupt (INTF1 untuk D3) |
| ESP32 me-reset secara acak | ISR tidak berada di IRAM atau mengerjakan tugas berat | Tambahkan IRAM_ATTR dan persingkat ISR |
Interrupt adalah alat ampuh yang membuatmu bisa menangkap setiap event bahkan ketika loop() sedang sibuk dengan pekerjaan lain; yang perlu kamu ingat hanya tiga aturan: buat ISR tetap singkat, jadikan variabel bersama volatile, dan baca variabel itu secara atomik di loop(). Di aplikasi BlueGrays kamu akan menemukan penghitung tombol, tes reaksi, proyek tombol ESP32, dan proyek Pico yang membandingkan debounce dengan interrupt (IRQ) dan dengan polling software, masing-masing lengkap dengan skema rangkaian dan kode langkah demi langkah.
Pertanyaan umum
Pin mana yang mendukung interrupt di Arduino Uno?
Arduino Uno R3, Nano, dan Pro Mini hanya punya dua pin interrupt eksternal: D2 (INT0) dan D3 (INT1). Di Mega 2560 kamu bisa memakai 2, 3, 18, 19, 20, dan 21, dan di Leonardo 0, 1, 2, 3, dan 7. Di pin lain, kamu hanya bisa memakai pin change interrupt (PCINT), yang sekadar memberi tahu bahwa sebuah pin berubah.
Kenapa delay() tidak bekerja di dalam interrupt (ISR)?
delay() mengukur waktu dengan penghitung yang diperbarui oleh interrupt Timer0. Selama ISR berjalan, interrupt lain menunggu, jadi penghitung itu tidak bertambah dan delay() tidak bisa bekerja dengan benar. Kalau ada sesuatu yang perlu menunggu, cukup pasang flag di ISR dan lakukan penantiannya di loop() dengan millis().
Apa fungsi volatile dan kapan aku harus memakainya?
volatile memberi tahu compiler bahwa variabel itu bisa berubah kapan saja di luar alur program normal, misalnya di dalam ISR, sehingga nilainya tidak disimpan di register tetapi dibaca dari memori setiap kali. Setiap variabel yang dipakai di ISR sekaligus di loop() sebaiknya volatile. Namun, volatile tidak membuat pembacaan multi-byte menjadi atomik; untuk itu kamu memakai noInterrupts().
Bisakah aku memakai lebih dari 2 interrupt di Arduino Uno?
Bisa. Berkat pin change interrupt (PCINT), hampir semua pin di Uno bisa memicu interrupt. Interrupt ini hanya melaporkan perubahan, dan pin-pin di port yang sama (D0–D7, D8–D13, A0–A5) berbagi satu ISR. Dengan library PinChangeInterrupt atau EnableInterrupt, cara memakainya terasa sangat mirip dengan attachInterrupt.
Sebaiknya aku pakai interrupt atau polling?
Untuk tombol yang ditekan dengan tangan dan sensor yang berubah pelan, polling di loop() biasanya sudah cukup dan lebih sederhana. Pakai interrupt untuk pulsa yang cepat dan singkat seperti encoder, sensor kecepatan dan aliran, atau untuk membangunkan board dari sleep. Kalau loop() penuh dengan panggilan delay() yang panjang, menggantinya dengan millis() juga menyelesaikan sebagian besar masalah.
Apa yang terjadi kalau aku tidak menulis IRAM_ATTR di ESP32?
Kodenya biasanya tetap terkompilasi dan tampak berjalan, tetapi ISR tetap berada di flash, mulai lebih lambat, dan tergantung konfigurasinya bisa crash dengan “Cache disabled but cached memory region accessed” selama flash sedang ditulis. Di ESP8266, tanpa IRAM_ATTR board langsung berhenti dengan “ISR not in IRAM!”. Jadi di board ESP, taruh IRAM_ATTR di depan setiap ISR.
Proyek untuk dibuat setelah membaca ini
Lihat semua proyek
Nyala/Mati LED dengan Tombol (On/Off)
Tekan tombol sekali dan lampu menyala, tekan lagi dan lampu mati, persis seperti sakelar lampu di kamarmu. Arduino menyadari setiap tekanan dan membalik LED ke keadaan sebaliknya, sambil menunggu sebe

Debounce Tombol Raspberry Pi Pico: IRQ vs Polling Perangkat Lunak
Tekan tombol sekali, kode menghitung 3-4? Itu pantulan kontak: kontak logam bergetar beberapa mikrodetik! Bandingkan 2 metode di Serial: polling MENTAH (ikut menghitung pantulan) vs IRQ + debounce perangkat lunak (akurat). Pelajari pantulan kontak, debounce perangkat lunak (ticks_diff() > 50ms), interrupt Pin.irq(), pemicu IRQ_FALLING, handler ISR, serta global + keamanan ISR. LED berbalik lewat IRQ, dan Serial menampilkan kedua penghitung.

Kendali LED dengan Tombol Tekan ESP32 (Toggle)
Setiap kali tombol ditekan, keadaan LED berubah: jika menyala jadi mati, jika mati jadi menyala. Pola toggle klasik. Dengan debounce, kita membersihkan derau mekanis tombol tekan agar satu tekanan menghasilkan satu kali toggle.

LED Toggle dengan Tombol Raspberry Pi Pico
Tombol + LED, level berikutnya! Berbeda dengan proyek tombol dasar, tombol tidak perlu ditahan. Tekan dan lepas → LED menyala. Tekan dan lepas lagi → LED mati (toggle). Rangkaiannya sama (LED GP14, Tombol GP2), tetapi kodenya sangat berbeda. Pelajari deteksi tepi (menangkap momen turun 1→0), pelacakan status, dan debounce (penyaring pantulan mekanis). led.toggle() MicroPython menukar status dalam satu baris. Dasar semua proyek tombol interaktif (pengganti mode, IRQ, klik ganda).

Penghitung dengan Layar 7 Segmen dan Tombol
Penghitung sederhana dengan tiga tombol: tekan satu dan angka di layar naik satu, tekan yang lain dan turun satu, dan yang ketiga mengembalikannya ke nol. Layar 7 segmen satu digit menghitung dari 0 s

Tes Waktu Reaksi Arduino dengan Isyarat Suara: Ukur Refleksmu dalam Milidetik (LCD + Buzzer + 3 LED)
Seberapa cepat refleksmu? Tekan tombol: LED merah berkata bersiap, LED kuning berkata siap, dan jeda acak 1,5 sampai 3,5 detik dimulai. Begitu LED hijau menyala dan buzzer berbunyi bip, tekan lagi; waktu reaksimu muncul di LCD I2C dalam milidetik, dengan rekor terbaik dan rata-rata sesimu di bawahnya. Tekan sebelum hijau dan layar menampilkan TOO EARLY! dengan suara kalah. Tahan tombol untuk pemain baru, agar semua orang beradu dengan urutan jeda yang sama.

Penghitung WebSocket ESP32 (2 Tombol + LCD + Web Real-Time)
Di proyek ini, dua tombol yang tersambung ke ESP32 (GPIO 5 menghitung naik, GPIO 4 menghitung turun) mengubah sebuah penghitung. Nilainya langsung diperbarui di LCD I2C 16x2 sekaligus, lewat dukungan WebSocket ESPAsyncWebServer, di setiap browser yang tersambung tanpa memuat ulang halaman. Alih-alih polling HTTP, dipakai satu koneksi yang tetap terbuka, dan ESP32 mengirim nilai baru ke semua orang dengan ws.textAll().

Game Memori Arduino (4 LED + 4 Tombol + Buzzer)
Percaya dengan ingatanmu? Empat LED berwarna menyala berurutan, masing-masing memainkan nadanya sendiri, dan kamu mengulang urutan yang sama dengan menekan tombol. Setiap ronde yang benar menambahkan
Artikel lainnya
Aplikasi BlueGrays
Rakit rangkaian ini langkah demi langkah di ponselmu
Skema pengkabelan, kode, dan daftar komponen dalam satu layar. 200+ proyek Arduino, ESP32, dan Pico siap dibuat.
Pindai dengan ponselmu
Arahkan kamera ke kode ini dan toko aplikasi di ponselmu akan terbuka.
atau buka toko aplikasi

