Web sitemize hoşgeldiniz, 07 Ocak 2025
muratdonmez.com.tr
Anasayfa » IOT » ESP8266 Arduino IDE ile Kesme ve Zamanlayıcıları Kullanma

ESP8266 Arduino IDE ile Kesme ve Zamanlayıcıları Kullanma

muratdonmez.com.tr
ESP8266 Arduino IDE ile Kesme ve Zamanlayıcıları Kullanma

Bu yazımızda, Arduino IDE kullanarak ESP8266 NodeMCU geliştirme kartı ile kesmeler ve zamanlayıcıları nasıl kullanacağımızı öğreneceğiz. Kesmeler (interrupts), mevcut değerini sürekli kontrol etmemize gerek kalmadan GPIO durumunda olan değişiklikleri tespit etmemizi sağlar. Kesmeler, herhangi bir değişiklik algıladığında bir olayı tetikleyebiliriz (bir fonksiyon çağırabiliriz).

Yapacağımız örnekte, bir PIR hareket sensörü kullanarak hareket algılayacağız. Hareketi algıladığında ESP8266’nın bir zamanlayıcı (timer) ile önceden belirleyeceğimiz süre için LED’i yakmasını ve süre dolduğunda da LED’i otomatik kapatmasını sağlayacağız.

Kesme (interrupt) oluşturmak için, attachInterrupt() fonksiyonunu çağıracağız ve çağırırken de GPIO kesme pinini, ISR (çağırılacak fonksiyon) ve hangi modu kullanacağını belirteceğiz. ISR fonksiyonu mutlaka ICACHE_RAM_ATTR özniteliğine (attribute) sahip olmalıdır. Mod olarakta CHANGERISING veya FALLING kullanılabilir.

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

Örneğimizi yapmadan önce Arduino IDE üzerine ESP8266 NodeMCU eklentisini kurmuş olmanız gerekmektedir. Henüz kurmadıysanız Arduino IDE Üzerine ESP8266 NodeMCU Kurulumu Nasıl Yapılır? yazımı referans alarak kurulumu gerçekleştirebilirsiniz.

ESP8266 Kesmeleri (interrupts) ne işe yarar?

Kesmeler (interrupts), mikro denetleyici programlarında işlemlerin otomatik yapılmasını sağlamakta ve zamanlama problemlerinin çözülmesine yardımcı olmaktadır.

Kesmeler (interrupts) pinlerde bir değişiklik farkettiğinde kodda belirlediğiniz bir olayı tetikler veya fonksiyonu çağırır, böylelikle pinleri sürekli kontrol etmemiz gerekmez. Bu fonksiyona kesme hizmeti rutini (interrupt service routine) (ISR) denir.

Kesme algılandığında, işlemci ana programın yürütülmesini durdurur ve tanımlanan görevi yerine getirdikten sonra tekrar ana programa geri döner. Kesme döngüsü aşağıdaki resimde görüldüğü gibi çalışmaktadır.

Bu, sürekli kontrol etmeye gerek kalmadan bir hareket algılandığında yada düğmeye basıldığında bir eylemi tetiklemek için çok kullanışlıdır.

attachInterrupt() Fonksiyonu

Arduino IDE’de kesme (interrupt) kullanmak için attachInterrupt() fonksiyonunu kullanıyoruz. Bu fonksiyonu çağırırken aşağıda da göreceğiniz gibi GPIO kesme pini, yürütülecek fonksiyon adı ve mod değerlerini de kullanmamız gerekmektedir.

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

GPIO Kesme pini

İlk değer GPIO kesmesidir. GPIO’lardan birini kesme (interrupt) pini olarak ayarlamak için digitalPinToInterrupt(GPIO) kullanıyoruz. Mesela, GPIO 14’ü kesme pini olarak kullanmak istersek aşağıdaki gibi yazıyoruz:

digitalPinToInterrupt(14)

ESP8266 ile kesme pini olarak GPIO 16 dışındaki herhangi bir GPIO pinini kullanabilirsiniz.

ISR

attachInterrupt() fonksiyonunun ikinci değeri, kesme her tetiklendiğinde çağırılacak olan fonksiyonun adıdır – kesme hizmeti rutini (interrupt service routine (ISR)).

ISR fonksiyonu mümkün oldukça basit olmalıdır, böylelikle işlemci fonksiyonu çalıştırıp ana programı yürütmek üzere hızlıca geri dönüş yapabilir.

En iyi yöntem, kesme tetiklendiğinde genel bir değişken kullanarak ana kodu haberdar ettikten sonra, loop() içinde bu değişkeni kontrol edip temizleyerek kodu çalıştırmaktır.

RAM’de kesme kodunu çalıştırabilmek için fonksiyon tanımından önce ISR’ların ICACHE_RAM_ATTR ye sahip olması gerekir.

Kesme (interrupt) Modları

Üçüncü değişken moddur ve 3 farklı mod bulunmaktadır.

  • CHANGE: Pin değeri her değiştiğinde kesmeyi tetiklemek için kullanılır. Örneğin, HIGH’dan LOW’a veya LOW’dan HIGH’a değişimlerde.
  • FALLING: Pin değeri HIGH’dan LOW’a değiştiğinde tetiklemek için.
  • RISING: Pin değeri LOW’dan HIGH’a değiştiğinde tetiklemek için.

Yapacağımız örnekte, RISING modunu kullanıyor olacağız çünkü PIR hareket sensörü hareket algıladığında bağlı olduğu GPIO LOW’dan HIGH’a geçiş yapar.

ESP8266 Zamanlayıcılar (timers) Nasıl Çalışır?

Bu yazımızdaki örneğimizde zamanlayıcılar (timers) kullanacağız. Hareket algılandıktan sonra LED’in önceden belirlediğimiz süre yanmasını istiyoruz. Bunun için kodumuzun çalışmasını engelleyen delay() fonksiyonunu kullanmak yerine bir zamanlayıcı kullanacağız.

delay() ve millis()

delay() fonksiyonu ile birlikte bir adet int (tamsayı) değişkeni kullanılmaktadır. Bu değişken, programın bir sonraki kod satırına geçene kadar beklemesi gereken süreyi milisaniye belirtmektedir.

delay(milisaniye cinsinden süre);

Programınızda delay(1000) fonksiyonunu kullanırsanız, program 1 saniye durup bekler. delay() programın çalışmasını engelleyen fonksiyondur. Engelleme fonksiyonları, bir programın söz konusu görev tamamlanana kadar başka bir şey yapmasını engeller.

Aynı anda birden fazla görev çalıştırmanız gerekiyorsa delay() kullanamazsınız. Projelerinizde mümkün olduğunca gecikmelerden kaçınmalı ve bunun yerine zamanlayıcılar kullanmalısınız. Böylelikle programınız daha hızlı olacak ve birden fazla görevi çalıştırabilecektir.

millis() fonksiyonunu kullanarak programınızın ilk çalıştırıldığı andan bu yana geçen süreyi milisaniye olarak döndürebilirsiniz.

millis();

Bu fonksiyon biraz matematik kullanarak, kodumuzun çalışmasını engellemeden geçen süreyi bulmamızı sağladığı için faydalı bir fonksiyondur.

millis() Kullanarak LED’i Yakıp Söndürme

millis() fonksiyonunu kullanmadıysanız ve kullanımına aşina değilseniz bu bölümü okumanızı tavsiye ederim. Eğer zamanlayıcılara aşinaysanız ve daha önce kullandıysanız PIR hareket sensörü projesine geçebilirsiniz.

Aşağıda verdiğim kod LED yakıp söndürme projesinde millis() fonksiyonunu nasıl kullanabileceğinizi göstermektedir. Bu kodu kullanarak LED’i 1000 milisaniye yakıp söndürebilirsiniz.

/*********
  Murat Donmez
  Bu ornegin detaylarina http://muratdonmez.com.tr den ulasabilirsiniz.
*********/

// LED'in baglandigi pini tanimliyoruz. const tanimlananlar degistirilemez.
const int ledPin = 26; 

// LED yanma durumunu ayarlamak icin kullaniyoruz. LOW sonuk, HIGH yaniyor
int ledDurumu = LOW; 

// zamani saklamak icin "unsigned long" degisken tipini kullanmalisiniz.
// zaman int degisken tipi icin kisa surede cok buyuk hale gelecektir.
unsigned long oncekiOkuma = 0; // LED durumunun son guncellendigi zaman

// const tanimlananlar degistirilemez.
const long yansonaraligi = 1000; // yanip sonme araligi (milisaniye)

void setup() {
  // dijital pini cikis olarak ayarla
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // Calistirmak istediginiz kodu her zaman buraya koyun.

  // LED'i yakip sondurmek icin zamani kontrol ediyoruz
  // mevcut zamanla LEDi yakip sondurdugumuz zaman arasindaki
  // farki kontrol ediyoruz ve yukarida tanimladigimiz sureden
  // buyuk veya esitse LEDi yakip sonduruyoruz.
  unsigned long sonOkuma = millis();

  if (sonOkuma - oncekiOkuma >= yansonaraligi) {
    // LEDi son yakip sondurdugumuz zamani degiskene atiyoruz
    oncekiOkuma = sonOkuma;

    // degiskene LED durumunu ata (kapaliysa yak, yaniyosa kapat)
    if (ledDurumu == LOW) {
      ledDurumu = HIGH;
    } else {
      ledDurumu = LOW;
    }

    // LED yakip sondurmek icin ledDurumu ile pin durumunu ayarla
    digitalWrite(ledPin, ledDurumu);
  }
}

Kodumuz Nasıl Çalışıyor?

Gelin delay() kullanmadan yaptığımız LEDi yakıp söndüren kodumuza yakından bakalım. Yanma sönme süresini ayarlayabilmek için tabii ki millis() fonksiyonunu kullanıyoruz.

Basitçe anlatacak olursak, önceden kaydettiğimiz zamanı (oncekiOkuma) geçerli yani son okuduğumuz zamandan (sonOkuma) çıkartıyoruz. Kalan değer verdiğimiz aralıktan (örneğimizde 1000 milisaniye) büyük yada eşitse sonOkuma yı geçerli zamanla güncelleyip LEDi yakıyoruz veya söndürüyoruz.

if (sonOkuma – oncekiOkuma >= yansonaraligi) {
   // LEDi son yakip sondurdugumuz zamani degiskene atiyoruz
   oncekiOkuma = sonOkuma;

Kodumuzda delay() gibi engelleyici fonksiyon olmadığı için ilk if koşul ifadesinin dışında kalan kodlar normal şekilde çalışacaktır. Böylelikle loop() fonksiyonu içine farklı kodlar ekleyerek çalıştırsak bile LEDimiz her 1 saniyede bir yanıp sönmeye devam edecektir.

Yukarıdaki kodu ESP8266 geliştirme kartınıza yükleyerek çalıştırabilirsiniz. Geliştirme kartınızın üzerinde bulunan yerleşik LED saniyede bir yanıp sönecektir.

ESP8266 NodeMCU Kullanarak PIR Hareket Sensörü Yapıyoruz

Zamanlayıcı kullanmayı öğrendiğinize göre artık kesme ve zamanlayıcı kullanarak hareket sensörü yapabiliriz. Sonrasında PIR sensör ile nasıl hareket algılama yapıldığını öğreneceğiz.

Parça Listesi

  • ESP8266
  • PIR Hareket Sensörü (AM312)
  • 5mm LED
  • 330 Ohm Direnç
  • Breadboard
  • Atlama Kabloları

Devre Şeması

ESP8266 NodeMCU kartımızın GPIO 12 (D6) pinine LEDimizi, GPIO 14 (D5) pinine de PIR hareket sensörümüzü aşağıda şemada göreceğiniz şekilde bağlayacağız.

ESP8266 NodeMCU pin detaylarını öğrenmek için ESP8266 üzerindeki hangi GPIO pinleri kullanılabilir?  yazımı okuyabilirsiniz.

Önemli: Bu projemizde kullandığımız mini AM312 PIR sensörü 3.3V ile çalışmaktadır. Eğer, HC-SR501 gibi 5V ile çalışan farklı bir PIR sensör kullanacaksanız, 3.3V ile çalışacak şekilde değiştirebilir veya 5V’u Vin pininden alabilirsiniz.

Aşağıdaki resimde AM312 PIR sensörün pin şemasını görebilirsiniz.

Kullanacağımız Kod

Devreyi yukarıda verdiğim şematik diagrama göre yaptıktan sonra aşağıdaki kodu Arduino IDE aracılığıyla ESP8266 NodeMCU kartınıza yükleyiniz.

Kodu olduğu gibi yükledikten sonra, hareket algılandığında LED’in yanma süresini değiştirebilirsiniz. yanmaSuresi değişkenini LED’in ne kadar yanık kalmasını istiyorsanız o süreye ayarlayınız.

/*********
  Murat Donmez
  Bu ornegin detaylarina http://muratdonmez.com.tr den ulasabilirsiniz.
*********/

#define yanmaSuresi 10

// LED ve PIR Hareket Sensorunun bagli oldugu pini tanimliyoruz.
const int led = 12;
const int pirSensor = 14;

// Zaman degiskenleri
unsigned long sonOkuma = millis();
unsigned long sonCalisma = 0;
boolean zamanlayiciyiBaslat = false;

// Hareket algilandiysa led pini HIGH yap, zamanlayiciyi baslat.
ICACHE_RAM_ATTR void hareketAlgila() {
  Serial.println("HAREKET ALGILANDI!!!");
  digitalWrite(led, HIGH);
  zamanlayiciyiBaslat = true;
  sonCalisma = millis();
}

void setup() {
  // Seri portu aciyoruz.
  Serial.begin(115200);
  
  // PIR Hareket Sensoru Modu INPUT_PULLUP
  pinMode(pirSensor, INPUT_PULLUP);
  // pirSensor pinini kesme ve RISING mod olarak ayarla
  attachInterrupt(digitalPinToInterrupt(pirSensor), hareketAlgila, RISING);

  // LED pinini LOW yapiyoruz
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // Zamani aliyoruz
  sonOkuma = millis();
  // yanmaSuresi degiskeninde tanimladigimiz sure dolunca LEDi kapat.
  if(zamanlayiciyiBaslat && (sonOkuma - sonCalisma > (yanmaSuresi*1000))) {
    Serial.println("Hareket Algilamasi Bitti...");
    digitalWrite(led, LOW);
    zamanlayiciyiBaslat = false;
  }
}

Kodumuz Nasıl Çalışıyor?

Kodumuzun nasıl çalıştığını anlamak için isterseniz kodumuza şöyle bir göz atalım.

Önce led ve pirSensor değişkenlerine bağlı oldukları pin numaralarını atayarak başlıyoruz.

const int led = 12;
const int pirSensor = 14;

Daha sonra, hareket algıladıktan sonra LED’i kapatmak için zamanlayıcı ile kullanacağımız değişkenleri tanımlıyoruz.

unsigned long sonOkuma = millis();
unsigned long sonCalisma = 0;
boolean zamanlayiciyiBaslat = false;

sonOkuma değişkeni ile geçerli zamanı, sonCalisma ile hareketin algılandığı zamanı ve zamanlayiciyiBaslat mantıksal değişkeni ile de zamanlayıcıyı başlatan değeri saklıyoruz.

setup()

setup() fonksiyonu içinde seri haberleşme noktasını 115200 baud hızında haberleşecek şekilde açıyoruz ki seri haberleşme ekranına mesaj yazdırıp kodu takip edebilelim.

Serial.begin(115200);

PIR hareket sensörünü INPUT_PULLUP olarak ayarlıyoruz. INPUT_PULLUP olarak ayarladığımızda kartımızın üzerinde dijital pinlere entegre olarak bulunan pull-up direncini aktif etmiş oluyoruz, bu işlemde dijital pinleri giriş olarak ayarladığımızda sinyalin bozulmamasını sağlıyor.

pinMode(pirSensor, INPUT_PULLUP);

PIR hareket sensörü pinini kesme olarak ayarlamak için yukarıda da bahsettiğimiz üzere attachInterrupt() fonksiyonunu kullanıyoruz.

attachInterrupt(digitalPinToInterrupt(pirSensor), hareketAlgila, RISING);

Hareketi algılayan pin GPIO 14 pinidir ve RISING modda hareketAlgila() fonksiyonunu çağıracaktır.

LED durumunu LOW olarak verebilmemiz için pin durumunu OUTPUT yapıyoruz.

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

loop()

loop() fonksiyonu sürekli çalışarak her döngüde sonOkuma değişkenindeki güncel zamanı günceller.

sonOkuma = millis();

Loop() içinde başka bir kod çalıştırmıyoruz ancak hareket algılandığında, setup() fonksiyonu içinde tanımladığımız kesmeden dolayı hareketAlgila() fonksiyonu otomatik olarak çalıştırılacaktır.

hareketAlgila() fonksiyonu içinde seri monitöre mesaj yazdırıp, LED’i yaktıktan sonra zamanlayiciyiBaslat değişkenine TRUE değerini atayıp, sonCalisma değişkeninde tuttuğumuz zamanı da geçerli zamana güncelliyoruz.

ICACHE_RAM_ATTR void hareketAlgila() {
Serial.println(“HAREKET ALGILANDI!!!”);
   digitalWrite(led, HIGH);
zamanlayiciyiBaslat = true;
sonCalisma = millis();
}

Zamanı da güncelledikten sonra kod tekrar loop() fonksiyonuna geri döner. zamanlayiciyiBaslat değişkeninin değeri TRUE olduğu için, hareket algılandıktan sonra saniye cinsinden verdiğimiz süre dolunca aşağıdaki if koşulu doğru olacaktır.

if(zamanlayiciyiBaslat && (sonOkuma – sonCalisma > (yanmaSuresi*1000))) {
Serial.println(“Hareket Algilamasi Bitti…”);
   digitalWrite(led, LOW);
zamanlayiciyiBaslat = false;
}

Burada da seri monitöre “Hareket Algilamasi Bitti” yazdırıp, LED’i kapattıktan sonra zamanlayiciyiBaslat değişkenini FALSE yapıyoruz.

Kodumuzu Arduino IDE üzerinde doğru kartı seçerek ESP8266 kartımıza yükledikten sonra doğru COM portunu ve baud hızını 115200 olarak seçtiğimizden emin olup seri monitörü açıyoruz. PIR hareket sensörünün önünden elimizi geçirdiğimizde hareket algılanacak ve seri monitöre “Hareket Algılandı” yazarak LED yanacaktır. 10 sn sonra LED otomatik olarak kapanacaktır.

Özetleyecek olursam, kesmeleri herhangi bir GPIO’nun durumunda değişiklik olduğunda hemen algılayarak fonksiyonumuzu çalıştırmak için kullanıyoruz. Kodumuzun çalışmasını engellememek için de zamanlayıcıları kullanarak kodumuzun performanslı çalışmasını sağlıyoruz.

Vakit ayırıp okuduğunuz için teşekkürler, umarım sizlere faydalı olabilmişimdir…

Etiketler:

muratdonmez.com.tr
BU KONUYU SOSYAL MEDYA HESAPLARINDA PAYLAŞ
Yorumlar
  1. Simla dedi ki:

    Nabız sensörü max30100 veya pulse nabız sensörü ile böyle bir uygulama var mı? Yardımcı olabilir misiniz?

    1. Murat Dönmez dedi ki:

      Merhaba, biraz geç geri dönüş olacak ama örnek uygulamaya “https://how2electronics.com/interfacing-max30100-pulse-oximeter-sensor-arduino/” adresinden ulaşabilirsiniz.

Yorum Yaz