Arduino Kaynak Kodu İncelemesi -1- Wiring.h Dosyası

Günümüzde pek çok mühendis adayının hatta mezun mühendisin bile öğrenmeye çalıştığı (!) Arduino platformuna biz daha derinden bir göz gezdireceğiz. Böylelikle Arduino programlamayı değil Arduino’nun nasıl yapıldığını görme şansınız olacak. Kaynak kodunun tamamını incelemek için zamanım olmadığından sadece önemli gördüğüm ve AVR programlamada bizi ilgilendiren kısımlarından bahsedeceğim. Arduino programcısı olup AVR’ye geçen fakat Arduino fonksiyonlarına alışmış kişiler de bu fonksiyonların iç yapısını öğrenince rahatça AVR üzerinde programlama yapabilecektir.

Bu yazı dizisini daha rahat anlayabilmek için bilmeyenlerin yazdığım Arduino Eğitim Kitabı ve C ile AVR Programlama yazı dizilerini okumasını rica ederim. Bu çalışma her ne kadar önemli olsa da giriş seviyesine hitap eder nitelikte değildir. Hiç bilmeden bu yazıları okuyup da Arduino’nun kaynak kodunu anlamanız beklenemez. Yine de yer yer derslerden ve kitaptan referans göstererek anlamanız için basitleştireceğim.

Arduino’nun anlatılmayan hikayesi yazımızda Hernando Barragan’ın dilinden Arduino’nun nasıl ortaya çıktığını okuyabilirsiniz. Bu yazıları okumanız Arduino’ya olan bakışınızı değiştirecektir.

http://www.lojikprob.com/embedded/arduinonun-anlatilmayan-hikayesi-1-wiringin-dogusu/

http://www.lojikprob.com/embedded/arduinonun-anlatilmayan-hikayesi-2-arduinonun-dogusu/

Bir şeyler öğrenmek istiyorsanız bol bol okumak zorundasınız. Bu sitede yazılan yazılar dahil bu konudaki makalelere 2 dakika göz gezdirip bırakıyorsanız bir şey öğrenmeniz beklenemez.  Bu yazı dizisini de mümkün olduğunca uzun tutarak bilgi yönünden daha da zenginleştirmeyi hedefleyeceğim. Eğer 2 dakika göz gezdirmekle okunacak bir eğitim makalesi yazan bir yazar varsa o yazar işini yapamıyor demektir. Bir eğitim yazısı yazmak haber sitesi yazısı yazmaya benzemez.

Arduino kaynak kodunun pek çeşitli sürümleri mevcuttur. En güncel sürümü değil en eski ve sade sürümü incelemeyi hedeflediğim için elimde fazla seçenek olmasa da işin ortasını bulma gayretinde oldum. Eski sürümleri incelediğimde  Arduino namına pek bir şey göremediğimi söylemem gerekir. Tamamı Wiring tabanlı olan sistemde kendi ürettikleri ucuz kartı çalıştırmaya yönelik bir kütüphane dosyasından başka Arduino’ya özgü bir şey göremiyoruz.

O yüzden Arduino Core adı verilen Arduino çekirdeğinin ilk sürümünü incelemekle işe başlayalım. Hem günümüz Arduino kaynak koduna benzerlik gösterecek hem de yeteri kadar sade bir kod olacak. Yıllar geçtikçe bu kodun ortalarına yapılan eklemeler ve çıkarmalar ile kod kalabalığı oldukça artmış olduğundan biraz da mecburiyetten ilk sürümleri tercih ettiğimizi söyleyelim. İşin içinden çıkamadıkça çalışmamız bir işe yaramaz.

Benim tercih ettiğim Arduino kaynak kodu şu bağlantıdadır.

https://github.com/arduino/ArduinoCore-avr/tree/master-older

Sizin bu kodu indirip Notepad++ gibi bir kelime işlem programında takip etmeniz anlamanız için daha faydalı olcaktır.

Arduino’nun Github sayfasını incelediğinizde diğer geliştiriciler tarafından söylenen bugları ve hataları okuyabilirsiniz. Bu hataları incelemek şimdilik bizim görevimiz değil. Şimdi kodları C ve AVR yönünden inceleyelim.

Arduino çekirdeğinin ilk dosyaları Wiring adıyla adlandırılmıştır. Aslında bu dosyalar Arduino’ya ait olmayıp Wiring’e aittir. Wiring’in kaynak dosyalarına göz atmanız mümkündür. Arada pek fark göreceğinizi sanmıyorum. Bu ilk dosyalardan biri olan Wiring.h dosyasını incelemekle işe başlayalım.

Bu kod bildiğimiz C/C++ kütüphanesinden başka bir şey değildir. Derleyici için bir şey söz konusu değildir. Eğer AVR kütüphane başlık dosyalarını inceleseydik makine dilinde yapılan bazı tanımları görebilirdik. Örneğin PORTA, DDRB gibi yazmaç adları aslında birer adres değerine tanımlanmış olurdu. Burada bizim karşılaştıracağımız ise AVR-GCC ve Arduino olacaktır. Şimdi kodları satır satır inceleyelim. En baştan itibaren baktığımızda iki adet kütüphane dosyasının eklendiğini görüyoruz.

Burada avr/io.h başlık dosyasını C ile AVR Programlama derslerimizden hatırlamış olmanız gereklidir. Bu başlık dosyası AVR-GCC derleyicisinde AVR mikrodenetleyiciler için bazı temel giriş ve çıkış fonksiyonlarını barındırıyordu. Aslında bu başlık dosyası kendi başına çalışmayıp eklendiğinde çeşitli başlık dosyalarını da beraberinde getirir.  Bu tamamen AVR programlama ile alakalı olup temel giriş ve çıkış ve port işlemleri kullanacağımız zaman io.h başlık dosyasını muhakkak çağırmamız gereklidir. Bu başlık dosyasının kaynak kodu aşağıdaki bağlantıda mevcuttur. Fikir edinmek için inceleyebilirsiniz.

https://www.nongnu.org/avr-libc/user-manual/io_8h_source.html

Bu kütüphane dosyasının sizin kullandığınız her mikrodenetleyici için ayrı bir başlık dosyası olduğuna dikkat edin. Hangi mikrodenetleyiciyi kullanmayı seçtiyseniz ona göre ayrı bir dosya çağırıyor. Bu Arduino çekirdeğinde hala Atmega8 kullanıldığı için şimdilik Atmega8 başlık dosyasını sizlerle paylaşalım ve buradan bir ayrıntıyı sizlere açıklayalım.

https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/include/avr/iom8.h

Atmega8 başlık dosyasının Atmega328’e göre biraz daha kısa yani basit olduğunu görebiliyoruz. Peki AVR-GCC derleyicisi için yazılmış bu başlık dosyasında neler var ? Şimdi bir kod kesitinden buna bakalım.

Biz C dilini kullarak AVR mikrodenetleyicileri programladığımızda PINC, PORTA, DDRB diye bazı değişkenleri kullanıyorduk. Bunlar önceden tanımlanmış değişkenlerdi ve biz bunlardan değer okuyup ya da bunlara değer aktarabiliyorduk. Diğer yazmaçlar da bu şekildeydi yani hepsinin birer adı vardı. Bu adları datasheet’den öğrensek de bu adların arkasında ne olduğunu bilmiyorduk. İşte bu adların arkasında ne olduğu burada yazılıdır. PIND için 0x10 adres değeri, DDRD için 0x11 adres değeri,PORTD için 0x12 adres değeri… Bu değerlerin sağlamasını datasheet’den bakarak yapabilirsiniz. Biz PORTD’ye bir değer atadığımızda bu değer doğrudan bellekteki 0x12 adresine aktarılıyordu. PORTD sadece işi kolaylaştırmak için kullanılan bir aracı ad idi. Gerçekte PORTD diye bir şey yok 0x12 diye bir şey vardır. 0x12 değeri yazılımsal bir soyut değer değil donanımsal bir adres değeridir.

Assembly dilinin de buna benzer bir yöntemle ortaya çıktığını söyleyebiliriz. Assembly dili kısaca makine dilinin alfanümerik sembollerle ifade edilmesidir diyebiliriz. Assembly diline ait birkaç özellik de olsa da genel olarak biz makine koduna verilen kısa isimleri kullanarak programlama yapıyorduk. C dilini kullanan bir derleyici öncelikle kodu C dilinden Assembly diline çevirmek zorundadır. Assembly dilinden de makine diline kodun çevrilmesi çok kolay olmaktadır. Çünkü Assembly ile makine dili arasında pek yabancılık yoktur.

Yukarıda bahsettiğimiz mesele AVR-GCC derleyicisini ilgilendirse de burada da kullanıldığı için kısaca bahsedelim dedik. Dikkatimizi çeken bir başka şey ise binary.h adında bir başlık dosyası oluyor. Bu başlık dosyasının ne olduğuna baktığımızda ise sırayla yazılmış değerleri görüyoruz. Bu değerler çok uzun olduğu için küçük bir kısmını buraya koyalım.

Bu kodun binary (ikilik) sayıları decimal (onluk) sayılara çevirdiğini görebiliriz. 255’e kadar böyle devam etmektedir. Dosya oldukça uzun olsa da basit bir dönüşüm tablosundan ibarettir. İleride bu kodları incelediğimizde böyle bir değer gördüğümüz zaman dönüşümün yapıldığını aklımızda tutmamız gerekir.

digitalWrite(1, HIGH) ya da digitalWrite(1,LOW) dediğmizde bu HIGH ve LOW’un tam olarak ne olduğunu merak etmiş olabilirsiniz. İşte bu kullanılan HIGH ve LOW burada tanımlanmıştır. HIGH 1’e eşittir ve LOW 0’a eşittir. Aslında derleyici sizin HIGH yazdığınız yere sonra kendisi 1 yazmaktadır. Eğer HIGH yerine 1 yazarsanız yine programınız sıkıntısız çalışacaktır. Buradaki HIGH ve LOW anlaşılırlığı artırmak için koyulan iki tanımdır. Aşağıda INPUT ve OUTPUT, true ve false tanımlarında da bu görülmektedir.

Görüldüğü gibi bunların çok da özel bir niteliği yok. Sadece 1 sayısına ve 0 sayısına farklı adlar verilmiştir. Hiç bilmeyen biri perde arkasında çok karmaşık kodların çalıştığını düşünse de aslında böyle değildir. Gördüğünüz gibi programlarken çok derin anlamlar yüklediğimiz INPUT, OUTPUT, HIGH ve LOW değişkenleri aslında 1 ve 0’ın farklı adlarla temsil edilmesinden başka bir şey değildir. Bu bir (1) ve sıfır (0) değerlerinin fonksiyonlarda nasıl kullanıldığına ise ilerleyen bölümlerde değineceğiz.

Burada matematik işlemleri için kullanılacak sabit değerleri görüyoruz. Bunlardan başlıcası Pi sayısıdır. Ayrıca çevirim işlerinde kullanılacak sabitler de burada tanımlanmıştır. Matematik işleminin olduğu yerde bu değerlerin ne olduğunu bu başlık dosyasına bakarak öğrenmiş oluyoruz.

Alt bitin önce mi gönderileceği yoksa baş bitin önce mi gönderileceğini kararlaştıran tanımlardır. Bunu SPI fonksiyonlarında kullanıyoruz. Bu değerleri LSBFIRST ya da MSBFIRST olarak yazsak da aslında 0 ve 1 değerlerinden ibarettir.

Bu tanımlar kesmeler için kullanılmıştır. Kesmelerin ayak değişiminde, düşen kenarda ya da yükselen kenarda mı çalıştıracağı bu değerlere bağlıdır. Bunların da 1, 2 ve 3 değerlerinden ibaret olduğunu görüyoruz. Fonksiyona baktığımızda daha iyi anlayacağız.

Pek çok matematik fonksiyonunun burada tanımlandığını görüyoruz. Bu fonksiyonlar aslında bizim bildiğimiz fonksiyon şeklinde çalışmaz. #define ile tanımladığımız bir değer ya da fonksiyona atadığımız değeri derleyici doğrudan kes-yapıştır yapar. Normalde C dilinde fonksiyonun bir adres değeri vardır fakat burada #define ile kullandığımız fonksiyonlar ne kadar fazla olursa kod kalabalığı da o kadar fazla olur. C dilindeki normal bir fonksiyonu çağırdığımızda program o adrese gider burada ise derleyici kodu kopyalayıp yapıştırır. O yüzden #define ile fonksiyon tanımlamanız pek sağlıklı değildir fakat bazı yönlerde size esneklik verecektir.

Burada min(), max(), abs() gibi fonksiyonların nasıl çalıştığını görüyoruz. Bunlar basit matematik işlemleri olduğu için çok fazla anlaşılmayacak bir nokta yok. Mesela yukarıda tanımlanan DEG_TO_RAD ve RAD_TO_DEG değerlerinin burada radyan ve derece çevriminde kullanıldığını görüyoruz. sq() denen kare alma işlemi ise x sayısını kendisiyle çarpmaktan ibaret bir işlem. Bu matematik işlemlerinin yazılımsal olarak yapıldığına dikkat edin. İşlemci makine dilinde bu işlemleri yapabilecek bir karmaşıklığa sahip değildir. İşlemciye toplama, çıkarma ve karşılaştırma gibi çok basit işlemleri yaparak nasıl karmaşık bir işlem yapacağını burada biz öğretiyoruz.

Konumuz C dili dersi olmasa da programcıların gözünden kaçan bir noktayı dile getirme adına min(a,b) fonksiyonunu inceleyelim. Bu fonksiyon (a)<(b)?(a):(b) işlemini yürütür. C dilinde ? operatörü if/else işlemini yapmaktan öte gitmez. Eğer a değeri b’den küçükse a’yı geri döndürür değilse b’yi geri döndürür. Bunu if/else ile yapabileceğimiz gibi kısaltma adına ? operatörü ile de yapabiliriz.

Bu fonksiyonlar kesmeleri açıyor ya da kapatıyordu. AVR derslerinde göreceğiniz üzere kesmeleri açmak için sei() ve kapamak için de cli() fonksiyonlarını kullanıyorduk. Bunlar da bu fonksiyonları almış ve farklı bir ad vererek yeni bir fonksiyon diye ortaya koymuş. Bu tanım sadece fonksiyonun adını daha anlaşılır yapmaktan öte gitmemektedir. Beğenen veya beğenmeyen muhakkak çıkacaktır.  Bu sei() ve cli() fonksiyonları öyle bir şeydir ki AVR Assembly dilinde de aynı şekilde kullanılır. Yani makine kodu olarak da sei ve cli olarak bunu yazarız. Aslında bu komutların yaptığı iş SREG yazmacındaki kesme bayrak bitini birlemek ya da sıfırlamaktan öte bir şey değildir. SEI ve CLI komutlarının mikroişlemci komut kümesinde yer aldığını şu bağlantıdan görebilirsiniz.

https://people.ece.cornell.edu/land/courses/ece4760/AtmelStuff/AVRinstr2002.PDF

Mikroişlemci komut kümesini ileride AVR Assembly hakkında bir çalışmamız olduğunda inceleyeceğiz. Siz yine de fikir edinmek için bir göz gezdirebilirsiniz.

Bu fonksiyonlar zamanlama işlemlerinde kullanılmak üzere tanımlanmıştır. Normalde Arduino referansında görmemiz mümkün değildir. Demek ki kaynak kodda karşımızda çıkacaktır. Bu fonksiyonlar saat çevrimi ve zaman birimi arasında dönüşümleri yapar. F_CPU değeri işlemcinin kaç MHz’de çalıştığını belirleyen bir değerdi. AVR programlamada F_CPU’yu tanımlasak da aynı zamanda bu değer Makefile’da da tanımlanabilir.

Biz 8-bitten yukarı bir işlem yaptığımızda her zaman düşük ve yüksek bayt olmak üzere iki ayrı bayt değerinden bahsetmiştik. Örneğin 8 bitlik yazmaca 10 bitlik ADC okuma verisi sığdırılamıyordu. Bu durumda ise yüksek bayt ve düşük bayt olmak üzere iki ayrı yazmaca sırayla bu değer yazdırılıyordu. Okuma yine 8 bit üzerinden olacağı için okumanın bir sırası olması gerekliydi. İşte burada bu yüksek bayt ve düşük payt üzerinde yapılan işlemler görülmektedir. Kısacası yüksek bayt 8 adım sağa kaydırılırken düşük bayt ise değer sağlaması yapılarak olduğu gibi yazılmaktadır.

Burada bit bazlı işlem yapılırken kullanılan fonksiyonlar tanımlanmıştır. AVR Assembly dilinde bile bit bazlı işlem yapmak için SBI (birleme) ve CBI (sıfırlama) komutları mevcuttur. Biz C dilinde ise biraz karışık bir iş yapıp bit bazlı operatörleri kullanıyorduk. Bu operatörler mantık işlemleri üzerinden aynı işlemi yapıyordu. Bu tanımlar ise bu mantık işlemlerini fonksiyon haline getirmiştir. sıfır yapmak için yine &= ~ operatörünü bir yapmak için yine |= operatörünü kullanmışlar. Anlaşılmayacak bir şey olduğunu sanmadığımdan bir sonraki tanımı sizlere açıklayayım.

Bu tanıma göre bitWrite fonksiyonu değer, bit ve bit değeri olarak üç ayrı argüman alır. Burada bitvalue 1 ya da 0 olup ? operatörü tarafından kullanılır. Eğer bir (1) ise bitSet() fonksiyonu tarafından birlenir (anlayamayanlar için sözlük bağlantısı yukarıda verilmiştir.) eğer sıfır ise de bitClear() fonksiyonu tarafından sıfırlanır.

Burada C diline pek aşina olmayanlar için yabancı gelecek typedef kelimesini görmekteyiz. typedef “tip tanımlama” diyeceğimiz bir dil özelliği olup değişken adlarını değiştirip kendi değişken adımızı oluşturmamızı sağlar. Burada aynı uint8_t (8-bit işaretsiz tam sayı) değişken adını boolean ve byte olarak tanımlandığını görüyoruz. Fakat bir dakika, boolean sadece bir (1)  ve sıfır (0) değerlerini alabilen bir değişken değil miydi? Burada 0-255 değerini alabilen 8-bitlik bir değişken olarak tanımlandığını görüyoruz. Açıkcası “boolean” diye bizi kandırıyorlar. 🙂 Aslında şu şekilde bir boolean yapısı kullanılsaymış amaca yönelik olabilirmiş.

Böyle bir tanım daha fazla “boolean” özelliği taşımaktadır. Sadece adda boolean olan bir tanımdan daha kaliteli olduğu ortadadır. Alternatif olarak C99’da gelen stdbool.h başlık dosyasını kullanmak daha standart olan yoldur.

Ben programlama yaparken doğrudan u_int8 değişkenini kullanma taraftarıyım. Bu değişken 0 olduğunda boolean olarak sıfır, sıfırdan farklı olduğunda ise boolean olarak bir sayılırsa hiç bool ile uğraşmadan aynı iş görülmüş olur.

Burada #define bit(b) (1UL << (b)) adında bir makroyu görmekteyiz. Bu bir yerden bize tanıdık gelmektedir. Sanki AVR-GCC’deki  BV_() makrosu ile tıpatıp aynı duruyor. Makroyu incelediğimizde gerçekten de öyle olduğunu görüyoruz. BV_() makrosunun tanımı şu şekildedir.

Şimdi geriye ise #define tanımları değil fonksiyon tanımları kalıyor. Bu fonksiyonların ne olduğunu Arduino programcısı olarak az çok bilmeniz gereklidir. Fonksiyonları listeleyelim ve hangi fonksiyonlarının ön tanımının burada yapıldığına bir bakalım.

Baktığımızda Wiring.h  başlık dosyasında Arduino kodlarının temellerinin olduğunu görüyoruz. Hatta setup() ve loop() fonksiyonları bile burada tanımlanmıştır. Bu fonksiyonların ne işe yaradığını bilseniz de argüman olarak hangi değeri aldığını ve hangi değeri değer olarak geri döndürdüğünü buradan görmeniz mümkündür. Wiring.h dosyasının tamamı bu şekildedir. Birkaç ufak yeri bilerek atladım ve konudan çıkmak istemedim.

Arduino bir kütüphane paketi olduğu için pek çok başlık dosyası birbiriyle bağlantılı olarak çalışmaktadır. Kaynak kodun tamamını anlamak için pek çok başlık dosyasını incelemek ve birbiri arasındaki bağlantıyı çözmek gereklidir. Bir dili ne kadar iyi bilirseniz bilin başkasının yazdığı bir kodu anlamanın zorluğu daima mevcut olduğu için hatalarımızın olması kaçınılmazdır. Bu hataları ise bilgili okuyucular yorumlarda “doğrusunu yazmak” kaydıyla belirtebilir. Gerekli düzeltmeleri her zaman yapıyoruz.

LojikProb.com artık anlık bildirim eklentisini kullanmaktadır. Bizi takip etmek için bildirimleri sağ alttaki zil ikonundan açmanızı tavsiye ederiz. 

UYARI!!

 

 

Kapak Resmi, http://2.bp.blogspot.com/-7aiOWn5BEVg/T94pQIyqr2I/AAAAAAAAAIs/AM1iGlvTTxM/s1600/arduinorx_by_zuggamasta-d34m2lr.jpg

 

 

 

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.