Merhabalar, bu yazımda sizlere ARM assembly diline giriş niteliğinde olan uygulamadan bahsedeceğim.

Globalleşen dünyada gömülü sistemlerde yüksek seviyeli dillerin kullanımına yönelik bir eğilim varken nerden çıktı şimdi bu diyenler olabilir.

C dilinin bile artık insanlara zor gelmesi, insanların satırlarca ayar (initial) kodları, kütüphaneler yazmak yerine basit arayüzleri, otomatik kod üreten araçları, hazır kütüphaneleri vs. kullanmalarına bir tepki olarak ve ayrıca merak ettiğim için bu konuda araştırma yapma gereği duydum.

Bu nedenle gerek kendi merakımı gidermek gerekse başkalarınında işine yaraması için basit bir uygulama yaptım.  ARM için assembly öğrenmenin ne faydası var diyenler de olabilir.

Hangi denetleyici olursa olsun hepsinin bir assembly komut seti vardır. Eğer kodlarımızı binary şeklinde yazmıyorsak assembly en temel programlama dilidir. Bu nedenle kullanılan mikrdenetleyicinin daha iyi anlaşılması için öncelikle temel anlamda assembly uygulamalar geliştirmek önemlidir. Bu sayede kullandığınız denetleyiciye daha iyi hükmedebilirsiniz. Hiç bir işe yaramazsa da beyin jimnastiği yapmış olursunuz.

ARM temelli mikrodenetleyiciler için assembly diline kod yazmak eminim bir çoğumuzun aklından geçmiştir. En azından benim aklımdan geçti. Konu ile ilgili internette yaptığım araştırmalarda fazla kaynağın olmadığı pek kimsenin uğraşmadığını gördüm. Bir şeyler yapmam gerektiğini düşünerekten fırsat buldukça kolları sıvayıp bir şeyler yapmaya çalıştım. Yine söylenmeyeni söylemeye, yazılmayanı yazmaya devam ediyoruz…

Hangi ARM?

Yazının başında ARM dedik ama ARM sadece firmanın ismi. Firmanın hangi ürününü kullanacağımdan bahsetmedim. Bilindiği gibi küçük boyutlu gömülü sistemlerde ARM olarak çoğunlukla Cortex-M serisi işlemciler kullanılır. Ben kolaylık olması açısından Cortex-M serisi işlemcilerden en basit olan Cortex-M0 işlemcisi için assembly kod yazmayı tercih ettim.

ARM® Cortex®-M0

ARM Cortex-M0 ARM firmasının ürettiği mevcut en küçük ARM işlemcisidir. 8/16- bit denetleyicilere alternatif olarak 8-bit fiyatına 32-bit denetleyici sloganıyla anılmaktadır. 8/16-bit denetleyicilerden 32-bit denetleyicilere geçmek için ideal bir başlangıç noktasıdır. Bir çok firmanın Cortex-M0 çekirdekli denetleyicisi ve uygun fiyatlı geliştirme kartları bulunmaktadır. Kolaylıkla bir geliştirme kartı edinilip uygulama geliştirilmeye başlanabilir.

ARM® Cortex®-M0 Assembly Komut Seti

Genel olarak ARM işlemcilerde ARM, Thumb, Thumb-2 gibi komut setleri mevcuttur. ARM komut seti orijinal komut setidir. İlerleyen zamanlarda ARM firması yaklaşık %30 daha az yer kaplayan buna karşın yaklaşık %20 daha az performanslı 16 bit Thumb komut setini çıkardı. Yine ilerleyen zamanlarda ARM firması boş durmayıp çalışmalar yaparak Thumb komut setini de kapsayan 32 bit Thumb-2 komut setini çıkardı. Thumb-2 komut seti ARM komut setine benzer performans ile yaklaşık %26 daha az kod üretir. ARM Cortex-M serisi işlemciler bu komut setlerinden bir kaçını destekleyecek şekilde üretilirler. Program çalışması anında diğer komut setine geçilebilir. Kapsamlı bir konu olduğu için burada detaya girmiyorum. Konu ile ilgili detaylı bilgiye buradan ulaşabilirsiniz.

Gel gelelim bizim Cortex-M0 işlemcimize, Cortex-M0 basit bir işlemci olduğu için 16-bit Thumb komut seti ile bazı 32 bit Thumb-2 komutlarını desteklemektedir. Yani toplamda 56 adet komut mevcuttur. Komutların yazılışları aşağıda görülebilir.

Şekil-2

Şekil-2’de Cortex-M0 tarafından desteklenen tüm komutlar görülmektedir. Komutlar kısaltma isimleri itibari ile diğer işlemcilerin komutları ile benzerlik göstermektedir. Komutların detaylı açıklamalarına buradan ulaşabilirsiniz. Bu yazıda tüm komutları açıklamak yerine uygulamada yeri geldikçe kullanılan komutlardan bahsedeceğim.

KEIL ARM Assembler

İnternet üzerinde yaptığım araştırmalarda ARM için assembly kod yazıp derleyebilmek için ARM GCC derleyici ile KEIL ARM Assembler olduğunu gördüm. Keil’e aşina olduğum için GCC ile uğraşmayıp direk olarak Keil ile çalışmaya başladım. Keil üzerinde assembly kod yazmanın C ile yazmaktan bir farkı yoktur. Tek fark assembly dosyaları .s uzantılı olmalıdır. Benzer şekilde proje oluşturup assembly kodlarınızı yazabilirsiniz. Keil ARM Assembler ilgili detaylı bilgiye buradan ulaşabilirsiniz. Uygulamada yeri geldikçe gerekli olan kısımlardan bahsedeceğim.

STM32F051R8

İyi güzel Cortex-M0 ama hangi denetleyici üzerinde koşacak bu assembly kodlar diyebilirsiniz. Bilindiği gibi Cortex-M0 işlemci çekirdeğinin adıdır. Denetleyici üreten firmalar bu çekirdeği alıp kendi çevre birimlerini, gerekli donanımları vs. tasarlayıp kendi denetleyicilerini üretirler. Bu kapsamda ST, NXP, TI, ATMEL gibi bir çok firmanın ARM işlemcili denetleyicileri bulunmaktadır. Bu uygulamada geliştirme kartı olarak, hem elimde bulunduğu için hemde yapısı basit gösterişsiz olduğu için üzerinde STM32F051R8 denetleyicisi bulunan STM32F0 Discovery kartını kullandım. Sade bir yapısı olan kart üzerinde 2 adet kullanıcı LED’i ve bir adette push buton bulunmaktadır. Uygulamada kart üzerindeki LED’ler kullanılmıştır.

LED Blink Uygulaması

İlk uygulama olarak giriş seviyesinde bir uygulama olması açısından basit LED yakıp söndürme uygulaması seçtim. LED yakıp söndürme uygulaması ile protlara erişim, ilk ayarlamalar gibi bir çok işlem yapılmaktadır. Tabi ki assembly kod yazmanın yanı sıra kullanılan denetleyici hakkında bilgi sahibi olunmalıdır. Gerek portların gerekse kullanılan çevre birimlerinin değişken adresleri kullanma kılavuzundan öğrenilmelidir.

LED blink uygulamasına ait kodlar aşağıdaki gibidir.

;****************************************************************************
; Uygulama : LED blink	                		       	            *
; Yazan	  : Erhan YILMAZ				                    *
; E-posta : erhanyilmaz.ytu@gmail.com		                            *
; Tarih	  : 13-06-2015				                            *
; Sürüm	  : 1.0					                            *
; Açıklama: Cortex M0 işlemcisine sahip STM32F051R8 denetleyicisi	    *
;           için assembly dilinde yazılmış LED yakıp söndürme uygulamasıdır.*
; 	    Geliştirme kartı olarak STm32F0 Discovery kullanılmıştır.	    *
;****************************************************************************
 
Stack_Size      EQU     0x00000400		;Stack boyutu tanımlanıyor.(1kb)
 
; Kullanılan kaydedicilerin adresleri tanımlanıyor.
RCC_BASE		EQU		0x40021000
RCC_AHBENR		EQU		RCC_BASE + 0x14
GPIOC_BASE		EQU		0x48000800
GPIOC_MODER		EQU		GPIOC_BASE + 0x0
GPIOC_OTYPER	        EQU		GPIOC_BASE + 0x4
GPIOC_OSPEEDR	        EQU		GPIOC_BASE + 0x8
GPIOC_PUPDR		EQU		GPIOC_BASE + 0xc
GPIOC_IDR		EQU		GPIOC_BASE + 0x10
GPIOC_ODR		EQU		GPIOC_BASE + 0x14
GPIOC_BSRR		EQU		GPIOC_BASE + 0x18
GPIOC_LCKR		EQU		GPIOC_BASE + 0x1C
GPIOC_AFRL		EQU		GPIOC_BASE + 0x20
GPIOC_AFRH		EQU		GPIOC_BASE + 0x24
GPIOC_BRR		EQU		GPIOC_BASE + 0x28
; Programda kullanılan tanımlamalar.
BSRR_VAL                EQU		0x0300
LEDDELAY		EQU		800000
 
; Stack için yer ayrılıyor.
                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
 
                PRESERVE8	; Linker bildirimi
                THUMB		; Thumb komut seti kullanılıyor.
 
; Vector Table Mapped to Address 0 at Reset
; Vektör tablosu tanımlanıyor.
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors		; Linker bildirimi
; İşlemci tarafından kullanılan kesmeler(exceptions) handler'lar ve stack top adresi tanımlanıyor.
__Vectors       DCD     __initial_sp                   ; Top of Stack
                DCD     Reset_Handler                  ; Reset Handler
                DCD     NMI_Handler                    ; NMI Handler
                DCD     HardFault_Handler              ; Hard Fault Handler
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     SVC_Handler                    ; SVCall Handler
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     PendSV_Handler                 ; PendSV Handler
                DCD     SysTick_Handler                ; SysTick Handler
; External Interrupts
; Denetleyici tarfından kullanılan kesmeler(exceptions) için handler'lar tanımlanıyor.
                DCD     WWDG_IRQHandler                ; Window Watchdog
                DCD     PVD_IRQHandler                 ; PVD through EXTI Line detect
                DCD     RTC_IRQHandler                 ; RTC through EXTI Line
                DCD     FLASH_IRQHandler               ; FLASH
                DCD     RCC_IRQHandler                 ; RCC
                DCD     EXTI0_1_IRQHandler             ; EXTI Line 0 and 1
                DCD     EXTI2_3_IRQHandler             ; EXTI Line 2 and 3
                DCD     EXTI4_15_IRQHandler            ; EXTI Line 4 to 15
                DCD     TS_IRQHandler                  ; TS
                DCD     DMA1_Channel1_IRQHandler       ; DMA1 Channel 1
                DCD     DMA1_Channel2_3_IRQHandler     ; DMA1 Channel 2 and Channel 3
                DCD     DMA1_Channel4_5_IRQHandler     ; DMA1 Channel 4 and Channel 5
                DCD     ADC1_COMP_IRQHandler           ; ADC1, COMP1 and COMP2 
                DCD     TIM1_BRK_UP_TRG_COM_IRQHandler ; TIM1 Break, Update, Trigger and Commutation
                DCD     TIM1_CC_IRQHandler             ; TIM1 Capture Compare
                DCD     TIM2_IRQHandler                ; TIM2
                DCD     TIM3_IRQHandler                ; TIM3
                DCD     TIM6_DAC_IRQHandler            ; TIM6 and DAC
                DCD     0                              ; Reserved
                DCD     TIM14_IRQHandler               ; TIM14
                DCD     TIM15_IRQHandler               ; TIM15
                DCD     TIM16_IRQHandler               ; TIM16
                DCD     TIM17_IRQHandler               ; TIM17
                DCD     I2C1_IRQHandler                ; I2C1
                DCD     I2C2_IRQHandler                ; I2C2
                DCD     SPI1_IRQHandler                ; SPI1
                DCD     SPI2_IRQHandler                ; SPI2
                DCD     USART1_IRQHandler              ; USART1
                DCD     USART2_IRQHandler              ; USART2
                DCD     0                              ; Reserved
                DCD     CEC_IRQHandler                 ; CEC
                DCD     0                              ; Reserved
 
 
                AREA     MAIN, CODE, READONLY
 
; Reset handler routine
; Reset sonrası başlangıç adresi
Reset_Handler    
                EXPORT  Reset_Handler                 [WEAK]	; Linker bildirimi
start			
; GPIOC clock aktif
	LDR R6, =RCC_AHBENR	; RCC_AHBENR değerini R6'ya yükle
	LDR R0, =0x00080000	; 0x00080000 değerini R0'a yükle
	str R0, [R6]		; RCC_AHBENR harici değişkenine 0x00080000 değerini yükle
; GPIOC.8, GPIOC.9 çıkış
	LDR R6, =GPIOC_MODER	; GPIOC_MODER değerini R6'ya yükle
	LDR R0, =0x00050000	; 0x00050000 değerini R0'a yükle
	str R0, [R6]		; GPIOC_MODER harici değişkenine 0x00050000 değerini yükle
loop				
; GPIOC.8, GPIOC.9 çıkışlarını sıfırla
	LDR R6, =GPIOC_BRR	; GPIOC_BRR değerini R6'ya yükle
	LDR R0, =BSRR_VAL	; BSRR_VAL değerini R0'a yükle
	str R0, [R6]		; GPIOC_BRR harici değişkenine BSRR_VAL değerini yükle
; Bir süre bekle
	ldr R1, =LEDDELAY	; LEDDELAY değerini R1'e yükle
delay1
	subs r1, #1		; R1=R1-1
	bne delay1		; sonuç 0 değilse delay1'e git
 
; GPIOC.8, GPIOC.9 çıkışlarını set et				
	LDR R6, =GPIOC_BSRR	; GPIOC_BSRR değerini R6'ya yükle
	LDR R0, =BSRR_VAL	; BSRR_VAL değerini R0'a yükle
	str R0, [R6]		; GPIOC_BSRR harici değişkenine BSRR_VAL değerini yükle
; Bir süre bekle			
        ldr R1, =LEDDELAY	; LEDDELAY değerini R1'e yükl
delay2
	subs r1, #1	        ; R1=R1-1
	bne delay2		; sonuç 0 değilse delay2'ye git
;Sonsuz döngü				
	B loop			; loop'a git
 
; Dummy Exception Handlers (infinite loops which can be modified)
; Kesme(exceptions) vektörleri için tanımlanmış iç boş sonsuz döngüye giren handler'lar
NMI_Handler     
        B .       
HardFault_Handler
        B .
SVC_Handler     
        B .
PendSV_Handler  
        B .
SysTick_Handler 
        B .
WWDG_IRQHandler
PVD_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_1_IRQHandler
EXTI2_3_IRQHandler
EXTI4_15_IRQHandler
TS_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_3_IRQHandler
DMA1_Channel4_5_IRQHandler
ADC1_COMP_IRQHandler 
TIM1_BRK_UP_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM6_DAC_IRQHandler
TIM14_IRQHandler
TIM15_IRQHandler
TIM16_IRQHandler
TIM17_IRQHandler
I2C1_IRQHandler
I2C2_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
CEC_IRQHandler   
        B .
	ALIGN	; Adres hizalama Assembler bildirimi
	END	; Program sonu assmbler bildirimi

 

Uygulamanın Çalışması

Yukarıda ki kodu denetleyicimize yükleyince GPIOC.8 ve 9 pinlerine bağlı ledlerin yanıp sönmeye başladığını görebilirsiniz. Kodun temel yapısından bahsedecek olursak; Kodun başlangıcında değişken adreslerine, kullanılan değerlere vs. EQU bildirimi ile isim verdik. Bu sayede adresleri hatırlamak zorunda kalmayız. Örneğin 0x40021000 adresi denetleyici içerisinde bulunan RCC(Reset and Clock Control) biriminin değişkenlerinin bulunduğu başlangıç adresidir. İlgili değişkenler bu adresten itibaren başladığı için RCC_BASE EQU 0x40021000 ifadesi ile RCC birimini temel adresi tanımlanmıştır. Benzer şekilde diğer kullanılan değerlerde uygun EQU bildirimleri ile tanımlanmıştır.

Diğer bir nokta ise AREA derleyici bildirimidir. AREA bildirimi ile hafızadan belli bir bölge ayrılır. Varsayılan olarak programda Stack ve Program kodları için alanların ayrılması gerekir. Bu nedenle programda iki adet AREA ifadesi kullanılmıştır. Bilinmesi gereken önemli nokta stack için ayrılan okunup yazılabilir(READWRITE) program için ayrılan alan sadece okunabilir(READONLY) alandır.

Stack alanı işlemcinin push ve pop komutlarında veri saklayıp, okuduğu alandır. Stack arka planda derleyiciler tarafından sıkça kullanılır. Bu nedenle programın başında stack için bir alan ayırmak ve adres belirtmek gerekir. Bu uygulamada varsayılan olarak stack için RAM belleğin en sonunda 1kb alan ayrılmıştır.

İşlemci ilk çalıştığında yada reset anından sonra ilk olarak hafızanın 0 adresine gelip stack adresini set eder ve reset vektörü ile belirtilen adrese gider. Buradan itibaren kodları çalıştırmaya başlar. Görüldüğü gibi uygulama kodları reset vektörünün gösterdi Rest_Handler etiketinden itibaren başlamaktadır. Görüldüğü gibi kodlarda bir çok tanımlı vektör değerleri bulunmaktadır. Bunların tamamı vektör tablosunu oluşturmaktadır. Vektör tablosunda ilgili kesmenin yada exception’ın oluşması durumunda gidilecek adresler belirtilmelidir.  Uygulamada sadece Reset_Handler kullanılmıştır. diğer vektörler kullanılmamasına rağmen varsayılan olarak tanımlanmalıdır.

Örneğin programda yanlışlıkla bir kesmeye izin verdiniz yada Hard fault oluşturacak bir kod yazdınız. Bu durumda işlemci ilgili vektörün gösterdiği adrese gidip yazılan kodları icra edecektir. Bu uygulamada reset dışında diğer vektörlerin gösterdiği adreste program sonsuz döngüye girer. Olası bir hata yada yanlış kesmenin açılması durumunda program durdurulur. Bu sayede debug yaparken programın hatalarını daha iyi gözleyebilirsiniz.

; GPIOC clock aktif
LDR R6, =RCC_AHBENR  ; RCC_AHBENR değerini R6'ya yükle 
LDR R0, =0x00080000  ; 0x00080000 değerini R0'a yükle
str R0, [R6]         ;RCC_AHBENR harici değişkenine 0x00080000 değerini yükle 

Yukarıda kod parçasında 3 adımda RCC_AHBENR değişkenine yazma işlemi yapılarak GPIOC biriminin saat sinyali aktif edilmiştir. Bu kod yapısı ile denetleyicinin her hangi bir harici değişkenine yazma işlemi yapabilirsiniz.

; GPIOC.8, GPIOC.9 çıkışlarını sıfırla
LDR R6, =GPIOC_BRR ; GPIOC_BRR değerini R6'ya yükle
LDR R0, =BSRR_VAL  ; BSRR_VAL değerini R0'a yükle
str R0, [R6]	   ; GPIOC_BRR harici değişkenine BSRR_VAL değerini yükle

Benzer şekilde yukarıdaki kod parçası ile GPIOC_BRR harici değişkenine BSRR_VAL değeri yazılmıştır. GPIOC_BRR değişkeni GPIOC portundaki bitleri resetlemek için kullanılan değişkendir. BSRR_VAL değeri ile GPIOC.8 ve GPIOC.9 bitleri sıfırlanmıştır. Kodlarda görülen GPIOC_BSRR değişkeni ise benzer şekilde GPIOC bitlerini set etmek için kullanılmıştır. Bu değişkenler denetleyici ile ilgili olduğu için tam anlamıyla kavramak için denetleyicinin kullanma kılavuzu incelenmelidir.

; Bir süre bekle
        ldr R1, =LEDDELAY ; LEDDELAY değerini R1'e yükle
delay1
	subs r1, #1	  ; R1=R1-1
	bne delay1	  ; sonuç 0 değilse delay1'e git

Son olarak koddaki önemli parça ise gecikme döngüsüdür. LED’lerin yanıp sönmesini görebilmek için araya gecikme döngüleri koymak gerekir. Yukarıdaki kod parçası ise gecikme sağlayabilirsiniz. Döngü başında LEDDELAY ile belirtilen değer R1 dahili değişkenine yüklenir ve her seferinde 1 azaltılır. Değer sıfıra ulaşana kadar işlem devam eder. Böylelikle gecikme sağlanmış olur. Döngünün tam olarak ne kadar sürdüğünü hesaplamak için döngü çevrimlerinin hesaplanması ve çevrim süresinin bilinmesi gerekir. Uygulamada LEDDELAY değerini ile belirtilen değer LED’lerin yanıp sönmesini görmek için yeterli bir değerdir.

İlk Ayarlamalar Nerede?

Uygulama kodlarında fark ettiyseniz denetleyicinin başlangıç ayarları, saat ayarları vs. ile ilgili bir kod yer almamaktadır. Bunun nedeni uygulamanın basit olması için tüm değerleri varsayılan değer olarak kullanmamdır. STM32F051R8 denetleyicisi başlangıçta herhangi bir ayar yapılmazsa ise içerisinde bulunan dahili 8-MHz RC osilatörü kullanır. Herhangi bir ayarı değiştirmek isterseniz gereken değişkene yazmanız yeterlidir. Değişkenlere yazmak için gerekli kod parçası uygulamada mevcuttur. Bunlar daha çok denetleyiciye özel konular olduğu için detaya girmeye gerek görmedim.

Şekil-3

Şekil-3’te uygulamanın çalışma görüntüsü görülmektedir. Böylelikle ARM assembly dilinde ilk uygulamamızı yapmış olduk. Uygulamaya ait kodlara proje halinde buradan ulaşabilirsiniz. Yazı ile ilgili eksik yerleri, görüş ve önerilerinizi bildirirseniz sevinirim. Bir sonraki yazıda görüşmek üzere inşaallah…