Modul 2 – Menjelajahi GPIO ESP32 (6/8)

ESP32 with PIR Motion Sensor

Menggunakan Interupsi dan Timer untuk Deteksi Gerakan

Pada unit ini, Anda akan belajar cara mendeteksi gerakan menggunakan sensor gerak PIR (Passive Infrared). Dalam contoh ini, ketika gerakan terdeteksi, ESP32 akan memulai timer dan menyalakan LED selama jumlah detik yang telah ditentukan. Ketika timer berakhir, LED akan mati secara otomatis.

Dengan contoh ini, kita akan memperkenalkan dua konsep baru yang sangat penting dalam pemrograman mikrokontroler: interupsi dan timer. Konsep-konsep ini memungkinkan program kita untuk lebih responsif dan efisien.

Proyek ESP32 dengan Sensor PIR dan LED

ESP32 dengan sensor PIR dan LED pada breadboard

1
Pengenalan Interupsi (Interrupts)

Apa itu Interupsi?

Untuk memicu suatu peristiwa dengan sensor gerak PIR, Anda menggunakan interupsi. Interupsi sangat berguna untuk membuat hal-hal terjadi secara otomatis dalam program mikrokontroler dan dapat membantu menyelesaikan masalah timing.

Dengan interupsi, Anda tidak perlu memeriksa nilai pin saat ini secara konstan. Ketika perubahan terdeteksi, suatu peristiwa dipicu (fungsi dipanggil). Fungsi ini disebut sebagai Interrupt Service Routine (ISR).

Cara kerja interupsi
Ilustrasi cara kerja interupsi: program utama akan terhenti sementara saat interupsi terjadi

Ketika interupsi terjadi, prosesor menghentikan eksekusi program utama untuk mengeksekusi tugas tertentu, dan kemudian kembali ke program utama seperti yang ditunjukkan pada gambar di atas.

Fungsi attachInterrupt()

Untuk mengatur interupsi di Arduino IDE, Anda menggunakan fungsi attachInterrupt(), yang menerima argumen berupa pin GPIO, nama fungsi yang akan dieksekusi, dan mode.

attachInterrupt(digitalPinToInterrupt(GPIO), fungsi, mode);

Pin GPIO Interupsi

Argumen pertama adalah nomor GPIO. Biasanya, Anda harus menggunakan digitalPinToInterrupt(GPIO) untuk mengatur GPIO yang sebenarnya sebagai pin interupsi. Misalnya, jika Anda ingin menggunakan GPIO 27 sebagai interupsi, gunakan:

digitalPinToInterrupt(27)

Dengan board ESP32, semua pin yang disorot dengan persegi panjang merah dalam gambar berikut dapat dikonfigurasi sebagai pin interupsi. Dalam proyek ini, kita akan menggunakan GPIO 27 sebagai interupsi yang terhubung ke sensor gerak PIR.

Pin interupsi ESP32

Pin ESP32 yang dapat digunakan sebagai interupsi (disorot merah)

Fungsi yang Dipicu – ISR

Argumen kedua dari fungsi attachInterrupt() adalah nama fungsi yang akan dipanggil setiap kali interupsi dipicu – interrupt service routine (ISR).

Fungsi ISR harus sesederhana mungkin, sehingga prosesor kembali ke eksekusi program utama dengan cepat.

Pendekatan terbaik adalah memberi sinyal ke kode utama bahwa interupsi telah terjadi dengan menggunakan variabel global dan dalam loop() memeriksa dan menghapus flag tersebut, dan mengeksekusi kode.

ISR perlu memiliki awalan IRAM_ATTR sebelum definisi fungsi untuk menjalankan kode interupsi di RAM.

void IRAM_ATTR detectsMovement() {
// Kode untuk menangani interupsi
}

Mode

Argumen ketiga adalah mode. Ada lima mode berbeda:

  • LOW: untuk memicu interupsi setiap kali pin berada pada level LOW;
  • HIGH: untuk memicu interupsi setiap kali pin berada pada level HIGH;
  • CHANGE: untuk memicu interupsi setiap kali pin berubah nilai—misalnya, dari HIGH ke LOW atau LOW ke HIGH;
  • FALLING: untuk ketika pin beralih dari HIGH ke LOW;
  • RISING: untuk memicu ketika pin beralih dari LOW ke HIGH;

Ketika sensor gerak PIR mendeteksi gerakan, ia mengirimkan sinyal HIGH—GPIO yang terhubung beralih dari LOW ke HIGH. Jadi, kita harus menggunakan mode RISING.

2
Pengenalan Timer

Mengapa Menggunakan Timer?

Untuk proyek ini, kita juga akan memperkenalkan timer. Kita ingin LED tetap menyala selama jumlah detik yang telah ditentukan setelah gerakan terdeteksi. Alih-alih menggunakan fungsi delay() yang memblokir kode Anda dan tidak memungkinkan Anda melakukan hal lain selama jumlah detik tertentu, kita akan menggunakan timer.

Konsep timer

Timer memungkinkan kode tetap berjalan sambil menghitung waktu di latar belakang

Fungsi delay()

Sampai sekarang, kita telah menggunakan fungsi delay() yang cukup mudah dimengerti. Fungsi ini menerima satu angka integer sebagai argumen. Angka ini mewakili waktu dalam milidetik program harus menunggu sampai pindah ke baris kode berikutnya.

delay(waktu dalam milidetik)

Ketika Anda melakukan delay(1000), program Anda berhenti pada baris itu selama 1 detik.

Catatan: delay() adalah fungsi blocking. Fungsi blocking mencegah program melakukan hal lain sampai tugas tertentu selesai. Jika Anda memerlukan beberapa tugas untuk terjadi secara bersamaan, Anda tidak dapat menggunakan delay().

Penting: Untuk sebagian besar proyek, Anda harus menghindari penggunaan delay dan menggunakan timer sebagai gantinya.

Fungsi millis()

Menggunakan fungsi bernama millis(), Anda dapat mengembalikan jumlah milidetik yang telah berlalu sejak program pertama kali dimulai.

millis()

Mengapa fungsi itu berguna? Karena dengan menggunakan beberapa perhitungan matematika, Anda dapat dengan mudah memverifikasi berapa banyak waktu yang telah berlalu tanpa memblokir kode Anda.

LED Berkedip dengan millis()

Cuplikan kode berikut menunjukkan cara menggunakan fungsi millis() untuk membuat proyek LED berkedip. Ini menyalakan LED selama 1000 milidetik, dan kemudian mematikannya.

// konstanta tidak akan berubah. Digunakan di sini untuk mengatur nomor pin:
const int ledPin = 26; // nomor pin LED

// Variabel akan berubah:
int ledState = LOW; // ledState digunakan untuk mengatur LED

// Umumnya, Anda harus menggunakan "unsigned long" untuk variabel yang menyimpan waktu
// Nilainya akan dengan cepat menjadi terlalu besar untuk int untuk menyimpan
unsigned long previousMillis = 0; // akan menyimpan waktu terakhir LED diperbarui

// konstanta tidak akan berubah:
const long interval = 1000; // interval di mana untuk berkedip (milidetik)

void setup() {
  // mengatur pin digital sebagai output:
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // di sinilah Anda akan menempatkan kode yang perlu berjalan sepanjang waktu.
  
  // periksa apakah sudah waktunya untuk berkedip LED; yaitu, jika
  // perbedaan antara waktu saat ini dan waktu terakhir Anda berkedip
  // LED lebih besar dari interval di mana Anda ingin
  // berkedip LED.
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousMillis >= interval) {
    // simpan waktu terakhir Anda berkedip LED
    previousMillis = currentMillis;
    
    // jika LED mati, nyalakan dan sebaliknya:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    
    // atur LED dengan ledState variabel:
    digitalWrite(ledPin, ledState);
  }
}

Mari kita lihat lebih dekat pada sketsa berkedip ini yang bekerja tanpa fungsi delay() (menggunakan fungsi millis() sebagai gantinya).

Kode ini mengurangi waktu yang direkam sebelumnya (previousMillis) dari waktu saat ini (currentMillis).

Jika sisa hasil bagi lebih besar dari interval (dalam hal ini, 1000 milidetik), program memperbarui variabel previousMillis ke waktu saat ini dan baik menyalakan atau mematikan LED.

Karena cuplikan ini non-blocking, kode apa pun yang terletak di luar pernyataan if pertama itu akan berfungsi normal.

Anda sekarang harus dapat memahami bahwa Anda dapat menambahkan tugas lain ke fungsi loop() Anda, dan kode Anda masih akan berkedip LED setiap satu detik.

Tip: Anda bisa mengunggah kode berkedip yang disediakan ke ESP32 Anda untuk melihat bagaimana itu bekerja. Anda dapat memodifikasi jumlah milidetik untuk melihat bagaimana itu bekerja.

Contoh kode berkedip lengkap tersedia di GitHub repository ESP32 Course.

3
ESP32 dengan Sensor Gerak PIR

Skema Rangkaian

Berikut adalah daftar komponen yang Anda perlukan untuk merakit rangkaian:

Rangkaian ini mudah untuk dirakit. LED terhubung ke GPIO 26. Kami menggunakan Mini AM312 PIR Motion Sensor yang beroperasi pada 3.3V—ini terhubung ke GPIO 27.

Cukup ikuti diagram skematik berikut:

Skema rangkaian ESP32 dengan sensor PIR

Skema rangkaian ESP32 dengan sensor PIR dan LED

Penting: Skema ini menggunakan modul ESP32 DEVKIT V1 versi dengan 36 GPIO – jika Anda menggunakan model lain, silakan periksa pinout untuk board yang Anda gunakan.

Catatan: Sensor Mini AM312 PIR Motion yang digunakan dalam proyek ini beroperasi pada 3.3V. Namun, jika Anda menggunakan sensor gerak PIR lain seperti HC-SR01, itu beroperasi pada 5V. Anda dapat memodifikasinya untuk beroperasi pada 3.3V atau cukup memberikan daya menggunakan pin Vin ESP32.

Gambar berikut menunjukkan pinout sensor gerak PIR AM312.

Pinout sensor PIR AM312

Pinout sensor gerak PIR AM312

Kode

Setelah merangkai rangkaian seperti yang ditunjukkan dalam diagram skematik, salin kode yang disediakan ke Arduino IDE Anda.

Anda dapat mengunggah kode apa adanya, atau Anda dapat memodifikasi jumlah detik LED menyala setelah mendeteksi gerakan. Cukup ubah variabel timeSeconds dengan jumlah detik yang Anda inginkan.

https://github.com/RuiSantosdotme/ESP32-Course/blob/master/code/PIR_Interrupts_Timers/PIR_Interrupts_Timers.ino

#define timeSeconds 10

// Atur GPIO untuk LED dan Sensor Gerak PIR
const int led = 26;
const int motionSensor = 27;

// Timer: Variabel tambahan
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;
boolean motion = false;

// Memeriksa apakah gerakan terdeteksi, mengatur LED HIGH dan memulai timer
void IRAM_ATTR detectsMovement() {
  digitalWrite(led, HIGH);
  startTimer = true;
  lastTrigger = millis();
}

void setup() {
  // Port serial untuk tujuan debugging
  Serial.begin(115200);
  
  // Mode Sensor Gerak PIR INPUT_PULLUP
  pinMode(motionSensor, INPUT_PULLUP);
  
  // Atur pin motionSensor sebagai interupsi, tetapkan fungsi interupsi dan atur mode RISING
  attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);
  
  // Atur LED ke LOW
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // Waktu saat ini
  now = millis();
  
  // Cek apakah gerakan terdeteksi
  if((digitalRead(led) == HIGH) && (motion == false)) {
    Serial.println("GERAKAN TERDETEKSI!!!");
    motion = true;
  }
  
  // Matikan LED setelah jumlah detik yang ditentukan dalam variabel timeSeconds
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
    Serial.println("Gerakan berhenti...");
    digitalWrite(led, LOW);
    startTimer = false;
    motion = false;
  }
}

Cara Kerja Kode

Mari kita lihat kode ini lebih detail.

Mulai dengan menetapkan dua pin GPIO ke variabel led dan motionSensor.

const int led = 26;
const int motionSensor = 27;

Kemudian, buat variabel yang akan memungkinkan Anda mengatur timer untuk mematikan LED setelah gerakan terdeteksi.

unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

Variabel now menyimpan waktu saat ini. Variabel lastTrigger menyimpan waktu ketika sensor PIR mendeteksi gerakan. startTimer adalah variabel boolean yang memulai timer ketika gerakan terdeteksi.

Fungsi ISR

Fungsi detectsMovement() berjalan ketika gerakan terpicu. Fungsi ini menyalakan LED, mengatur variabel boolean startTimer ke True, dan memperbarui variabel lastTrigger dengan waktu saat ini.

void IRAM_ATTR detectsMovement() {
digitalWrite(led, HIGH);
startTimer = true;
lastTrigger = millis();
}

Catatan: IRAM_ATTR digunakan untuk menjalankan kode interupsi di RAM. Jika tidak, kode disimpan di flash dan lebih lambat.

Setup

Dalam setup(), mulai dengan menginisialisasi monitor serial pada baud rate 115200.

Serial.begin(115200);

Atur sensor gerak PIR sebagai INPUT PULLUP.

pinMode(motionSensor, INPUT_PULLUP);

Untuk mengatur pin sensor PIR sebagai interupsi, gunakan fungsi attachInterrupt() yang dijelaskan sebelumnya.

attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

Pin yang akan mendeteksi gerakan adalah GPIO 27, dan itu akan memanggil fungsi detectsMovement() pada mode RISING.

LED adalah OUTPUT yang statusnya dimulai pada LOW.

pinMode(led, OUTPUT);
digitalWrite(led, LOW);

Loop

Fungsi loop() terus berjalan berulang-ulang. Dalam setiap loop, variabel now diperbarui dengan waktu saat ini.

now = millis();

Kondisi berikut memeriksa apakah LED saat ini menyala (HIGH) dan apakah flag gerakan adalah false. Flag gerakan menunjukkan apakah gerakan telah terdeteksi sejak LED dinyalakan.

if((digitalRead(led) == HIGH) && (motion == false)) {
Serial.println("GERAKAN TERDETEKSI!!!");
motion = true;
}

Jika kedua kondisi benar, itu berarti gerakan terdeteksi saat LED menyala. Pesan “GERAKAN TERDETEKSI!!!” dicetak ke Serial Monitor dan flag gerakan diatur ke true.

Setelah tahap ini, kode kembali ke loop().

Kali ini, variabel startTimer benar. Jadi, ketika waktu yang ditentukan dalam detik telah berlalu (sejak gerakan terdeteksi), pernyataan if berikut akan benar.

if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
Serial.println("Gerakan berhenti...");
digitalWrite(led, LOW);
startTimer = false;
motion = false;
}

Pesan “Gerakan berhenti…” akan dicetak di monitor serial, LED dimatikan, dan variabel startTimer diatur ke false.

Demonstrasi

Unggah kode ke board ESP32 Anda. Pastikan Anda telah memilih board dan port COM yang benar. Buka Serial Monitor dengan baud rate 115200.

Gerakkan tangan Anda di depan sensor PIR. LED akan menyala, dan pesan dicetak di Serial Monitor yang mengatakan “GERAKAN TERDETEKSI!!!”. Setelah 10 detik, LED akan mati.

Demonstrasi ESP32 dengan sensor PIR

Demonstrasi ESP32 dengan sensor PIR – LED menyala saat gerakan terdeteksi

Tip aplikasi: Proyek ini bisa menjadi dasar untuk berbagai aplikasi praktis:

  • Sistem pencahayaan otomatis yang hanya menyala ketika ada orang di ruangan
  • Sistem keamanan yang mengirimkan pemberitahuan ketika gerakan terdeteksi
  • Penghitung kunjungan untuk mengetahui berapa banyak orang yang melewati area tertentu
  • Pengontrol perangkat yang mengaktifkan layar atau fungsi hanya ketika pengguna terdeteksi

4
Kesimpulan

Sebagai ringkasan, interupsi digunakan untuk mendeteksi perubahan dalam kondisi GPIO tanpa perlu terus-menerus membaca nilai GPIO saat ini. Dengan interupsi, ketika perubahan terdeteksi, suatu fungsi dipicu.

Anda juga telah belajar cara mengatur timer sederhana yang memungkinkan Anda memeriksa apakah sejumlah detik yang telah ditentukan telah berlalu tanpa memblokir kode Anda.

Interupsi dan timer adalah dua konsep penting yang akan sangat berguna dalam proyek-proyek Anda nantinya. Mereka memungkinkan kode Anda menjadi lebih responsif dan efisien, menangani beberapa tugas secara bersamaan tanpa terhambat oleh operasi yang memakan waktu.

Praktik Terbaik

Berikut adalah beberapa praktik terbaik untuk diingat ketika bekerja dengan interupsi dan timer:

Untuk Interupsi

  • Jaga fungsi ISR sesederhana mungkin
  • Hindari penggunaan delay() dalam ISR
  • Gunakan variabel volatile untuk variabel yang diubah dalam ISR
  • Selalu sertakan awalan IRAM_ATTR pada fungsi ISR di ESP32

Untuk Timer

  • Selalu gunakan unsigned long untuk variabel waktu
  • Perhitungkan kemungkinan overflow dari millis()
  • Hindari penggunaan delay() untuk tugas yang memerlukan eksekusi lain
  • Pisahkan logika waktu untuk tugas yang berbeda dengan variabel terpisah

Selanjutnya

Lanjutkan ke unit berikutnya untuk mempelajari tentang ESP32 Flash Memory – cara menyimpan data permanen (Tulis dan Baca).

Lanjut ke Unit 7: ESP32 Flash Memory – Store Permanent Data (Write and Read) →

👥

Komunitas & Dukungan

Jika Anda memiliki pertanyaan atau mengalami kesulitan dengan contoh sensor PIR, interupsi, atau timer pada ESP32, silakan tinggalkan komentar di bawah atau bertanya pada grup telegram https://t.me/kodingindonesia. Selamat belajar dan bereksperimen dengan ESP32!

 

Anton Prafanto

Konten developer kodingindonesia.com & staf pengajar tetap di Universitas Mulawarman Samarinda

all author posts