AVR ile Kendi Mikrodenetleyici Kütüphanemizi Oluşturmak (Struct ve bitfield yöntemi ile)

Bu çalışmada bir önceki makalede bahsedilen yapı ve bit alanı özellikleri kullanılarak basit bir port kütüphanesi gerçekleştirilecek ve çalışma prensibi anlatılacaktır. Bu örnekte sadece B portunun yazmaçları üzerinden uygulama yapsak da bunu bütün bir mikrodenetleyici üzerine uygulayabileceğinizi ve hatta pek çok farklı platformda kullanabileceğinizi belirtelim. Belki başta bütün mikrodenetleyiciye uygulamak zaman alıcı gibi görünse de bunun kısa sürede telafi edileceğini öngörebiliriz.

İlk uygulamamız şu şekildedir,

Burada port_bits ve gpio_regs adında iki yapı değişkeninin tanımlandığını görebilirsiniz. İlk yapı değişkeninde bit alanları kullanılmış ve toplam 8 bite karşılık gelecek şekilde sekiz ayrı değişken tanımlanmıştır. İlk üçünü inceleyerek devam edelim.

uint8_t bit0:1;  uint8_t bit1:1;  uint8_t bit2:1; 

Burada değişkenlerin aynı tip olduğuna dikkat ediniz. “:” operatöründen sonra bu değişkenin kaç bit yer kapladığı belirtilmektedir. Yani bir uint8_t içinde bir bitlik 8 değişken toplamda 8 bit yer kaplamaktadır. bit0:1 diyerek bit0 değişkeninin bir bit yer kapladığını belirtmekteyiz.

Bu arada bu bit alanlarından bahsetmişken pek çok gömülü sistemde bu bit alanlarının sadece C derleyicisinde kaldığını, donanımsal olarak bir karşılığı olmadığını söyleyelim. AVR mikrodenetleyicilerde herhangi bir hafıza hücresine bit bölümlü bir erişim söz konusu değildir. 8 bitlik hücrelerin her birinin bağımsız bir adresi vardır ve okuma işleminde doğrudan 8-bitlik veri okuyup yazma işleminde de doğrudan 8 bitlik veri yazılır. STM32 gibi ARM Cortex-M tabanlı denetleyicilerde ise bu 32-bit seviyesindedir. 32 bitlik hafıza hücreleri tek tek ele alındığı için örneğin 32 bitlik bir hücrede 8 bitlik 4 adet char değişkeni veya 2 adet char değişkeniyle bir adet uint16_t tipinde değişken saklayıp bunlara ayrı ayrı erişmemiz söz konusu değildir. Bu konu hakkında daha ayrıntılı bilgiyi Joseph Yiu’nun kitabında bulabilirsiniz. Biz AVR’de bit alanlarına erişmek istediğimizde ise mikroişlemci OR ya da AND işlemlerini yaparak bu hafıza adresinden maskeleme yoluyla veriyi yazmakta veya elde etmektedir.

port_bits tipinde 8 ayrı bitten oluşan bir 8 bitlik yapıyı tanımladık. Biz bunu özel fonksiyon yazmacı yerine kullanmak istiyorsak buna adres atamamız gerekli. Eğer şu şekilde bir tanım yapsaydık C derleyicisi RAM’de rastgele bir adreste bu değişkeni oluşturacaktı.

port_bits myport;

O halde bizim port_bits tipinde bir yapının adresini tutan değişkene ihtiyacımız vardır. Böyle bir adres tutan değişkenin değerini değiştirerek değişkenin adresini değiştirme imkanına sahip oluruz.

port_bits* myport;

Buraya kadar işin zor kısmını bitirmiş olduk. Şimdi bir yapı daha ekleyelim. Datasheette her ne kadar DDRB, PORTB ve PINB olarak yazmaçlar ayrı olsa da bunların üst bir kategoride yani B portunda toplanması mümkündür. Bunlar tamamen B portuna ait yazmaçlar olup başka bir birim tarafından kullanılmamaktadır. O halde sanal bir B portu oluşturabiliriz.

Burada pinb, ddrb, portb diye isimlendirmemiz sayesinde bitlere erişimde adları kolay bir şekilde görebileceğiz. Diğer portlar için de aynı yapıyı kullanmak istersek değişkenlerin adlarını şu şekilde değiştirebiliriz.

Şimdi ise bir port tanımlayalım ve bu porta ait olan yazmaçların adreslerini sırasını gözeterek işaretçilere aktaralım.

gpio_regs gpiob = {(port_bits*)0x23, (port_bits*)0x24, (port_bits*)0x25};

Burada 0x23 pinb’ye, 0x24 ddrb’ye, 0x25 ise portb’ye karşılık gelecektir.

Şimdi ana programa bakalım ve nasıl kullanıldığını görelim.

gpiob.ddrb->bit5 = 1;

Burada gpiob yapısının ddrb değişkeninin bit5 değerine 1 atandığını görüyoruz. Yani bu DDRB |= (1<<5) ifadesine eşdeğerdir. Fakat burada kod tamamlayıcı yardımıyla bit operatörleri kullanmadan bunu yapmaktayız. Programı yazarken aşağıdaki örnekte görüldüğü gibi geliştirme stüdyosu bize yardımcı olabilmektedir.

Sonsuz döngüde ise bir LED yakıp söndürme işleminin yapıldığını görmekteyiz. Örneğin LED yakmak için şöyle bir kod yazılmıştır.

gpiob.portb->bit5 = 1;

Burada gpiob.portb.bit5 = 1 şeklinde yazılsa daha okunur olabileceğini düşünebilirsiniz. Fakat bu şekilde yazmak işaretçilerin olmasından dolayı mümkün olmamaktadır. İşaretçilerin değerine erişirken ok operatörü (->) kullanılmaktadır. Eğer işaretçiler kullanılmasaydı hangi adrese bu değerleri yazmamız gerektiğini belirtemeyecektik. Üzerinde çalıştığım C2000 mikrodenetleyiciler ise buna farklı bir çözüm getirmekte ve linker (bağlayıcı) ayarlarından yapı değişkenlerinin adreslerini belli bir adrese atayabilmektedir. Bu sayede C programında düz bir şekilde oluşturulan ve kullanılan yapı değişkenleri doğrudan özel fonksiyon yazmaçlarına etki edebilmektedir.

Buraya kadar uygulamayı eksiksiz yaptığımız söylenemez. Bütün bitlere tek tek erişme imkanımız olsa da yazmacın tamamına erişme imkanımız bulunmamaktadır. Çoğu zaman bunun eksikliğini hissetmemiz kaçınılmazdır. Bunun için hem aynı yazmaca bit bit erişebilmeli ama yeri geldiğinde de yine aynı yazmacın bütün değerlerini alabilmeliyiz. Bunun için aynı adreste iki farklı değişkene ihtiyacımız vardır. C dilinde bu özellik union ile sağlanmaktadır. İkinci uygulamaya union ekleyerek devam edelim.

Burada yukarıdaki uygulamadan farklı olarak bir union yapısını görmekteyiz. Bunu daha ayrıntılı incelemek için şu iki satıra dikkat etmek gereklidir.

port_bits bits;
uint8_t all;

Öncelikle port_bits adında bit alanlarına ayrılmış bir yapı tanımlamaktayız. Bu yapının boyutunun uint8_t boyutunda olduğunu unutmayın. Aynı sırada bir de uint8_t tipinde all adında bir değişken tanımlamaktayız. Yani all değişkeni ile bits değişkeni işin aslında aynı değişkendir. Yalnız bits değişkeni bitlere bölündüğü için tümden bir erişim sağlanamamaktadır, bunu all değişkeni telafi etmektedir.

union yapısının en son kısmında portb, ddrb, pinb şeklinde üç ayrı portbits tipinde değişken tanımlandığını görebilirsiniz. Bunlar ayrı değişkenler olduğu için ayrı adreslere sahiptir fakat içindeki değişkenler aynı adresleri paylaşmaktadır. Şimdi ise GPIO birimini temsil eden gpio_regs yapısını bu şekilde düzenleyelim.

Şimdi bunun iki ayrı kullanımını inceleyelim.

gpiob.ddrb->all = 0xFF;

Burada gpiob yapısının ddrb değişkenine erişilmiş ve all denilerek bütün bitler seçilmiş ve 0xFF değeri atanarak çıkış yapılmıştır. Daha öncesinde bunu bu şekilde kullanmamız mümkün değildi.

gpiob.portb->bits.bit5 = 0;

Burada ise portb değişkeninin bits kısmına erişilip bit5 seçilmiş ve bu 0 yapılmıştır. Artık all ve bits adında iki ayrı değişken unionda olduğu için araya bunu eklememiz şarttır. Tümüne erişmek için all, bir bite erişmek için bits.bitadı şeklinde belirtmemiz gereklidir. Bu şekilde bir yapıyı mikrodenetleyicinin bütün birimlerine uygulayıp SPI, ADC, UART, I2C gibi birimleri daha kolay bir şekilde kullanabilirsiniz.

Bu şekilde kullanım kolay bulunsa dahi performans noktasında pek çok kişide kaygılara sebep olabilir. Kısıtlı sistemlerden olan gömülü sistemlerde 8-bit mikrodenetleyiciler artık oldukça kısıtlı sistemler sayılabilir. Bu kısıtlı sistemlerde soyutlamada biraz ileriye gidiş performansı ciddi bir derecede olumsuz etkileyebilir. Bunu Arduino ile rahatça görmeniz mümkün. Bu yazılan kütüphane de performansı nispeten olumsuz etkileyecektir. Şimdi derlenen kodda bir LED yakma işinin nasıl yapıldığına bakalım.

Bir LED yakmak için fazlaca kod işletildiğini görebilirsiniz. Bunun kaç çevirim tutacağını ise mikrodenetleyicinin komut seti kılavuzundan bakarak öğrenmekteyiz. Benim hesabıma göre toplam 11 çevirim tutmaktadır. Bu da 16MHz’de çalışan bir işlemci için saniyede yaklaşık 1.45 milyon işlem anlamına gelmektedir. Eğer bir aç kapa işlemi yapmak isteseydik çıkıştan 0.725MHz’lik bir sinyal elde edecektik. Osiloskopta yaptığım ölçümlerde de buna benzer bir sinyali gözlemledim. Optimizasyon seviyesi 3 yapıldığında ise şu şekilde bir kod üretilmektedir.

Burada ise toplamda 8 çevirim gerçekleştiğini görmekteyiz. Bu da saniyede 2 milyon işleme denk gelmektedir. Eğer optimizasyon seviyesi yükseltilirse gayet kabul edilebilir sonuçlar elde edilmektedir. Bundan sonrasında artık geriye kalan iş bu örneği referans alarak kullanılacak çevre birimleri için datasheete bakarak gerekli tanımlamaları yapmaktır.

 

Gökhan Dökmetaş

Bilgi Teknolojileri Uzmanı

You may also like...

Bir cevap yazın

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