AVR ile Kendi Mikrodenetleyici Kütüphanemizi Oluşturmak (#define yöntemi ile)

Mikrodenetleyiciler üzerinde çalışırken STM32 için HAL, AVR için Arduino, genel olarak ARM mikrodenetleyiciler için mbed örneklerinde olduğu gibi bir programlama arayüzü ile bazı işlerin hazır fonksiyonlarla gerçekleştirildiğini görebilirsiniz. Bir de bunun yanında bare-metal adı verilen ve tamamen sıfırdan programı kendi yazdığınız bir yöntem bulunmaktadır. Bu yöntemde bütün programlama işi neredeyse tamamen size ait olsa da pek çok bare-metal programlamada bile geliştirme ortamlarında mikrodenetleyici kütüphanelerinin yer aldığını her şeyi sıfırdan yapmadığımızı göremekteyiz. Aslında her şeyi sıfırdan yapmanın özellikle bu noktada çok da bir anlamı yoktur. Ama bazen bu noktada bir geliştirme yapmak geliştirme ortamının sağladığına bağlı kalmayıp daha etkili bir şekilde program yazmamızı sağlayabilir. Bunu daha öncesinde C’nin yapı ve bit alanlarını kullanarak geliştirdiğim Arduino uyumlu port kütüphanesinde tecrübe etmiştim. Aşağıdan bunu okuyabilirsiniz.

https://medium.com/bilgisayarbilimleri/avr-ile-arduinodan-10-kat-daha-performansl%C4%B1-port-k%C3%BCt%C3%BCphanesi-5826f61f7366

Eğer mikrodenetleyici kütüphaneleri, hazır proje şablonları olmasaydı ve elimizde sadece bir C derleyicisinden başka bir şey olmasaydı nasıl program yazacaktık?

Bu durumda öncelikle kullanacağımız donanıma ait yazmaç ve bit tanımlarını yapmalı, yani donanımı program yazarken etkin bir şekilde kontrol edecek hale getirmeliyiz. Her ne kadar C dili oluşturduğumuz değişkenlerin adreslerini  ve hatta stack ya da heap konusunu arka planda halletse de biz mikrodenetleyicinin çevre birimlerini, ayarlarını ve dış dünya ile etkileşimini özel fonksiyon yazmaçları sayesinde yapmaktayız. Bu yazmaçların adresi hafızada bellidir ve doğrudan bu adreslere erişmemiz gerekir, yani belli bir adrese sahip bir değişken olmalıdır. Bu noktada hemen aklınıza C’nin işaretçileri gelebilir.

C dilinin en kuvvetli yanlarından biri olan işaretçiler işin aslında sistem programcılığının olmazsa olmaz noktalarından biridir. Biz mikrodenetleyicilerde özel fonksiyon yazmaçlarını kullansak da diğer bilgisayar sistemlerinde de yine alt seviye programlamada belli adreslere erişmemiz gerekmektedir. Bir uygulama programcısının işaretçilerle çok işi olmayabilir, hatta onun için bir kayıp da olmaz. Ama sistem programcılığı için geliştirilen C dilinde işaretçiler en kuvvetli özelliklerden biri olmanın yanı sıra olmazsa olmaz diyeceğimiz bir özelliktir.

Şimdi bu işaretçi özelliklerini kullanarak en basit seviyede nasıl kendi yazmaç değişkenlerimizi oluşturacağımıza bakalım.

Burada #define direktifi ile myPINB, myDDRB ve myPORTB olmak üzere üç adet yazmaç tanımı yaptık. Diğer çevre birimlerine ait yazmaçlar da bu şekilde tanımlanabilir, ben burada örnek gösterme adına PORT yazmaçları üzerinden anlatacağım. Bu PORT yazmaçları tanımında 0x23, 0x24, 0x25 gibi değerlerin olduğunu görmekteyiz. Bu değerler rastgele bir değer olmayıp üreticinin sağladığı mikrodenetleyici teknik veri kitapçığından doğrudan alınmıştır. İlgili kısmın görseli şu şekildedir.

Kaynak : http://ww1.microchip.com/downloads/en/DeviceDoc/40001906A.pdf

Buradan bu yazmaçların adres değerini teknik veri kitapçığından öğrenmemiz gerektiğini anlayabiliriz. Her üretici ürettiği donanıma ait fiziksel bilgileri bu kitapçıklarda anlatmaktadır. Yani hangi adreste ne olduğu, hangi adresteki hangi bitin ne işe yarayacağını buradan öğrenebiliriz. Yalnız her üretici ürettiği donanımla ilgili bir yazılım desteği vermek zorunda değildir. Bu durumda bazen her şeyi sıfırdan yapmanız gerekebilir, gömülü yazılımcının her şeye sıfırdan başlayacağı nokta ise bu kitapçıklardır.

Bir diğer nokta ise AVR mikrodenetleyicilerin hafıza yapısıdır. AVR mikrodenetleyiciler harvard mimarisi üzerine kurulu olduğu için program hafızası ile veri hafızası birbirinden ayrıdır. Bu durumda program akışı sürecinde veri hafızasına erişimimiz olup bu hafıza CPU yazmaçları, özel fonksiyon yazmaçları ve kullanıcı verisi olarak belli adresler aralığında ayrı bölümlere ayrılmıştır.

AVR mikrodenetleyicilerde özel fonksiyon yazmaçlarının 0x20’den 0xFF’e adresleri aralığında olduğunu görebiliriz. Bu yazmaçların sayısı üretilen modele göre değişiklik göstermektedir. AVR mikrodenetleyici kodları arasında görülen asıl uyumsuzluk sebebinin bu farklılıktan kaynaklandığını da görmekteyiz.

Buraya kadar neden 0x23, 0x24 gibi değerleri yazdığımızı ve bunları nereden öğrendiğimizi anladıysak kodun geri kalanında ne yapıldığını inceleyebiliriz. Şimdi örnek bir satırı ele alalım.

#define myPINB (volatile uint8_t*)0x23

Burada myPINB adında bir sabit tanımlanmakla beraber 0x23 diye belirttiğimiz değer onaltılık sayı sabiti olarak ele alınmak yerine 8 bitlik işaretçi olarak ele alınmaktadır. Yani bizim 0x23 değeri artık bir adres değeri olmuştur. Bunun için de tip dönüşüm ifadesi olarak (volatile uint8_t*) ifadesini kullanmaktayız. Volatile ise derleyiciye herhangi bir şekilde optimizasyon yapmamasını bildirir. Bu elimizdeki değer işaretçi sabiti, yani bir diğer ifadeyle adres değeri olduğuna göre buna erişim için C dilinde işaretçiler için kullanılan değer erişim operatörünü (*) kullanabiliriz.

*myPORTB = 0xFF;

Burada eğer değeri doğrudan atamaya kalkarsak bir anlamı kalmayacağı gibi derleyici hatası ile de karşı karşıya kalırız. Elimizde 0x20 adresi olduğuna göre bu adresin içine ancak değer atanabilir veya bu adresten değer okunabilir. Programı çalıştırdığımız zaman B portunun 500 milisaniye aralıkla yanıp söndüğünü görebilirsiniz. Eğer isteseydik işaretçi sabiti yerine işaretçi değişkeni kullanarak da bunu yapabilirdik.

Programı bu şekilde yazıp çalıştırdığımız zaman yine aynı işlevin gerçekleştiriğini görebiliriz. Bu programda veri hafızasından 4 bayt  işgal edilirken yukarıdaki örnekte bu değer sıfırdır.

Şimdi ise bu yazdığımız örneği geliştirmek adına bir adım atalım ve bizi zorlayan bir noktayı tespit edelim. Yukarıda göreceğiniz üzere program yazarken normal bir değişkene değer atarken yaptığımız gibi sadece değişkenin adını yazmamaktayız. Bunun yerine her erişim sırasında * operatörünü koymak zorundayız. Bu kod yazarken unutacağımız bir nokta olabilir. Bunu doğrudan tanımladığımız adları yazarak yapmamız da mümkündür. Bunun için programı şöyle düzenlememiz gerekli.

Burada aslında yapılan program yazarken koyacağımız (*) operatörünü #define tanımlarında baştan koymaktır. Buraya geldiysek artık Microchip Studio’nun bize sağladığı kütüphanenin neyden ibaret olduğunu görebiliriz. Bu yöntem basit ve sade bir şekilde program yazmamıza izin verse de kısmen eksiklikler ve zorluklar yaşatmaktadır. Bunları şu şekilde sıralayabiliriz.

  • En büyük zorluk bitlere erişimde yaşanmaktadır. Yukarıdaki örnekte olduğu gibi yazmaçlar tek bir değişken olarak ele alındığında zor görünmese de bitleri tek tek işlemek için bolca bitwise operatörleri kullanmak gereklidir. Bu da kod yazarken zorlanmamıza ve hata yapmamıza sebep olabilir. Ayrıca bitleri anlamlandırmak da zor olduğu için sık sık karıştırmaya ve datasheete bakmaya sebep olur.
  • Pek çok geliştirme stüdyosunun sağladığı otomatik tamamlama özelliği bu şekilde kullanılamamaktadır. Normalde . ya da -> gibi erişim operatörleriyle erişmek istediğimiz değerlerin listesi karşımıza çıkmaktadır.
  • Yazmaçlar dağınık olup bir çevre birimine ait yazmaçlar bağımsız olarak ele alınmaktadır. Mesela PORTB, DDRB, PINB üç ayrı yazmaç olsa da B portuna aittir ve üst kategori olarak bakarsak GPIO birimi olarak ele alınması gerekidir. Bu bağlantı program içinde sağlanmamaktadır. Bu eksiklik bir dağınıklığa sebep olmaktadır.

Bu sorunlar C dilinin yapı ve bit alanları (struct / bitfield) özelliği sayesinde çözülebilir. Her ne kadar nesne-sınıf özellikleri kadar olmasa da bu kadar yalın ve dağınık bir şekilde program yazmamızın önüne geçen özelliklerdir. Bu konuyu ise ilerleyen zamanlarda ele alacağız.

 

 

Gökhan Dökmetaş

Bilgi Teknolojileri Uzmanı

You may also like...

1 Response

  1. 27/11/2021

    […] çalışmada bir önceki makalede bahsedilen yapı ve bit alanı özellikleri kullanılarak basit bir port kütüphanesi […]

Bir cevap yazın

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