Dasar · 12 mnt baca

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.

Pin interrupt Arduino Uno D2 (INT0) dan D3 (INT1)
Arduino Uno hanya punya dua pin interrupt eksternal: D2 (INT0) dan D3 (INT1).

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.

FiturPollingInterrupt
Cara kerjaloop() membaca pin berulang-ulangHardware menangkap perubahan dan menjalankan ISR
Waktu responsMakin lambat loop(), makin besar jedanyaBeberapa mikrodetik
Jika ada delay() di kodeEvent bisa terlewatEvent tetap tertangkap
Pulsa yang sangat cepatTerlewatDihitung satu per satu
Kerumitan kodeSederhanaButuh volatile dan pembacaan atomik
Paling cocok untukTombol, sensor yang lambatEncoder, sensor kecepatan dan aliran, bangun dari sleep

Pin mana yang mendukung interrupt? Arduino, ESP32, dan Pico

BoardPin interrupt eksternalCatatan
Arduino Uno R3, Nano, Pro MiniD2, D3D2 = INT0, D3 = INT1
Arduino Uno R4 (Minima / WiFi)D2, D3Pin yang tercantum di referensi resmi Arduino
Arduino Mega 25602, 3, 18, 19, 20, 2120 dan 21 tidak bisa dipakai untuk interrupt selama dipakai untuk I2C
Arduino Leonardo, Micro0, 1, 2, 3, 70 dan 1 juga merupakan pin port serial
Arduino Nano EverySemua pin digitalProsesor ATmega4809
ESP32Semua GPIOGPIO 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 WSemua GP0–GP28Pin 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.

Kesalahan yang paling sering terjadi adalah menulis attachInterrupt(2, fungsi, FALLING). Di sini angka 2 dibaca sebagai nomor interrupt, bukan nomor pin; Uno tidak punya interrupt 2, jadi tidak terjadi apa-apa dan kamu juga tidak mendapat pesan error. Bentuk yang benar adalah attachInterrupt(digitalPinToInterrupt(2), fungsi, FALLING).

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.

ModeKapan terpicu?Penggunaan umum
LOWBerulang-ulang selama pin bernilai LOWMembangunkan dari sleep
CHANGESetiap ada perubahan (kedua tepi)Encoder, mengukur panjang pulsa, debounce
RISINGKetika berubah dari LOW ke HIGH (tepi naik)Awal pulsa sensor, tombol dengan pull-down
FALLINGKetika berubah dari HIGH ke LOW (tepi turun)Menekan tombol dengan pull-up, sensor open-collector
HIGHSelama pin bernilai HIGHHanya 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

  1. Hubungkan satu kaki tombol ke D2 dan kaki lainnya ke GND. Resistor eksternal tidak diperlukan; kita akan memakai pull-up internal.
  2. Upload kode di bawah dan buka Serial Monitor di 9600 baud.
  3. Tekan tombol beberapa kali dan lihat penghitungnya bertambah. Meski ada delay(500) di dalam loop(), tidak ada satu pun tekanan yang terlewat.
  4. Jangan kaget kalau angkanya kadang langsung melompat 2 atau 3: itu pantulan kontak (contact bounce), dan sebentar lagi kita akan memperbaikinya.
C++ (Arduino IDE)
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.
Pola yang paling andal adalah “pasang flag, kerjakan di loop”: ISR hanya mengubah sebuah volatile bool menjadi true atau menaikkan penghitung, dan loop() yang mengerjakan pekerjaan sebenarnya, seperti memperbarui layar, menggerakkan motor, atau menghitung, begitu melihat flag itu. Kalau kamu menulis loop() dengan millis() alih-alih delay, seluruh sistem tetap cepat sekaligus konsisten.

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.

C++ (Arduino IDE)
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).

C++ (Arduino IDE)
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.

C++ (Arduino IDE)
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:

TimerUkuranDipakai olehPin PWM
Timer08-bitmillis(), delay(), micros() (fungsi waktu)5, 6
Timer116-bitLibrary Servo, TimerOne9, 10
Timer28-bittone(), IRremote3, 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.

C++ (Arduino IDE)
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:

MicroPython
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

GejalaPenyebabSolusi
Interrupt tidak pernah terpicuattachInterrupt(2, ...) diberi nomor pin, atau dipilih pin yang tidak mendukung interruptPakai digitalPinToInterrupt(pin); di Uno hubungkan ke D2 atau D3
Satu tekanan menaikkan penghitung 2–5Pantulan kontakDebounce lewat software dan/atau tambahkan kapasitor 100 nF
Variabel tidak pernah berubah di loop()Lupa memakai volatileDeklarasikan variabel bersama sebagai volatile
Sesekali muncul nilai yang besarnya tidak masuk akalVariabel multi-byte tidak dibaca secara atomikSalin di antara noInterrupts() dan interrupts()
Program macet atau millis() bergeserAda delay(), Serial, atau tugas panjang di dalam ISRDi ISR, cukup perbarui flag atau penghitung
Interrupt terpicu tanpa tombol disentuhPin mengambang (floating), tanpa pull-upPakai INPUT_PULLUP atau resistor eksternal 10 kΩ
ISR langsung berjalan sekali setelah attachInterrupt dipasang ulangAda 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 acakISR tidak berada di IRAM atau mengerjakan tugas beratTambahkan 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.

Lihat semua proyek
Nyala/Mati LED dengan Tombol (On/Off)
#10Pemula

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

4.8(100)
167
Debounce Tombol Raspberry Pi Pico: IRQ vs Polling Perangkat Lunak
#159Menengah
Premium

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.

0.0(0)
12
Kendali LED dengan Tombol Tekan ESP32 (Toggle)
#105Pemula

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.

4.6(39)
57
LED Toggle dengan Tombol Raspberry Pi Pico
#156Pemula

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).

0.0(0)
9
Penghitung dengan Layar 7 Segmen dan Tombol
#43Mahir
Premium

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

4.6(35)
47
Tes Waktu Reaksi Arduino dengan Isyarat Suara: Ukur Refleksmu dalam Milidetik (LCD + Buzzer + 3 LED)
#97Menengah
Premium

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.

4.6(33)
50
Penghitung WebSocket ESP32 (2 Tombol + LCD + Web Real-Time)
#130Mahir
Premium

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().

4.5(14)
23
Game Memori Arduino (4 LED + 4 Tombol + Buzzer)
#93Mahir
Premium

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

4.8(46)
69