AVR ile Bitfield Uygulaması
Bu makalede kısaca AVR ile C’nin bitfield özelliğini nasıl kullanacağınızı göstereceğim. Gömülü sistemlerde ileri seviye çalıştığınız zaman karşınıza daha soyutlanmış veri tipleri çıkacaktır. Şimdiye kadar yazmaçları birer değişken gibi kullanıp doğrudan veri atayarak çalışsak da örneğin ARM mimarisinde çalışırken CMSIS kullanacaksınız ve orada yazmaçların birer yapı elemanı olduğunu göreceksiniz. Örneğin PORT yapısı içerisinde port birimine ait okuma, yazma ve yönlendirme yazmaçlarını göreceksiniz. Buna ise doğrudan değer atayarak değil erişim operatörü (->) ile erişmeniz gerekecek. Örnek bir kullanım şöyle olabilir.
PORTA -> ODR = 0xFFFFFFFF
Bu tarz soyutlamalar sizi donanımdan biraz daha uzaklaştırsa da program yazarken işinizi kolaylaştırmaktadır. Özellikle bit erişim operatörlerini sürekli yazmak sizi sıkabilir ve kodun okunabilirliğini olumsuz etkileyebilir. Onun yerine her bir ayağın değerini değişkene değer atar gibi verseydik iyi olmaz mıydı?
Bu tarz kullanımları fonksiyonlar yardımıyla veren Arduino gibi platformların ise performans yönünden oldukça kötü olduğunu görmekteyiz. Mesela işin aslında bir öğrenci projesi olup hocası tarafından çalındıktan sonra üzerine kayda değer hiçbir iyileştirme ve ilave yapılmayan Arduino platformunda bu dijital giriş ve çıkış işleri oldukça acemice yazılmıştır. Biz çok daha iyisini şimdi yazmaya başlayacağız.
Şimdi yazdığımız örnek uygulamayı inceleyerek bu çalışmaya başlayalım.
Burada C’nin bitfield özelliği sayesinde bitwise operatörler yerine bit alanları yardımıyla bitlere erişim sağlayacağımız gibi bu bitleri birer değişkenmiş gibi kullanma imkanımız da vardır.
Şunu baştan diyelim ki AVR, ARM gibi mimarilerde x86 mimarilerinde olduğu gibi donanımsal bit alanları bulunmamaktadır. Donanımsal bit alanları bir hafıza hücresinde birden fazla değişkeni kullanma imkanı verip hafızadan tasarruf etmemize yaramaktadır. Ama ARM mimarisinde her adreslenebilir hafıza alanı 32 bitlik bir alan olup buraya sadece tek bir değişken atanabilmektedir. Mesela biz uint8_t tipinde 8 baytlık bir değişken tanımlasak dahi bu 32-bitlik bir yer kaplayacaktır. Aynı durum 16 bitlik değişkenler için de geçerlidir. Bu yüzden gömülü sistemlerde bool tipinde bir değişken tanımlamanın da çoğu zaman bir anlamı yoktur. Bizim burada tanımladığımız bit alanları ise sanal bit alanları olup gerçekte var olan bir bit alanı değildir. Derleyici optimizasyon ile bizim ne yapmak istediğimizi görüp ona göre kod üretmekte ve yine maskeleme komutları ile bunu yapmaktadır.
Burada bir yapı tipi içerisinde toplamda 8 adet unsigned char tipinde değişken tanımlandığı görülse de aslında iki nokta ile bir değişkenin içinde kaç bitlik bir alanı kapladığını belirtmekteyiz. Burada önemli nokta unsigned char olduğu için toplamda 8 bite tamamlamamız gerekmektedir. Burada da her birine bit bitlik değer vererek toplamda 8 bite tamamladık. bit0 en alt bit, bit7 ise en üst bit olarak tanımlanmaktadır. Biz şimdi bit0 dediğimizde değişkenin tamamını değil sadece en alt biti seçmiş olacağız. Bunun üzerinde yapacağımız işlemler o biti etkileyecek. “:1” diyerek tek bir bit olduğunu belirttiğimiz için sadece 1 ve 0 değerlerini alabileceğini unutmayınız.
Bit tanımlamalarında sadece bir satırı incelemek yeterli olacaktır. Diğerlerinde tanım yine aynı sadece değerler farklıdır.
Burada öncelikle bit_port2_dir diye bir tanım adı oluşturduk ve parantez içerisinde (volatile io_register*)_SFR_MEM_ADDR(PORTD) diye belirttik. _SFR_MEM_ADDR makrosunun ne anlama geldiğini ise AVR derleyicisinin sfr_defs.h dosyasında görmekteyiz.
#define _SFR_MEM_ADDR(sfr) ((uint16_t) &(sfr))
Bu PORTD’nin değerini değil adresini bize geri döndüren bir makro olarak karşımıza çıkmakta. PORTD’nin adresini alarak bunu tanımladığımız yapı değişkeninin işaretçisine çevirmekteyiz. Yani bu komut PORTD’yi alıp bizim tanımladığımız yapıya çevirmekte. Böylelikle bit bit erişme imkanımız olmakta. Yapı erişim operatörü ile (->) de istediğimiz bite erişim sağlamakta ve buna değer atayabilmekteyiz.
#define kirmizi bit_portd2_out
Sonrasında ise daha anlaşılır olması adına yeni bir tanım daha getirdik. PORTD’nin 2 numaralı ayağına bir kırmızı LED bağladığım için bunu kırmızı diye isimlendirdim.
bit_portd2_dir = 1;
Burada ise kırmızının çıkış olduğunu belirtiyorum. Gördüğünüz gibi her bir bit artık birer değişken haline geldi ve atama operatörü ile değer atayabiliyoruz. PORTD |= (1<<2); demekten çok daha anlaşılır oldu.
Uygulama kısmında ise performans testi yapmayı tercih ettim. Bunun için şöyle bir kod kullandım.
kirmizi = 1;
kirmizi = 0;
Bu kodu çalıştırdığımda 2MHz’lik bir sinyal elde ettim. Normalde bitwise operatörlerle aynısını yaptığımda 4MHz’lik bir sinyal elde ediyordum. Yarı yarıya bir performans kaybı söz konusu olsa da bu oldukça kabul edilebilir bir orandır. Mesela Arduino’dan 13 kat daha hızlı bir sonuç elde ettiğimi söyleyebilirim. Üstelik Arduino’nun bu noktada hiçbir avantajı da yoktur. Böyle bir port kullanımı daha pratik ve anlaşılır olmaktadır. Bir sonraki uygulamada Arduino’nun pabucunu dama atacağız.