Bilgisayar Bilimleri -17- Assembly Dilinin Temelleri

Daha önceki başlıkta Assembly dilinin mantığını anlatsak da Assembly diline ait özellikleri ve dilin temel parçalarını anlatmadan bu dilin tam olarak anlamış sayılmazsınız. Mühendislik fakültelerinde Z80 veya 8086 (x86) Assembly teorik olarak anlatılsa da öğrenciler neden yıllar önce kullanılmış olan mikroişlemcilerin hala öğretildiğini sorgulamaktadır. Bunun pratikte kullanım alanının kısıtlı olması bu bilgiyi “doğrudan” pratiğe dökme imkanınızı elinizden alacaktır. O yüzden ben doğrudan uygulayabileceğiniz bir dil olan AVR Assembly dili üzerinden Assembly dilini kısaca anlatacağım. AVR Assembly öğrenerek kendi mikrodenetleyici projelerinizi geliştirebilir, bitirme projenizi yapabilir ve hatta iş yapabilirsiniz. Biz her zaman olduğu gibi sitemizde yazdığımız bilgilerin gerekli ve uygulanabilir olmasına dikkat etmekteyiz. Sırf kitapta yazıyor diye hiç kullanmayacağımız ve lazım olmayacak bilgilerle siteyi doldurmuyoruz. Daha öncesinde AVR Assembly’i anlattığımız kısa bir yazı dizisi hazırlamıştık. İlgilenenler buradan okuyabilir.

Assembly diline baktığımızda temelinde makine dilinden çok da farklı olmadığını her komutun ayrı bir satırda ve makine diline benzer bir şekilde yazıldığını görürüz. Örnek bir söz dizimi verirsek bu şöyle olabilir.

Komut  Operand1 Operand2

Komut  Operand1

Komut 

Bu üç farklı söz dizimi üç farklı komut tipi için kullanılmaktadır. Bu komut tipleri ise Assembly dilinin değil makinenin özelliğinden dolayı böyledir. Örneğin iki sayıyı toplayacak bir komut için iki değer ve toplama komutuna ihtiyaç duyulur. Mikroişlemciyi uyku moduna sokmak için tek bir komut yeterlidir. Bu tamamen komut tarzına bağlıdır. Şimdi bu komut tiplerinin her biri için ayrı bir örnek verelim.

Komut Adres Veri -> ADD R1 0x55 

Komut Adres -> INC R1 

Komut -> SLEEP 

Burada daha önce bahsettiğimiz üzere ADD toplama işlemini yapan komuttu. Biz toplama işlemini yapabilmek için iki farklı değere ihtiyaç duyarız. Bu durumda iki adet operanda ihtiyaç duyulmaktadır. Operand dediğimiz sabit bir veri olabileceği gibi adres de olabilir. Neyin adres olarak neyin veri olarak sayılacağı ise yine komutun yapısından ileri gelir. Örneğin ADD R1 0x55 dediğimizde R1 adresindeki veri hücresine 0x55 değerini ekle demek isteriz. R1 adresi ise aslında 0x01 adresine eşit olup R1 diye sembolleştirilmiştir. Bu sembolleştirme bizi sayısal verilerden uzak tutmakta ve programı daha kolay yazdırmaktadır.

Assembly dilinden makine diline çevirim işi de aynı çeviri programlarında gibi olmaktadır. Örneğin ADD komutunun makine dilindeki karşılığı 0110 ise çevirici program ADD gördüğü yere 0110 değerini yapıştırmaktadır. Geriye kalan operandların konumu ve diğer parametreler opcode (işlem kodu) içerisinde belli bir düzene göre yer aldığından çevirici program değerleri bölüp parçalayıp ilgili yerlere yapıştırmaktadır. Bu programlamanın en derin seviyesi olsa da birazdan bunun nasıl işlediğini açıklayacağız.

Bizim yazdığımız kodu program haline getiren çeşitli yazılımlar vardır. Yüksek seviyede yazılan dilleri makine koduna çeviren programa derleyici (compiler), yüksek seviyede yazılan program kodunu derlemeden çalıştıran programa yorumlayıcı (interpreter), Assembly dilinde yazılan programı makine diline çeviren programa ise çevirici program (Assembler) adı verilir. Diğer program çeşitlerini yüksek seviye dilleri anlattığımız sırada göreceğiz.

Assembly dilinin temel yapısı sadece komutlar ve operandlardan ibaret değildir. Assembly dili öncelikle komutlar bizim daha rahat anlayacağımız kısaltmalara çevrilerek meydana getirilse de sonrasında makine dilinde programlamada görülen eksiklikler dil özelliği olarak bu dile katılmıştır. Bu sorunların başlıcası adresleme ve adres kaymasıydı. Assembly dilinde etiketler sayesinde bu adresleme sorunu ortadan kalkmıştır. Artık 500 numaralı adres yerine “DÖNGÜ, ALTPROGRAM” tarzında bir etiket koyulmakta ve program değiştirilse bile çevirici program o etiketin değerini otomatik olarak ayarlamaktadır. Böylelikle adres değerinin kaymasından dolayı programı baştan sona düzeltmemiz gerekmeyecektir. Şimdi Assembly dilinde etiketlerin nasıl kullanıldığını görelim.

Etiketler

Daha önce algoritmalarda anlattığımız gibi algoritmaların en basit hali sıralı algoritmalar olup program arası seçenek veya tekrarlamalardan yoksundur. Bunun için şartlı ve tekrar edici özelliklere sahip bir algoritma kurmamız gereklidir. Bilgisayar programları da bunları bolca kullanmaktadır ve mimariler buna göre tasarlanmaktadır. Yani hem şart hem de tekrarlama işlerini yerine getirecek komutlar makine dilinde mevcuttur. Tekrarlama işlemleri için hafızadaki belli bir bellek adresine program akışının atlaması gereklidir. Örneğin şöyle bir döngü yazdığımızda programın atlayacağı adresi belirtmemiz gerekir.

500 Komut1

501 Komut2

502 Komut3

503 ATLA 500 

Burada son komutta program hafızasının 500 numaralı adresine program akışının atlaması konusunda talimat verdik. AVR Assembly dilinde bu talimatın karşılığı İngilizce “Jump” kelimesinin kısaltması olan “JMP” komutudur. Bunun gibi pek çok dallanma ve atlama komutları mevcut olsa da en basitini size anlatıyoruz. Bizim burada program adresini belirtmemizde bir sakınca yoktur. Ama ne zaman program üzerinde değişiklik yapmak istesek adreste kayma olacaktır. Şimdi üst tarafa örneğin 300-400 adresleri arasına 2 adet yeni komut eklediğimizi varsayalım. Yeni program şu şekilde olacaktır.

502 Komut1

503 Komut2

504 Komut3

505 ATLA 500 

Biz 1. komuta atlamasını isterken bu 2 sıra gerideki komuta atlayacaktır. Çünkü bizim yukarıya ilave ettiğimiz iki komut sırayı bozdu program sırası boyunca “bütün” atlama komutlarının adreslerini değiştirdi. O yüzden baştan itibaren düzenlememiz gerekecektir. Şimdi ise aynı durumu etiketler vasıtasıyla yapalım.

DONGU : Komut1
                    Komut2
                    Komut3

                    JMP DONGU 

Burada herhangi bir adres değeri yok sadece program akışına koyduğumuz etiket var. Böylelikle araya yazdığımız veya sildiğimiz kodlar hiçbir şekilde program akışını bozmayacak tekrarlayıcı ve şart işlemlerini yerine getirdiğimiz atlama ve dallanma komutları rahatlıkla çalışacaktır. Assembly makine diline karşı en büyük avantajlarından biri işte bu etiketlerdir.

Günümüzde yüksek seviye programlama dillerinin bazılarında etiketler halen bulunsa da kullanmayı hiç tavsiye etmemekteler. Çünkü programcılık gelişince bu etiketleri kullanmak bile sıkıntılar doğurdu. O yüzden fonksiyonlar ve daha ilerisinde nesne-sınıf ilişkisi ile programlar yazılmaktadır.

Yorumlar

Makine dilinde program yazılırken sadece makinenin anlayacağı sayısal ifadeler kullanılır. Biz programın kenarına not almak istediğimizde ancak bunu kağıt üzerinde yapabiliriz. Kaynak kodun kendisinde herhangi bir açıklama veya yorum yapmamıza izin verilmez çünkü bunu görmezden gelecek aracı bir program yoktur. Assembler programları kaynak kodu okuyup makine diline çeviren bir aracı oldukları için bu programların görmezden geleceği kısımları ekleme şansımız olur. Buna yorum adı verilir. Yorumlar programın belli noktalarını veya programın tamamını anlamamız ve başkalarının rahatça okuyup anlaması noktasında büyük bir katkıda bulunmaktadır. Assembly dilinde yorumlar çevirici programların tipine göre değişiklik göstermektedir. Genelde bir satırda noktalı virgülden itibaren yazdığımız kısımlar yorum olarak değerlendirilmekte ve programa dahil edilmemektedir.

// Bu bir yorumdur

ADD R1 R2 ; İki sayıyı topladık 

SLEEP /* Uykuya Geç */ 

Assembler Direktifleri

Assembly dilinin en büyük özelliklerinden biri de makine dilinde olmayan bir tek çevirici programın anlayacağı bazı komutları bulundurmasıdır. Bunlar “.” işareti ile normal programdan ayrılmakta ve sadece çevirici sürecinde kullanılmaktadır. Biz Assembly dilinde bazı sabit verileri adlandırabilir ve bunları değişken gibi kullanabiliriz. Bunun için .EQU direktifi oldukça kullanışlı olmaktadır.

.EQU SAYI = 0x50 

Yukarıda görüldüğü gibi artık SAYI adında bir tanımlama oluşturduk ve bu SAYI ifadesini Assembly programının içerisinde kullanabiliriz. Makine dilinin en büyük dezavantajlarından biri sadece sayılardan ibaret olması olduğundan Assembly dilinin bu yönde büyük bir artısı vardır. Ayrıca veri tiplerini yüksek seviye programlama dilleri kadar olmasa da çevirici programlara göre değişen miktarda kullanma şansımız vardır. Örneğin karakter verisi olacaksa bunu tek tırnak arasında ‘a’ şeklinde olduğu gibi gösterebiliriz veya onaltılık sayı olacaksa 0x öneki veya $ son eki ile 0x2A örneğinde olduğu gibi kullanabiliriz.

Bunun gibi değişik direktifler de bulunmaktadır. Bunlar sadece Assembly dilinin mantığını öğrenmek isteyenler için ayrıntıya kaçacağından burada bırakıyoruz. Şimdi örnek bir Assembly programını inceleyelim. Bu program AVR mikrodenetleyicilerde dahili EEPROM belleğe veri yazmak için kullanılmaktadır.

Burada öncelikle EEPROM_write: adında bir etiket ile alt program oluşturulmuştur. EEPROM yazma işlemini yapmak istiyorsak öncelikle bu alt programa program akışını atlatmamız gerekecektir. Daha öncesinde böl ve yönet yöntemini size söylemiştik. Programı daha rahat ele alabilmek için alt programlara bölmek etkili bir yöntemdir.

sbic eecr,eepe  Bu komut iki operand ve bir mnemonikten meydana gelmektedir. Her komutun başında bir mikroişlemci komutu olduğunu unutmayalım. Bu komutun yaptığı tek bir iş vardır ve o iş de “Skip if Bit in I/O Register is Cleared” işidir. Bunu Türkçe’ye çevirmek istersek ilgili giriş ve çıkış yazmacında belirtilen bit 0 ise atla şeklinde bir çeviri elde ederiz. Bu komutun algoritmalardaki şart ifadesini yani EĞER ifadesini karşıladığını rahatça görebiliriz. Burada makine koduyla biz “EĞER EECR yazmacının EEPE biti sıfır ise alttaki komutu GEÇ” ifadesini yapmaktayız. Makine dilinin bizim algoritma kurduğumuz dile benzer olmaması yüzünden biz algoritmalarımızı makine diline uydurmak zorundayız. O yüzden şart ifadelerini SBIC komutu ile makine diline uyarlayabiliriz. Burada iki operand olduğunu görüyoruz. Bunlardan biri yazmaç veya yazmacın adresi öteki ise bitin adı veya bitin adresidir. Burada sayı belirtebileceğimiz gibi yukarıda bahsettiğimiz gibi .EQU ile tanımlanan ön tanımlı değerleri de girebiliriz. İlk operand EECR yazmacının adresini işaret etmekte ikinci operand ise bit numarasını vermektedir. AVR Instruction Set Manual adlı komut kümesi kılavuzunu açtığımızda bu komutun şu şekilde olduğunu görebiliriz.

Burada Opcode adı verilen 16 bitlik program satırının bitlerinin dizilimini görmekteyiz. Burada SBIC A, b şeklinde bir söz dizimi mevcuttu. Yani A dediğimiz yazmaç adresi olarak birinci işleneç, b dediğimiz ise bit sayısını ifade eden ikinci işleneçti. Başta gördüğümüz 10011001 değeri ise SBIC komutunun kendisidir. Yani çevirici program nerede SBIC görürse oraya 10011001 değerini yapıştırır. Burada AAAAA 5 bitlik yani 32 adet değere sahip olabilir. Bu komut da alt taraftaki 32 adet giriş ve çıkış yazmaçları üzerinde kullanılmaktadır. bbb adı verilen ikinci operand ise 000, 001, 010, 011… diye devam eden değerlere sahip olur. Toplamda 8 bitlik kapasiteye sahip olan ikinci operand ise işaret edilen 8 bitlik yazmacın bit numarasını belirtmektedir. Bütün komutlara baktığımızda 16 bitlik program satırına böyle işlendiğini görebiliriz. Biz makine dilinde program yazmak isteseydik bu bir ve sıfırların yerlerini bile ezberlememiz gerekecekti. Ama Assembly dilinde çok lazım olmamaktadır.

Bu komut EECR yazmacında yer alan EEPE durum bitini denetler. Bu durum biti yazma devam ederse bir durumunda olacağı için komut alttaki komutu çalıştırır. Şimdi alttaki komuta bakalım.

rjmp EEPROM_write Bu komut yukarıda belirtilen EEPROM_write etiketine atlamayı sağlayan bir atlama komutudur. Yani burada tekrarlayan bir iş yaptık ve bu atlama ile yukarıdaki SBIC komutu çalıştırılmaya devam edecek. Bu döngüden çıkmanın tek yolu EEPROM’un yazmaya hazır olduğunu gösteren EEPE bitinin sıfır olmasıdır. Yani program yazma işlemi bitene kadar mikroişlemciyi meşgul edecek ve hazır olduğunda alt programa devam edecektir. Burada JMP yerine RJMP diye bir komut kullanılması tamamen performans odaklı olmasından dolayıdır. RJMP daha az adres kaplayan bir komut olup yakın mesafeler için atlama işini yerine getirmektedir.

EECR yazmacındaki EEPE bitinin sıfır olduğunu varsayalım. Bu durumda program yukarıdaki sonsuz döngüyü tamamlamış olacak ve EEPROM yazma işlemini yerine getirecek. Bunun için öncelikle EEPROM yazmaçlarındaki adres ve veri kısımlarının doldurulması gerekli. Nereye neyi yazacağımızı belirlemedikçe yazma işlemini yapmanın bir anlamı yoktur.

out eearh,r18
out eearl,r17

EEPROM yazmaçları EEARH ve EEARL olmak üzere iki adres yazmacından oluşmaktadır. Bunlar alt kısım ve üst kısım olarak bir bütünün iki parçasıdır. R18 ve R17 yazmaçlarına daha önce yazdığımız değeri OUT komutu ile EEARH ve EEARL yazmaçlarına yazıyoruz. OUT komutu giriş ve çıkış yazmaçları için özelleştirilmiş veri yazma komutudur.

Yukarıda iki out komutu ile iki parçalı EEPROM adres yazmacına veriyi nereye yazacağımızı kararlaştırdık. Ama daha hangi veriyi yazacağımızı belirlemedik. Bunun için yine aynı komutu kullanarak EEPROM veri yazmacına yazacağımız veriyi yazıyoruz.

out eedr,r16

EEPROM mikroişlemcinin ikinci hafıza birimidir. Burada RAM bellekte bulunan yazmaçlara yazma işlemini yapsak da bu yazmaçlar doğrudan dış birim olan EEPROM biriminin yazmaçlarına bağlıdır. Yani burada yazmaçlara yazarak aslında dışarıda bağlı olan bir aygıtın yazmacına veri yazmaktayız. Bu EEPROM’a veri yazmak için sadece adres ve veri bilgisini yazmamız yeterli olmaz. Bunu kontrol edeceğimiz bir kısım da olmalıdır. Bu da EECR adı verilen EEPROM kontrol yazmacının ilgili bitleri ile olmaktadır.

sbi eecr,eempe

SBI komutu (Set Bit) komutunun kısaltmasıdır. Bunun anlamı ilgili yazmaçtaki belirtilen biti (1) yap demektir. Bunun için komutun ardından yazmaç ve bit adını belirtmek zorundayız. Burada SBI komutu ile EEMPE biti (1) konumuna getirilmektedir. EEMPE biti kazara yazma işlemleri olmasın diye iki anahtarlı bir güvenlik mekanizmasının birinci anahtarıdır. Bunu mikrodenetleyicinin teknik kitapçığında okumaktayız.

sbi eecr,eepe

Burada ise EEPE biti bir yapılmaktadır. Bu da ikinci anahtar olup asıl EEPROM yazma işlemini yerine getirir. Altprogram içerisinde güvenlik kontrolünü yaptık, adres verisini yükledik ve veriyi de yükleyerek ilgili kontrol bitlerini ayarlayıp yazma işlemini yerine getirdik. Artık burada bir işimiz kalmadığına göre nereden geldiysek oraya dönmemiz gereklidir. Bunun içinde en sonda yer alan RET komutu kullanılmaktadır. RET komutunun Return kelimesinin kısaltması olduğunu söyleyelim.

Buraya kadar Assembly dilinin temellerini ve önemli noktalarını anlattık. Aynı zamanda örnek bir Assembly kodunu kafanızda hiçbir soru işareti kalmayacak şekilde derinlemesine inceleyip anlattık. Son kısımda Opcode veya söz dizimi açıklaması yapmamamız gereksiz tekrar olmaması içindi. Eğer sözdizimlerini ve opcode formatını merak ediyorsanız “AVR Instruction Set Manual” adlı komut kılavuzuna bakabilirsiniz.

Bizi Facebook grubumuzda takip etmeyi unutmayın. Bilgili ve öğrenmeye hevesli bir topluluk oluşturmak istiyoruz.

https://www.facebook.com/groups/1233336523490761/

 

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.