Arduino DHT11 Kütüphane İncelemesi -3- dht.cpp Dosyası

Şimdi kütüphanenin en zor kısmı olan .cpp uzantılı dosyayı inceleyeceğiz. Kütüphanenin nasıl çalıştığını anlamak için bu dosyayı baştan sonra okuyup anlamamış şarttır. Burada daha önce öğrendiğimiz donanım bilgisinden de faydalanacağız. Şimdi .cpp dosyasını açalım ve baştan itibaren incelemeye başlayalım.

Burada önce her kütüphanede olduğu gibi DHT.h başlık dosyasının dahil edildiğini sonra ise MIN_INTERVAL olarak asgari aralık süresi sabitinin belirlendiğini görmekteyiz. TIMEOUT -1 diyerek ise zaman aşımını etkinleştirmiş oluyoruz. Bunun uygulanışını devamında göreceğiz.

DHT adında bir nesne tanımladığımızda pin ve type olarak iki değer belirliyoruz. pin değişkeni özel bir değişken olan _pin değişkenine aktarılmış. type değişkeni ise yine özel fonksiyon değişkeni olan _type değişkenine aktarılmış. Eğer derleyici tarafından __AVR tanımlanmış ise (AVR kullanıldığı zaman bu tanımlanıyor.) iki özel fonksiyon daha yürütülecektir. Bu fonksiyonlar Arduino kaynak kodu incelemesi çalışmamızdan bize tanıdık gelmektedir.

_bit = digitalPinToBitMask(pin);
_port = digitalPinToPort(pin);

Normalde bunlar Arduino’da karşımıza çıkmasa da Arduino kaynak kodunun içerisinde yer almaktadır. Bunu Arduino.h başlık dosyasını kütüphaneye dahil etmekle kulllanabilir hale geliyoruz. Bu fonksiyonlardan biri digitalPinToBitMask() fonksiyonu olup belirttiğimiz dijital ayağın bit konumunu bize vermektedir. digitalPintoPort() fonkiyonu ise dijital ayağın hangi porta bağlı olduğu bilgisini vermektedir. Kütüphanenin devamına bakmadım fakat buradan anladığım kadarıyla kütüphane _bit ve _port değerleri üzerinden doğrudan yazmaç ve bitleri üzerinden giriş ve çıkış işlemleri yapacağa benziyor. Bunun sebebi de digitalWrite() ve digitalRead() fonksiyonlarının oldukça hantal çalışmasıdır.

_maxcycles = microsecondsToClockCycles(1000); 

Burada gecikme 1 milisaniye bir gecikme süresi ayarlanmış. Bunun için yine Arduino’nun iç fonksiyonlarından olan microsecondsToClockCycles() fonksiyonu kullanılmış. Bu fonksiyona girdiğimiz mikro saniye değeri saat çevirimi değerine çevrilir.

Burada ise begin() fonksiyonunun iç yapısını görmekteyiz. Program dışında bir tanım yaptıktan sonra setup() fonksiyonu içinde begin() fonksiyonu ile başlatıyoruz. Biz burada _pin ayağının INPUT_PULLUP ile giriş yapıldığını görmekteyiz. Burada önemli bir noktadan bahsetmek istiyorum. Normalde biz DHT’nin DATA ayağının 5.1K dirençle yukarıya çekilmesi gerektiğinden bahsetmiştik. AVR mikrodenetleyicilerde ise portların dahili pull-up dirençleri vardır. Biz INPUT_PULLUP dediğimizde yukarıya çekme işlemi yapılmış oluyor. Bu sayede dirençsiz kullanma imkanımız vardır.

_lastreadtime = millis() – MIN_INTERVAL; Burada _lastreadtime değişkenine millis() ile başlangıçtan geçen süre ile yukarıda MIN_INTERVAL değeri çıkarılıp elde edilen sonuç atanmıştır. millis() – 2000 değerini burada görmekteyiz.

DEBUG_PRINT(“DHT max clock cycles: “);
DEBUG_PRINTLN(_maxcycles, DEC);

Burada hata ayıklama mesajlarını görmekteyiz. _maxcycles olarak yukarıda verilen değer ekranda yazdırılmaktadır.

pullTime = usec; usec değeri aslında begin() fonksiyonun içerisine yazılan bir argüman olmakta. Fakat normalde biz buraya bir değer yazmadan başlatıyoruz. Bunun standart değeri ise .h dosyasında 55 olarak görülmekte. pulltime adlı özel değişkene usec değerinin aktarılmasıyla fonksiyon bitmektedir. .h dosyasındaki açıklamada “Time (in usec) to pull up data line before reading” yazmaktadır. Yani okuma yapmadan önce kaç mikrosaniye yukarıya çekileceğine dair bir değerdir.

Burada sıcaklık okuma fonksiyonunu görmekteyiz. Aldığı S argümanı ölçü değeri olup eğer true ise fahrenheit false ise celcius değerini verir. force ise zorlama anlamı taşıyıp true olduğunda yeni değer okumaya zorlar. Bu fonksiyonun içerisinde okuma işlemi yapılmadığını görmemiz gerekir. Okuma işlemi if (read(force))  komutundaki read() fonksiyonu vasıtasıyla yapılmaktadır. Sonrasında daha öncesinde tanımladığımız DHT tipine göre bir seçim yapısı çalıştırılmakta ve DHT tipine göre veri formatlandırılması yapılmaktadır. Eğer değeri fahrenheit seçtiysek elde edilen değer convertCtoF(f); fonksiyonuna gönderilmekte ve elde edilen fahrenheit değeri ile değiştirilmektedir. Biz DHT11’i incelediğimizden dolayı şu kısım bizi ilgilendirmekte.

Burada data verisini anlamadan bunu çözmemiz kolay değildir. data verisini de aslında biz datasheetten anlamaktayız. Sıcaklık verisi bir tam sayı bir de ondalıklı değeri içermekteydi. data’nın her bir dizi elemanı bir bayt olduğuna göre önce ondalıklı sıcaklık verisi f değişkenine f = data[2]; komutu ile atanmaktadır. Sonrasında ise data[3] yani tam sayı kısım devreye girmektedir.

data[3] & 0x80 komutu sayının negatif olup olmadığını denetlemektedir. Bunu anlamak için 0x80 değerinin binary yapısına bakmak lazımdır. 0x80 değeri binary olarak 10000000 değerine karşılık gelmektedir. Negatif sayılarda baş bit (MSB) her zaman negatif işareti anlamına gelmekteydi. Yani elde edilen sayıda negatif işareti varsa float değerini negatife çekmektedir. Bunu da f = -1 – f; komutu ile yapmaktadır. En sonunda ise f += (data[3] & 0x0f) * 0.1; ile tam sayı değer kısmı f değişkenine aktarılmaktadır.

Burada santigrattan fahrenheite ve fahrenheitten santigrata dönüşüm formülleri yer almaktadır.

Bu fonksiyon yine data dizisi üzerinden işlem yapmaktadır. Yine read() fonksiyonunu if bloku içerisinde görebilirsiniz. Sonrasında tipe göre bir seçim yapılmaktadır. Bu kodda bizi ilgilendiren kısım şudur.

Burada data[1] 0.1 ile çarpılmış ve data[0] ile toplanmıştır. Bunların nem verisi olduğunu biliyoruz.

Burada ısı indeksinin hesaplandığını görmekteyiz. Bu kısım donanımla alakalı değil sadece ek bir yazılım özelliği. Isı indeksi sıcaklık ve nemi bir hesaplayarak hissedilen sıcaklığın hesaplanması demektir. Bu hesaplanmanın nasıl olacağına dair bir araştırma yaptım ve aşağıdaki formülleri buldum.
HI = -42.379 + 2.04901523*T + 10.14333127*RH – .22475541*T*RH – .00683783*T*T – .05481717*RH*RH + .00122874*T*T*RH + .00085282*T*RH*RH – .00000199*T*T*RH*RH

Bu sıcaklık endeksi fahrenheit üzerinden hesaplandığından eğer santigrat seçilmişse  en sonda santigrata çevrilmekte. Donanımla alakalı olmadığından burayı geçiyorum.

İşte asıl donanımla alakalı  ve bütün işi yapan fonksiyonu görüyoruz. read() fonksiyonu force argümanını almakta. Şimdi fonksiyonun iç yapısına bakarak devam edelim.

uint32_t currenttime = millis(); diyerek kaç milisaniye geçtiği bir değişkene atanmakta ve sonrasında şu komut işletilmektedir.

if (!force && ((currenttime – _lastreadtime) < MIN_INTERVAL)),

Eğer force 0 ise ve currentime değişkeni ve _lastreadtime değişkeni arasındaki fark MIN_INTERVAL’dan (2000) den küçükse return _lastresult; komutu ile son okunan değer geri döndürülmekte ve bir okuma yapılmamaktadır. Eğer force 1 ise !force ile 0 yapılacağından bu blok işletilmeyecek ve okunamaya zorlanacaktır. Aksi halde iki ölçüm arasında iki saniye geçmeden yeni bir okuma yapılmayacak ve return ile geri dönülecektir. Son okumanın değeri ise _lastreadtime = currenttime; olarak saklanmaktadır. _lastreadtime değişkeni sınıfın private kısmında tanımlanmıştır.

private içinde tanımlanan data[5]adlı diziyi biliyoruz. Bu dizi burada sıfırlanmaktadır. Bildiğiniz üzere DHT11 2 bayt nem, 2 bayt sıcaklık ve 1 bayt da eşlik biti olmak üzere toplamda 5 bayt veri göndermekte. İşte o 5 baytlık veriyi bu dizi içerisine kaydetmekteyiz.

ESP8266’nın Arduino çekirdeği için bir uyumluluk getirilmiş.

Önce DATA ayağına bağlı ayağı yüksek empedansa sokup pull-up direncini etkinleştiriyoruz. Okumaya ön hazırlık demektir.

İşte burada okuma sürecimiz başlıyor! _pin ayağı önce OUTPUT olarak ayarlanmış ve çıkış yapılmıştır. Sonra digitalWrite() ile bunun LOW yapıldığını yanı 0’a çekildiğini görüyoruz. Bundan sonra  datasheetten okuduğumuz kadarıyla en az 18 mili saniye beklememiz gerekli.

Burada DHT11 için konuşursak delay(20) fonksiyonu ile 20 milisaniye beklendiğini görmekteyiz.

cycles adında 80 adet 32 bitlik değişken tanımlanmış. Bu her çevirim için bir değişken demek.

Ayak 20 mili saniyeliğine 0 yapıldıktan sonra tekrar giriş moduna getirilmiş. Bu noktadan sonra okuma yapacağız.

pullTime yani usec verisi kadar bekleme yapılıyor. Burada usec DHT.h dosyasında begin fonksiyonun içinde tanımlanmış ve 55 değeri verilmiş. Yani DHT’den cevap alınana kadar 55 mikrosaniyelik bir bekleme konulmuş.

Kesmeler devre dışı bırakılmış çünkü burada zamanlamanın hassas olduğu bir iş var. Herhangi bir kesme ile rahatsız edilmek istemeyiz. Arduino’nun T0 kesmesi sürekli çalışmaktadır. Başta bunu durdurmak istemişler.

Önce 80 mikro saniyelik LOW sinyal sonrasında ise 80 mikrosaniyelik HIGH sinyal kontrol edilmekte. Burada expectPulse() fonksiyonu kullanıldığı için anlamak için önce o fonksiyona bakalım.

Bu fonksiyon biraz karmaşık görünse de ben bunun AVR ile ilgili olan kısmını ayıklayıp tekrar koydum. Böyle daha sade bir şekilde inceleyeceğiz. Öncelikle bool tipinde level adlı bir argümanı alıyoruz. Burada level seviye anlamı taşır ve beklediğimiz LOW veya HIGH sinyali ifade eder. Bunu yukarıda fonksiyonu çağırırken kullandık. Sonrasında uint32_t count = 0; diyerek 32 bitlik bir sayaç değişkeni tanımlıyoruz.

uint8_t portState = level ? _bit : 0; Bu komutla argüman olarak aldığımız level değişkeninin durumuna göre portState değişkenine _bit veya 0 değerlerini atamaktayız. portState değişkeninin ne işe yaradığını bir sonraki kısımda görüyoruz.

while ((*portInputRegister(_port) & _bit) == portState) { Burada portInputRegister fonksiyonu yine Arduino’nun içinde yer alan fakat Arduino’nun bile tutoriallerde hiç bahsetmediği fonksiyonlardan biridir. Bu fonksiyon parantez içine yazdığımız portun değerini bize geri döndürür. Bir nevi PINx yazmacından okuma yapmak gibidir. portInputRegister(_port) & _bit dediğimizde portun ilgili bitinin durumunu elde etmiş oluyoruz. Bunu portState ile karşılaştırdığımızda 1 veya 0 olmasına göre döngüyü devam ettiriyoruz.

if (count++ >= _maxcycles) {return TIMEOUT;} Burada while döngüsünün içindek if yapısında count++ diyerek önce sayaç değişkeni bir artırılır ve _maxcycles değişkeni ile karşılaştırılır. Eğer azami çevirim değeri geçilirse zaman aşımı sabiti geri döndürülür.Eğer zaman aşımı gerçekleşmezse okunan çevirim miktarı geri döndürülür. Yani bu fonksiyon ayaktan istediğimiz sinyalin ne kadar miktarda okunduğunu bize bildirmektedir.

Şimdi expectPulse() fonksiyonunun ne işe yaradığını öğrendiğimize göre başta bahsettiğimiz iki if yapısını kolaylıkla anlayabiliriz. Sonrasında işin en zor kısmına geliyoruz ve algılayıcıdan yollanan 40 biti okumaya başlıyoruz.

Burası asıl okumanın başladığı yerdir. Öncelikle toplamda 40 defa bir okuma gerçekleşmekte ve önce LOW sinyali okunup sonrasında ise HIGH sinyali okunmaktadır. cycles dizisi toplam 80 eleman içerdiği için her bit sinyaline iki değişken düşmektedir. İlk değişkende okunan LOW sinyalinin kaç çevirim sürdüğü yazılmakta ve sonrasında ise okunan HIGH sinyalinin kaç çevirim sürdüğü kaydedilmektedir. Datasheetten okuduğumuz kadarıyla okunan HIGH sinyalinin süresine göre bitler 0 veya 1 olarak okunmaktadır. Bütün bu kayıt işlemi bittiğine göre elde ettiğimiz değerleri bitlere dönüştürebiliriz.

Burada yine 40 çevirimlik bir döngü içerisinde LOW ve HIGH sinyallerini tespit edip bunu data dizisine aktarmaktayız. Bunun için önce lowCycles adındaki değişkene cycles dizisindeki LOW okunan kısımları highCycles değişkenine ise cycles dizisinde HIGH okunan kısımları atamaktayız. Eğer TIMEOUT yani zaman aşımı gerçekleşmişse DEBUG_PRINTLN makrosuyla bunu hata olarka yazdırmaktayız. Eğer zaman aşımı olmamışsa data[i / 8] <<= 1; diyerek birer bit kaydırılır ve buraya bir mi sıfır mı yazılacağı ise şu komutla belirlenir.

if (highCycles > lowCycles) {
// High cycles are greater than 50us low cycle count, must be a 1.
data[i / 8] |= 1;
}

Burada 1 konumu 0 konumundan uzun sürdüyse okunan bitin 1 olduğu belirlenmekte ve bir yazılmakta. Eğer böyle bir şey olmadıysa 0 olmakta ve değer sıfır olarak kalmaktadır.

Burada okunan verinin tamamı hata ayıklama olarak ekrana yazdırılmakta.

Burada eşlik biti karşılaştırılması yapılmakta ve doğru ise okunan verinin doğru olduğu yazdırılmaktadır.

 

DHT.c dosyasının tamamı böyleydi. Başta size karmaşık görünse de her kütüphane yazarının ayrı bir yöntemi olduğundan bazısı daha basit bazısı daha karmaşık halde kütüphaneyi yazabilmekte. Bu tarz kütüphaneleri okuyup anlayabilmeniz gömülü sistemler üzerinde çalışabilmeniz için büyük önem arz eder.

Bizi Facebook grubumuzda takip etmeyi unutmayın. Bilgili ve öğrenmeye hevesli bir topluluk oluşturmak istiyoruz.

https://www.facebook.com/groups/1233336523490761/

UYARI!!

Gökhan Dökmetaş

"Arduino Eğitim Kitabı" ve "Arduino ve Raspberry PI ile Nesnelerin İnterneti" kitaplarının yazarı. Başkent Teknoloji ve Dedektör Merkezi'nde Ar-ge Sorumlusu. Araştırmacı-Yazar.

You may also like...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.