起航,第一个程序——还是LED灯
如同學基本語言一樣,helloworld是很多語言的第一個程序。在嵌入式開發中,點亮LED燈也是各種架構和開發板的第一個程序,其中很多東西是和單片機例如stm32是類似的,只是,現在我們沒有了庫函數,我們要自己完成一些東西。
先說啟動文件,st官方已結給我們做好了,但是jz2440開發板沒有統一的啟動文件,需要自己編寫,那么,基礎的arm匯編就得有所熟悉,在之后的學習中,遇到一個指令就學習一個。
(匯編)指令是CPU機器指令的助記符,經過編譯后會得到一串1、0組成的機器碼,可以由CPU讀取執行。 (匯編)偽指令本質上不是指令(只是和指令一起寫在代碼中),它是編譯器環境提供的,目的是用來指導編譯過程,經過編譯后偽指令最終不會生成機器碼。
ARM官方的ARM匯編風格:指令一般用大寫、Windows中IDE開發環境(如ADS、MDK等)常用。如: LDR R0, [R1] GNU風格的ARM匯編:指令一般用小寫字母、linux中常用。如:ldr r0, [r1]
ARM采用RISC架構,CPU本身不能直接讀取內存,而需要先將內存中內容加載入CPU中通用寄存器中才能被CPU處理。 ldr(load register)指令將內存內容加載入通用寄存器。 str(store register)指令將寄存器內容存入內存空間中。 ldr/str組合用來實現 ARM CPU和內存數據交換。
先看流水燈的一段啟動代碼:
1 @****************************************************************************** 2 @ File:crt0.S 3 @ 功能:通過它轉入C程序 4 @****************************************************************************** 5 6 .text 7 .global _start 8 _start: 9 ldr r0, =0x53000000 @ WATCHDOG寄存器地址 10 mov r1, #0x0 11 str r1, [r0] @ 寫入0,禁止WATCHDOG,否則CPU會不斷重啟 12 13 ldr sp, =1024*4 @ 設置堆棧,注意:不能大于4k, 因為現在可用的內存只有4K 14 @ nand flash中的代碼在復位后會移到內部ram中,此ram只有4K 15 bl main @ 調用C程序中的main函數 16 halt_loop: 17 b halt_loop預備知識:摘自 http://blog.csdn.net/qq506124204/article/details/7952966
mov?? r1, #0x53000000?? //立即數尋址方式?
mov?? r2, #0x0?
str?? r2, [r1]????????
立即數尋址方式,立即數要求以“#”作前綴,對于十六進制的數,還要求在#后面加上0x或者&。STR是比較重要的指令了,跟它對應的是LDR。 ARM指令集是加載/存儲型的,也就是說它只處理在寄存器中的數據。那么對于系統存儲器的訪問就經常用到STR和LDR了。STR是把寄存器上的數據傳輸 到指定地址的存儲器上。它的格式我個人認為很特殊:
STR(條件) 源寄存器,<存儲器地址>
比如 STR R0, [R1] ,意思是R0-> [R1],它把源寄存器寫在前面,跟MOV、LDR都相反。
LDR應該是非常常見了。LDR就是把數據從存儲器傳輸到寄存器上。而且有個偽指令也是LDR,因此我有個百思不得其解的問題。看這段代碼:
mov r1, #GPIO_CTL_BASE?
add?? r1, r1, #oGPIO_F?
ldr?? r2,=0x55aa?? // 0x55aa是個立即數啊,前面加個=干什么??
對于當中的ldr 那句,我就不明白了,如果你把=去掉,是不能通過編譯的。我查了一些資料,個人感覺知道了原因:這個=應該表示LDR不是ARM指令,而是偽指令。作為偽指令的時候,LDR的格式如下:
LDR 寄存器, =數字常量/Label
它的作用是把一個32位的地址或者常量調入寄存器。嗬嗬,那大家可能會問,
“MOV r2,#0x55aa”也可以啊。應該是這樣的。不過,LDR是偽指令啊,也就是說編譯時編譯器會處理它的。怎么處理的呢?——規則如下:如果該數字常量 在MOV指令范圍內,匯編器會把這個指令作為MOV。如果不在MOV范圍中,匯編器把該常量放在程序后面,用LDR來讀取,PC和該常量的偏移量不能超過 4KB。
然后說一下跳轉指令。ARM有兩種跳轉方式。
(1) mov pc <跳轉地址〉
這種向程序計數器PC直接寫跳轉地址,能在4GB連續空間內任意跳轉。
(2)通過 B BL BLX BX 可以完成在當前指令向前或者向后32MB的地址空間的跳轉(為什么是32MB呢?寄存器是32位的,此時的值是24位有符號數,所以32MB)。
B是最簡單的跳轉指令。要注意的是,跳轉指令的實際值不是絕對地址,而是相對地址——是相對當前PC值的一個偏移量,它的值由匯編器計算得出。
BL非常常用。它在跳轉之前會在寄存器LR(R14)中保存PC的當前內容。
為什么要先關閉看門狗?因為板子上電,看門狗是硬件打開的,不關閉會一直復位。
再看Makefile文件:
1 CFLAGS := -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -ffreestanding 2 leds.bin : crt0.S leds.c 3 arm-linux-gcc $(CFLAGS) -c -o crt0.o crt0.S 4 arm-linux-gcc $(CFLAGS) -c -o leds.o leds.c 5 arm-linux-ld -Ttext 0x0000000 crt0.o leds.o -o leds_elf 6 # arm-linux-ld -Tleds.lds crt0.o leds.o -o leds_elf 7 arm-linux-objcopy -O binary -S leds_elf leds.bin 8 arm-linux-objdump -D -m arm leds_elf > leds.dis 9 clean: 10 rm -f leds.dis leds.bin leds_elf *.o這里用到了arm的交叉編譯器,-ld,是鏈接指令,-Ttext是指明程序段,即程序存儲的地方,為什么是從0地址開始,這是因為在我使用的2440中,是以nand flash啟動的,nand啟動會復制nand flash中4K的內容到2440芯片的SRAM,這4K內容從地址0開始取址啟動,nor啟動就不需要復制內容到SARM,可以直接啟動。前面-c選項,表示只編譯不鏈接,而后面ld,表示把那些.o文件鏈接起來構成*elf文件,然后通過objcopy,把上面編譯鏈接之后的目標文件轉化成二進制文件即.bin文件。objcopy把一種目標文件中的內容復制到另一種類型的目標文件中.?-S?表示移出所有的標志及重定位信息 ;
-O?binary?xyb?xyb.bin?表示由xyb生成二進制文件xyb.bin。
objdump指令,-D代表反匯編所有的段,-m代表的是反匯編目標文件使用的構架,這里是arm構架,>重定向成*.dis文件。
關于Makefile中的幾個編譯選項:參考鏈接:http://blog.chinaunix.net/uid-20737871-id-1881211.html
-Wall 開啟所有警告
-Wstrict-prototypes?如果函數的聲明或定義沒有指出參數類型,編譯器就發出警告。
-O2 優化等級2
-fomit-frame-pointer選項是發布產品時經常會用到的優化選項,它可以優化匯編函數中用edp協助獲取堆棧中函數參數的部分,不使用edp,而是通過計算,全部使用esp來完成。參考鏈接:http://www.cnblogs.com/yamadie/p/3363567.html
-ffreestanding按獨立環境編譯,該環境可以沒有標準庫,且對main()函數沒有要求。最典型的例子就是操作系統內核。參考鏈接:http://blog.csdn.net/eroswang/article/details/1966640
?
?
終于介紹完了預備知識,這些東西有的需要理解記憶,有的只用了解熟悉即可。現在開始編寫程序。就流水燈的C語言程序來說,其實和單片機一樣,屬于簡單的。但是arm版之所以很多人買了之后就吃灰了,就是因為對只是體系的構架不熟悉,還有基本技能的缺失。這也是為什么很多人說,韋老師的課程不適合新手吧。
1 #define GPFCON (*(volatile unsigned long *)0x56000050) 2 #define GPFDAT (*(volatile unsigned long *)0x56000054) 3 4 #define GPF4_out (1<<(4*2)) 5 #define GPF5_out (1<<(5*2)) 6 #define GPF6_out (1<<(6*2)) 7 8 void wait(volatile unsigned long dly) 9 { 10 for(; dly > 0; dly--); 11 } 12 13 int main(void) 14 { 15 unsigned long i = 0; 16 17 GPFCON = GPF4_out|GPF5_out|GPF6_out; // 將LED1,2,4對應的GPF4/5/6三個引腳設為輸出 18 19 while(1){ 20 wait(30000); 21 GPFDAT = (~(i<<4)); // 根據i的值,點亮LED1,2,4 22 if(++i == 8) 23 i = 0; 24 } 25 26 return 0; 27 }這是韋老師的參考代碼,硬件上是低電平LED亮。
IO口分別對應GPIO_F ?4,5,6。雖然老師的代碼很好地實現了流水燈,但我覺得這個代碼寫成一個函數更友好,可移植性也更高,要是我的LED等編程GPI0_F 0,3,7,要實現流水燈就要大改代碼了,所以自己做了更改,模仿st的庫函數寫法,但是由于才接觸2440,對它的寄存器還不熟悉,所以先不急于封裝,等學了韋老師資料之后再去封裝。
總結
以上是生活随笔為你收集整理的起航,第一个程序——还是LED灯的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WARNING: Max 1024 op
- 下一篇: Android利用canvas画各种图形