Arduino Kaynak Kodu İncelemesi -3- Wiring.c Dosyası ve Temel Arduino Komutları

Arduino kaynak kodlarını incelemeye 3. yazımızda devam ediyoruz. Bu inceleme yazı dizisinin bu alanda yapılan ilk çalışma olduğunu da söylememiz gerekir. İngilizce olarak şöyle bir kaynak bulsam da burada kısmen ve yüzeysel olarak incelendiğinden dolayı pek kullanma ihtiyacı hissetmedim. Yine de bu alanda yazılmış başka bir kaynak olarak bunu zikretmemiz gerekir.

http://garretlab.web.fc2.com/en/arduino/inside/index.html

wiring.c dosyasını incelediğimizde iş biraz daha ciddileşiyor. Burada AVR kodlarını ve yazmaçları görmemiz mümkün. Öncesinde ise zamanlayıcılar için çeşitli #define tanımlarını görüyoruz. Bu #define tanımlarını açıklayarak yazımıza devam edelim.

Burada önceki dosyada gördüğümüz clockCyclesToMicroseconds() fonksiyonu kullanılmış. Bunu anlattığımız için burada yapılan işlemi anlatalım. Burada TC0 zamanlayıcısının ön derecelendiricisi (prescaler) 64 olduğu için zamanlayıcının sayma ünitesi her 64 saat çevriminde birer birer artmaktadır. 256’da taşma gerçekleşeceği için bunu çevirim bazında hesaplamak için 64 ile 256 birbiriyle çarpılır. Böylelikle taşmanın gerçekleşeceği çevirim sayısı bulunmuş olur. Sırada fonksiyon ile bunu mikro saniyeye çevirmek vardır.

MICROSECONDS_PER_TIMER0_OVERFLOW yazan yerde anlayacağımız TC0 zamanlayıcısının taşması için gerekli mikro saniye değeridir.

Bu tanım ise yukarıdaki makroyu kullanır fakat bunu 1000’e bölerek yeni bir değer elde eder. Yukarıdaki mikro saniye değerini MILLIS_INC ile milisaniye olarak alıyoruz. Kodda MILLIS_INC gördüğümüz yerde TC0 zamanlayıcısının kaç milisaniyede taştığı verisi aklımıza gelmelidir.

Burada ise yukarıdaki değerler kesirli olarak elde edilir. 1000 üzerinden mod alma işlemi uygulanmış ve sonrasında 3 bit sağa kaydırılmıştır. Buradaki tanımlamaların ardından zamanlayıcılar ile alakalı daha fazla koda rastlıyoruz.

Burada zamanlayıcılarla alakalı üç adet değişken tanımlanmıştır. Bu değişkenler tanımlanırken başta hepsi sıfır değerini almıştır.

Burada SIGNAL adında farklı bir fonksiyon görmekteyiz. Bu sizi şaşırtmasın. Bu fonksiyon avr/interrupt.h kütüphane dosyasının içerisinde mevcuttur ve ISR fonksiyonu ile aynı görevi görür. Yalnız eski sürümde olduğu için şimdi kullanmamız tavsiye edilmez. Burada SIGNAL (TIMER0_OVF_vect) aslında ISR(TIMER0_OVF_vect) anlamına gelir. Farklı bir özelliği yoktur. Burada kesme vektörlerinin anlamını yine C ile AVR Programlama dersimizde tablo şeklinde vermiştik. Hatırlamak amacıyla tekrar söyleyelim. TIMER0_OVF_vect, TC0 zamanlayıcısı taştığı zaman yürütülen kesmedir.

Burada unsigned long m, f olarak iki adet yerel değişken tanımlanmış ve volatile değişkenlerdeki değer bunlara aktarılmıştır. Bunun sebebi ise yerel değişkenler yazmaçlarda tutulabilirken volatile değişkenler her defasında hafıza ünitesinden okunması gerekir.

m değerine MILLIC_INC değeri ilave edilir. Aynı şekilde f değerine de FRACT_INC değeri ilave edilir. Bu değerlerin ne olduğunu ise yukarıda açıklamıştık. FRACT_INC için bir azami değer tanımlanmış olup kesirli kısımla eğer bir bütün elde edilebiliyorsa bir milisaniye olarak f değişkenine eklenir ve f değerinden bu azami değer çıkarılır.

Bu fonksiyon özet olarak TC0 zamanlayıcısı taştığı zaman MILLIS_INC ve FRACT_INC makrolarından elde edilen değerleri alıp düzenleyerek kaydeder. Sonrasında ise önceden program tarafından tanımlanmış değerler üzerinden yapılan hesaplama değerlerini volatile olan  timer0_millis ve timer0_fract değerlerine kaydeder. Böylelikle sonrasında yine kalan değerler üzerinden hesaplama yapabilir. Fonksiyonun en sonunda ise timer0_overflow_count bir artırılmaktadır. Bu ++ operatörüyle olup her taşma gerçekleştiğinde bu değişken birer birer artar. Böylelikle toplamda kaç adet taşma gerçekleştiği görülebilir.

Bu fonksiyonun işleyişi bu kadardır. Bu fonksiyonda toplamda kaç milisaniye geçtiği kaydedilir, küsüratlı değer bir sonraki hesaplama için saklanır ve toplamda kaç taşma gerçekleştiği kaydedilir.

Bu fonksiyon hepimize tanıdık gelecektir. 🙂 Meşhur millis() fonksiyonu işte bundan ibarettir. Fakat yukarıda verdiğimiz fonksiyon olmadan millis() fonksiyonunun bir işe yaramayacağını söyleyelim. timer0_millis değişkenine bütün değer atamasını TC0 taşma kesme fonksiyonu yapıyordu. Bu fonksiyon ise unsigned long cinsinde timer0_millis değişkeninin değerini geri döndürüyor. oldSREG = SREG şeklinde bir komut kullanıldığına dikkat ediniz. SREG yani mikrodenetleyicinin durum yazmacının değerini oluşturduğumuz bir bayt değişkenine atıyoruz. Böylelikle lazım olduğunda sonra kullanma imkanımız oluyor. Sonrasında ise cli() fonksiyonuyla kesmeleri kapatıyoruz. Hesaplama anında bir kesme yürürse hesaplama bozulacağı için bunu yapıyoruz. Sonrasında ise yukarıda unsigned long olarak tanımladığımız m değişkenine volatile olan timer0_millis değişkenini atıyoruz. Sonrasında ise SREG yazmacına eski değerini yükleyerek kesmeleri tekrar etkin hale getiriyoruz. Fonksiyon en sonra ise m değişkenini geri döndürecektir. İşte millis() fonksiyonu bundan ibarettir. Burada timer0_millis değeri çalıştıkça artmaya devam edecektir ve belli bir süre sonra sıfırlanacaktır. Bunu elle sıfırlamanın da sistemde kararsızlığa neden olacağını söyleyenler var. En iyisi hiç dokunmamak.

Burada ise micros() fonksiyonunun iç yapısını görmekteyiz. micros() fonksiyonu millis() fonksiyonuna benzer bir özelliğe sahiptir. millis()’den farkı program başlangıcından itibaren kaç mili saniye geçtiğini değil kaç mikro saniye geçtiğini bize söylemesidir. Burada millis() fonksiyonuna benzer olarak m ve t adında değişkenlerin tanımlandığını ve kesmelerin kapatıldığını görüyoruz. Yine SREG yazmacının değerini bir değişkene kaydettiklerini görebiliriz. Burada yine #ifdef diye başlayan bir kontrol yapısı görmekteyiz. Arduino’nun taşınabilirliğinden söz etmiştik. Burada farklı bir donanım kullanıldığında yapılacak işlemden bahsediliyor. Aynı yazmaç bir mikrodenetleyicide TIFR adındayken ötekinde TIFR0 adında olabiliyor. Bu yazmaçların farklı adda olması taşınabilirliği olumsuz etkilese de yapabileceğimiz pek bir şey yoktur. Kodumuzu ona uygun yazmak durumundayız.

Atmel Studio’da AVR programlarken her aygıtın farklı bir başlık dosyasında farklı tanımlamalarının olduğunu görebiliriz. Yazmaç adları ve bazı değerler mikrodenetleyiciye göre değişmektedir. O yüzden gömülü sistemlerde Assembly ve C dilleri etkin olarak kullanılırken bir Framework veya üst seviye bir dil kullanılırken zorluk yaşanmaktadır. İster istemez sürekli farklı donanımlarda çalıştığımız için donanım bilgisine sahip olmamız gerekir ve bu donanım bilgisi gerektiren dilleri kullanmamız gerekir.

m değişkenine timer0_overflow_count değişkenindeki değer aktarılmaktadır. Böylelikle TC0 zamanlayıcısının kaç kere taştığı aşağıdaki komutta kullanılabilir.

Burada m değeri 8 bitlik zamanlayıcının taşma sayısını ifade ettiğinden ikilik olarak sekiz basamak üstte yazılmalıdır. Bu ise (m << 8) + t komutuyla sağlanır. t değerinin üstteki kodda 256 olduğuna dikkat edelim.  Bu değer ise clockCyclesPerMicrosecond() fonksiyonun 64’e bölümüyle çarpılır ve mikrosaniye değeri elde edilmiş olur. return ile de bu değeri geri döndürüyoruz.

Burada ise bizim belki binlerce defa kullandığımız delay() fonksiyonunun iç yapısını görüyoruz. delay() fonksiyonu bizim  belirlediğimiz mili saniye değerini argüman olarak alıp o değer kadar mikrodenetleyiciyi beklemeye sokuyordu. Bu bekleme donanımsal bir bekleme değil yazılım olarak meşgul ederek suni bir bekleme ortaya koymak idi. Burada da millis() fonksiyonu kullanarak bir başlangıç değeri elde ediliyor. Bu başlangıç değeri millis() fonksiyonu taşmaya yakın alınırsa programı sonsuz döngüye sokup çalışmaz hale getireceği bir gerçektir. Belki on binde bir bir ihtimal olsa da böyle bir zayıflık burada mevcuttur. Fonksiyon argüman olarak aldığı değeri millis() fonksiyonunun güncel değeri ile  karşılaştırır ve ms kadar zaman geçtikten sonra program sonsuz döngüden çıkar. İşlemci bu süreç içerisinde boş yere çalıştırılmış olur. Bu basit matematik ve mantık işlemini anladığınızı varsayıyoruz.

Burada ise delaymicroseconds() fonksiyonunu görmekteyiz. Bu fonksiyonu satır satır açıklama zahmetine girmeyip yine anladığımızı yazarak geçeceğiz. Burada yine oldSREG adında bir değişken tanımlanmıştır. Çünkü yine kesmeler kapatılacak ve sonrasında SREG yazmacının eski değeri yazmaca yüklenecektir. Bekleme fonksiyonlarında kesmeleri kapatmazsak bekleme esnasında mikrodenetleyici kesmeye gidecek ve programımız kararsız çalışabilecektir. Programın kararlılığına etkisi olan hassas işlemlerde kesmeleri kapatmamız gereklidir.

#if F_CPU >= 16000000L ile yine bir karar yapısı görmekteyiz. Burada farklı frekansta çalışan Arduino kartları için taşınabilirlik adına yapılan bir uyumluluk yapısı söz konusudur. Böylelikle her sistem için ayrı kütüphane yazmak yerine aynı kütüphaneyi farklı sistemlerde kullanabiliriz. C diline dair pek çok kitapta C dilinin taşınabilir olduğunu yazsalar da bu Gömülü C için pek geçerli değildir. Bunu da yine buradan görmekteyiz. Burada argüman olarak alınan us değerine dair çeşitli işlemler yapılmaktadır. Bu aşağıdaki Assembly koduna uyumlu hale getirmektedir.

 

Bu meşhur delay fonksiyonu AVR mikrodenetleyiciler için sıklıkla kullanılır. Assembly dilinde yazıldığı için ayrıntısını şu an açıklamamıza gerek yoktur. AVR için örnek kodları arattığınızda da bu fonksiyonu pek çok yerde görebilirsiniz.

Burada init() adında Arduino’da görmediğimiz bir fonksiyon görüyoruz. Bu fonksiyon içerisinde zamanlayıcılara ait kodları bulunduruyor. Bu fonksiyonun nerede ve ne zaman çalıştığı hakkında bir bilgimiz yok. Fakat bunun mikrodenetleyici ilk başladığında yürütülecek tanımlama ve hazırlama kodlarından oluştuğunu görmekteyiz. Bu fonksiyonun nerede kullanılacağına dair bir bilgiyi elde etmek için diğer dosyaları araştırdığımızda bu fonksiyonu main.cxx dosyasında görmekteyiz. Şimdi bu dosyanın içeriğine bakalım.

Bu aslında bizim gizlenmiş ana programımızdan ibarettir. Biz Arduino’da kod yazarken setup() ve loop() fonksiyonlarından ibaret bir yapıyı görmekteydik. Aslında gerçek yapı bu olup önce init() fonksiyonu çalıştırılıp ardından setup() fonksiyonu çalıştırılmaktadır. En sonunda ise loop() fonksiyonu sonsuz döngü içerisinde çalışmaya devam etmektedir. loop() içerisine program kodunu ve setup() içerisine bir kere çalışmaya mahsus tanımlama ve başlatma kodlarını yazıyorduk. Burada gizli bir init() fonksiyonu olduğunu ise şimdi görüyoruz. Arduino geliştiricilerinin bize bundan haber vermemesi oldukça normaldir çünkü bu fonksiyon Arduino programcısını alakadar etmez.

Şimdi bu gizli init() fonksiyonunda hangi kodların çalıştırıldığına bakalım.

Yine bir kontrol yapısıyla karşı karşıyayız. Bu kontrol yapısı derleyiciyi ilgilendiren bir yapı olup hangi kodun hangi şartta programa dahil edileceğini belirler. Örneğin Atmega8 kullanılmıyorsa bu komutlar programa eklenmez ve devamında belirlenen komutlar eklenir. Burada ise TCCR yazmaçlarının ad farklılığından dolayı ortaya çıkan bir farklılık söz konusudur.

Zamanlayıcı yazmaçları ve diğer bazı yazmaçların adı yukarıda belirttiğimiz gibi her AVR mikrodenetleyicisinde aynı değildir. Bunu mikrodenetleyicinin başlık dosyasından ve teknik veri kitapçığından okuyup öğrenmeniz gereklidir. C ile AVR programlama derslerini de ATmega328p mikrodenetleyicisine göre anlattık. ATmega32 kullanacağınız zaman zamanlayıcı yazmaçlarının adları değişmektedir  ve bu adları programcının öğrenmesi gereklidir. Tabi ki her mikrodenetleyicide tamamen değişen adlar söz konusu değildir. Fakat belli başlı serilerde belli başlı adlar değişmektedir.

Buradaki kodları incelediğimizde ise ön derecelendirici ve zamanlayıcı ayarlarının yapıldığını görmekteyiz. Arduino’da zamanlayıcıları tam anlamıyla kullanamadığımızı biliyoruz. Bunun bir sebebi de Arduino fonksiyonlarının zamanlayıcıları kendine göre kullanmasıdır. Bu koddan bunu çok rahat anlamamız mümkündür. Çünkü tüm ayarlar bize sorulmadan önceden yapılmaktadır.

Burada TC0 zamanlayıcısının millis() gibi zamanlama fonksiyonlarını çalıştırmak için kullanıldığını ve buna göre ayarlandığını görebiliriz. TC1 ve TC2 zamanlayıcıları ise PWM sinyali üretmek için kullanılır ve kodda ona göre ayarlanmıştır.

Burada yazmaç ve bit adlarını anlamak için C ile AVR Programlama derslerimize bakabilirsiniz. Mikrodenetleyici değişse de birbirine benzemektedir ve buradan çıkarım yapmanız mümkündür. Burada tek tek anlatarak konuyu boş yere uzatıp tekrarlamak istemiyorum.

Wiring.c dosyamız init() fonksiyonu ile bitmektedir. Arduino’nun temel kodlarını burada görmemiz mümkündür. Arduino kodlarını incelemeye devam edeceğiz. Bizi takip etmek için sağ alttaki zil ikonuna tıklayabilirsiniz. Böylelikle yeni yazılardan haberdar olursunuz.

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.