AVR Mikrodenetleyicileri İstediğiniz Programlama Dili ile Kullanın

Mikrodenetleyicileri diğer bilgisayarlardan ayıran en büyük özellik, mikrodenetleyicilerin diğer bilgisayarlarda olmayan veya erişimimize kapalı olan çevre birimlerine doğrudan erişim imkanı tanımasıdır. Bu sayede I2C veya SPI protokolünü kullanan algılayıcıları veya modülleri kullanabilir, analog değer okuyabilir veya doğrudan dijital giriş çıkışa sahip olabiliriz. Bunlar pek çok x86 tabanlı bilgisayarda (PC) erişemediğimiz özellikler olduğu gibi pek çok tek kart bilgisayarda da analog özellikler nispeten kısıtlıdır. Örneğin Raspberry PI bize UART, SPI, I2C ve GPIO özellikleri verse de ADC, DAC, karşılaştırıcı gibi mikrodenetleyicilerde sıkça gördüğümüz analog özelliklerden yoksundur. Ayrıca gelişmiş mikrodenetleyicilerde daha gelişmiş sinyal üretme yeteneğine sahip ve dış dünya ile etkileşimi daha kuvvetli olan zamanlayıcılar da bulunmaktadır.

Mikrodenetleyicileri kullanırken bütün bu çevre birimlerine erişmek için özel işlev yazmacı (Special function register) adı verilen hafıza birimleri kullanılır. Bu hafıza birimleri RAM üzerinde adreslenmiş olup CPU ve çevre birimleri arasında bir köprü görevi görmektedir. Örneğin ATmega328P’de (Arduino UNO) RAM bellekteki 0x2B adresine yazacağımız değer doğrudan D portunun çıkışına etki etmekte ve D0 ve D7 ayaklarından çıkış almamıza imkan vermektedir. Bu çevre birimleri arasında köprüyü CPU, okuma ve yazma komutlarını kullanarak kurmaktadır.

O halde biz bir şekilde bu özel işlev yazmaçlarına erişirsek istediğimiz mimari üzerinde, istediğimiz programlama dili ile mikrodenetleyicinin bütün çevre özelliklerini kullanabiliriz. Böylelikle mikrodenetleyicinin çevre özelliklerini kullanmak için donanımın ve geliştirme ortamının verdiği kısıtlamalar ile sınırlanmış olmayız.

Bu uygulamada mikrodenetleyicinin özel işlev yazmaçlarına bütün aracıları ve mikrodenetleyici taraflı yazılımı kaldırarak erişmeyi hedefledim. Seri port üzerinden bilgisayara bağladığım Arduino Python veya başka bir dil ile doğrudan yazmaçlara erişim imkanı vermektedir. Buna benzer firmata uygulaması olsa da burada AVR programlama bilgisiyle ve doğrudan donanıma erişerek kontrol etmemiz mümkündür.

Mikrodenetleyici Yazılımı

Mikrodenetleyici tarafında pratiklik açısından Arduino’nun Serial kütüphanesini kullandım. Burada mikrodenetleyicinin tek yaptığı iş seri port üzerinden gelen veriyi beklemek, aldığı veriyi özel işlev yazmaçlarına yazmak veya istenilen özel işlev yazmacını okuyarak UART üzerinden değerini göndermekdir.

Yazdığım bu ilk programda mikrodenetleyicinin program akışı şu şekilde gerçekleşmektedir.

  • Seri porttan veri gelene kadar bekle
  • Seri port tampon belleğindeki veriyi karakter dizisi olarak oku
  • Karakter dizisinde boşluk vs. karakterleri kırp
  • rw, *ptr ve val değişkenlerine sırasıyla değerlerini aktar. (Veri ileminde sıra çok önemli.)
  • Eğer rw 0 ise ptr üzerindeki adresteki veriyi oku ve bunu seri porttan gönder.
  • Eğer rw 1 ise val değerini ptr’deki bulunan adrese yaz.

Bu program istediğim gibi çalışsa da veriyi gönderme ve alma arasında neredeyse 1 saniyeye yakın bir gecikme oluyordu. Bu gecikmenin olması yine Arduino’nun fonksiyonlarından dolayıydı. Programı biraz daha basitleştirdiğimde çok daha hızlı çalıştığını gözlemledim.

Burada serial.read() fonksiyonu çok hızlı işletildiği için atlamalar olmasın diye 1ms gecikme ekledim. Teorik olarak UART protokolünde 3 baytın 115200 bps’de gönderilmesi 100us civarında sürüyor. Bu bekleme süresi ihtiyaç halinde düşürülebilir.

Mikrodenetleyici yazılımı görüldüğü gibi RAM bellekte istenilen alana okuma yazma erişimi vererek özel fonksiyon yazmaçları ve seri port arasında doğrudan bir köprü kurmakta. Ben burada mümkün olduğu kadar mikrodenetleyiciyi aradan çıkarmayı hedefledim.

Bilgisayar Yazılımı

Bilgisayar tarafında bize seri porta erişim veren herhangi bir programlama dilini kullanabiliriz. Örnek program Python dili ile yazılmıştır.

Bilgisayar yazılımını anlamamız için mikrodenetleyici ile bilgisayar arasındaki dili anlamamız gereklidir. Mikrodenetleyici bilgisayardan sırayla 3 bayt alır ve bilgisayara tek bir bayt gönderir.

Sırayla aldığı 3 bayt şu şekildedir: Komut -> Yazmaç Adresi -> Değer.

Komut: Okuma ve yazma olmak üzere ikiye ayrılır.
Yazmaç Adresi: Datasheetten okuduğumuz 8 bitlik adres verisidir.
Veri: Yazmaca yazılacak 8 bitlik değerdir.

Burada mikrodenetleyici alınan veriyi bayt olarak değerlendirdiği için seri port üzerinden göndereceğimiz veriler bayt olarak formatlanmak zorunda. bayt_oku ve bayt_yaz olarak komut sabitlerini belirlerken üzerinde okuma-yazma yapacağımız yazmaçların değerleri ise datasheetten öğrenilmiş ve onaltılık notasyonda belirtilmiştir.

İletişimde kullanaağımız fonksiyonlar sfr_read ve sfr_write fonksiyonlarıdır. sfr_read fonksiyonu okuma fonksiyonu olup okuma komutu gönderdikten sonra mikrodenetleyiciden gelecek değer verisini bekler ve bunu geri döndürür. Bu komutu alıp değeri geri döndürene kadar arada bir miktar gecikme olmaktadır. Eğer Serial nesnesi tanımlarken timeout=0 yapsaydık bunu kaçırmamıza sebep olacaktı. timeout = 0.1 diyerek 100ms’lik bir gecikme süresi ekledim. Burada bilgisayar 100ms boyunca mikrodenetleyiciden gelecek veriyi yoklayacaktır.

sfr_write fonksiyonu ise mikrodenetleyicinin kabul ettiği formatta (komut + adres + veri) veriyi hazırlayıp bunu gönderir. Yalnız val değeri tamsayı olduğu için birleştirme işleminde bunu öncelikle to_bytes() fonksiyonu ile bytes tipine çevirmek gereklidir. Birleştirme işleminin ardından 3'lü bayt sırası seri port üzerinden gönderilmektedir.

val değişkeninin neden önceden bytes değil de int olduğunu sonraki fonksiyonlarda daha iyi anlayabiliriz.

Burada bit bazlı işlemleri tam sayılar üzerinde yapabildiğimiz için sfr_deger değişkeni int olarak tanımlanmış olmakta. Bir yazmacın bir bitini birlemek ya da sıfırlamak için öncelikle ilgili yazmacı mikrodenetleyiciden okumamız gereklir. Bunun için sfr_read() fonksiyonunu kullandıktan sonra bunu from_bytes() fonksiyonu ile tam sayıya çeviriyoruz. Sonrasında ise bit bazlı operatörler ile argüman olarak aldığımız bit değişkenini kullanarak maskeleme işlemi yapıyoruz. Maskeleme işlemi aynı C dilinde olduğu gibi gerçekleşmekte. Örneğin bit değişkenimiz 4 değerine sahip olsun. Bu durumda 3 numaralı satırda yer alan (1<<bit) ifadesi (1<<4) ifadesine yani ikilik notasyonda 10000 ifadesine denk gelmektedir. Bunu sfr_deger değişkeni ile OR işlemine tabi tuttuğumuzda sfr_deger değişkenindeki 5 numaralı bit her halükarda 1 değerine sahip olacak ve diğer bitler bu işlemden etkilenmeyecektir. Sonrasında sfr_write ile güncellenmiş değeri aynı adrese yazmaktayız.

Benzer işlem sfr_bit_reset() fonksiyonunda da gerçekleşmekte fakat burada farklı olarak OR değil NOT işlemine tabi tutulan bit AND işlemiyle değere yazılmaktadır. Bu da her halükarda ilgili biti 0 haline getirmektedir.

Bu dört fonksiyon ile AVR geliştirme ortamında yapılan hemen her şeyi yapmak mümkündür. Geliştirmek amacıyla kesme sinyallerinin gönderilmesi ve yakalanması programa eklenebilir.

Fonksiyonların temel kullanımı yukarıdaki gibidir. AVR programlama üzerine çalışanlar için oldukça tanıdık gelen ifadeler istediğimiz bir porta doğrudan değer vermemize imkan vermektedir. Bu ifade DDRD portunun tüm ayaklarını çıkış olarak tanımlayıp HIGH çıkışı elde etmemizi sağlamaktadır.

Uygulamanın son kısmında ise yanıp sönen led uygulaması gerçekleştirilmektedir.

Burada 200ms aralıkla D portunun 3. ayağına bağlı LED yakılıp söndürülmektedir. Bu fonksiyonlarla doğrudan bit erişimi yapılmış olup portun diğer ayakları etkilenmemektedir.

Bu uygulama sadece led yakıp söndürme veya dijital giriş çıkış için kullanılmayıp zamanlayıcılar, karşılaştırıcılar, ADC vb. çevre birimlerini kontrol için de kullanılabilir. Ayrıca burada bütün programlama dillerindeki temel özellikler kullanıldığı için (bit bazlı işlemler, bayt değerler üzerinde işlem) seri port özelliği olan her geliştirme ortamında bunu uygulama imkanımız teorik olarak vardır.

--

--