arm-linux-gcc 裸機程序開發(一)

概述與SDRAM運行

        以前開發arm裸機程序都是在ADS1.2開發環境下編譯和調試的。剛開始時初學嵌入式好多東西不懂,選擇這個開發環境的理由,一是資料多的,mini2440開發板上提供了很多例程可以參考,網上幾乎所有arm裸機程序都是基於ADS1.2開發的。二是開發環境友善,雖然後來感覺ADS1.2有點難用,但畢竟是IDE的環境,對初學者來說總比命令行的方式更加直觀與方便。隨着學習的深入,感覺它就像傻瓜相機一樣,雖然好用但屏蔽了很多內容,影響了我們深入理解代碼編譯以及鏈接的細節。而且ADS對於程序的開發沒有GNU工具鏈靈活。這段時間因爲需要,又要編寫一些arm裸機程序。自己已經用Linux習慣了,不想再切回windows下工作了。所以,最近對linux下arm的裸機程序開發進行了學習。要讓程序在arm裸機下運行,主要解決如下問題:
    (1) 編譯器問題,這個不用說肯定是GNU的大名鼎鼎的GCC了,與此相關的什麼連接器,彙編器也都包含在內了。針對arm的GCC,當然就是arm-linux-gcc了,我所用的版本就是友善之臂光盤自帶arm-linux-gcc 4.4.3。也有資料說也可以用arm-elf-gcc,這個與arm-linux-gcc帶的c庫不同,是uclibc,更精簡更適合嵌入式。但是,相對於arm-elf-gcc而言,我對arm-linux-gcc更熟悉一點,畢竟編譯u-boot的時候用過,所以選擇了arm-linux-gcc作爲編譯器。
    (2) 啓動代碼問題:C程序運行是需要一定條件的,首先板子必須初始化好,然後堆棧必須初始化好,這樣C代碼才能夠運行起來。mini2440的啓動代碼對於ADS1.2就是2440init.S。本來打算移植這個,因爲ARM彙編與GNU彙編雖說在一般的彙編指令之間差別不大,但是在僞指令和語法結構上還是有很大的區別。所以移植起來會很困難,我果斷放棄了這個想法。然後我想起了u-boot的start.S,這個是現成基於GNU彙編的啓動代碼。但是start.S畢竟是基於u-boot工程的,所以還是需要移植的。
    (3) 直接下載到內存中運行:其實這也是啓動代碼的問題。在ADS開發環境下,我一般編譯程序都先是用NorFlash裏的supervivi將程序下載到內存中運行看結果,默認的下載地址是0x30000000。所以用arm-linux-gcc編譯也是一樣,要首先實現在內存中下載運行。其實在supervivi這個環境下,不需要啓動代碼也是可以運行C程序的,因爲這種情況下supervivi已經在運行了,它已經做過了啓動代碼應該做的事。不過這裏還是要加上啓動代碼,這是未雨綢繆,因爲我們的程序以後得自食其力。
    (4) NandFlash啓動問題,S3C2440提供了兩種啓動方式,NorFlash啓動以及NandFlash啓動,第三個問題其實也是爲了NorFlash啓動,因爲運行的環境是norflash啓動後的。對於NandFlash啓動,u-boot的啓動代碼與ADS的啓動代碼都提供瞭解決的辦法。u-boot讀寫Nandflash的程序就是nand_read.c,而ADS用的是nand.c。其實這兩個文件都做了同一件事,就是將NandFlash裏的程序拷貝到內存中。具體選那種方式,還是要看移植時的修改量。看看nand_read.c前兩個頭文件#include <common.h> #include <linux/mtd/nand.h> 頓時失去了修改他的想法,函數比較複雜。那麼只剩下nand.c了,隨便看了一下,函數很簡單基本上不用修改什麼。所以就選這個文件作爲讀寫NandFlash的程序了。
    (5) 移植2440lib.c問題,這個文件包含一些操作板子資源的庫函數。是mini2440自帶的,因爲是C語言編寫的,所以可以輕鬆的移植到linux編譯環境下。
    (6) 中斷的問題。中斷對嵌入式編程非常重要。所以編寫穩定高效的中斷處理程序很重要,也很必要。在ADS中我們只需要在自己的中斷服務程序中加入一行字:_irq,編譯器就會給我們保存中斷現場,多智能呀。可是arm-linux-gcc目前我還沒有發現這個功能,所以中斷現成的保護與恢復就得我們自己來完成。
     
一. 編譯器問題
        按照友善之臂說明書,安裝好arm-linux-gcc4.4.3,我的編譯器目錄是/home/sun/study/crosstools/4.4.3/ ,我所用的開發環境爲linux發行版 ubuntu10.10。
二. 啓動代碼問題
        首先修改start.S以及消除編譯錯誤,這個最容易解決,只要去掉u-boot有關的一些宏定義,以及註釋掉與nandflash啓動相關的部分就可以了。最難的是能在開發板上正確的運行。在此之前,我們得先弄清楚一個問題,就是嵌入式程序的加載域與運行域的關係與區別。就拿u-boot來說明,我們知道友善之臂mini2440的u-boot,在u-boot.lds裏有這一行代碼
. = 0x33f80000;在編譯u-boot的輸出信息中,我們會發現的最後的鏈接選項有一個-Ttext 0x33f80000。那麼這個0x33f80000究竟是什麼意思呢,他都會影響什麼呢?弄清楚這個問題之前,我們先清楚一些概念。我們知道一個鏈接好的可運行的二進制代碼,無非是一條一條的機器指令羅列而成。u-boot.bin也是這樣,每條指令相對於代碼的開始都是有一個偏移的地址,第一條指令偏移爲0,第二條爲1,以此類推直到程序的結束,最後一條指令偏移量就是程序的長度。
        我們先拋開這個0x33f800000不說,先說一下CPU運行u-boot的過程。假如我們把u-boot下載到內存0x30000000處運行,那麼u-boot第一條指令的地址就是0x30000000, CPU要去運行u-boot,那麼首先必須執行類似這樣的指令adr pc,#0x30000000,這樣CPU就會取到0x30000000裏的第一條u-boot指令。假如我們把u-boot下載到內存的0x0地址處運行,那麼CPU是去0x0地址取指令。這個過程與你鏈接的時候指定什麼地址無關,程序該到哪裏運行就到哪裏運行,一切地址都是相對與第一條指令在內存中的位置。這時程序就是運行在加載域,也就是剛剛下載進去的內存地址範圍運行。
        回到問題的初衷,鏈接時我們指定了一個地址0x33f80000,這個究竟是做什麼的。這個就涉及到了程序的絕對地址與運行域。也就是說二進制代碼的指令有兩種地址,一個就是相對地址,相對於你加載到內存中的位置,一個就是絕對地址,無論你下載到內存中的哪裏,這個地址都是不變的。比如u-boot的這個0x33f80000,那u-boot的第一條指令的絕對地址就是0x33f80000了,以後的指令的絕對地址類推。說完了這個地址是什麼,接下來說說他的作用,很明顯他是程序運行域的開始地址。具體運行域的概念,我的理解就是,程序正常運行時所處的地址空間。這裏的正常運行是指的代碼自拷貝之後的狀態,在u-boot有一段自拷貝程序,如果程序的加載地址與鏈接是指定的地址不相符的話,程序會把自己拷貝到鏈接指定的地址處,u-boot就是0x33f80000。所以0x33f80000這個地址的作用就是,提供程序運行域的開始地址。那麼這個地址影響的代碼,就是調用ldr僞指令的代碼。ldr僞指令就是取標號的絕對地址的。如ldr sp,=UndefStack,就是將UndefStack的絕對地址賦值給sp,這個地址一經鏈接無論程序下載到哪裏都是不變的。如果寫成adr sp,UndefStack 就是取UndefStack的相對地址,他會隨程序下載的地址不同而不同。
       現在總結一下,程序剛開始下載的內存或者norFlash的位置就是下載域,當程序把自己拷貝到鏈接是指定的位置後就會運行在運行域。程序的代碼有兩種地址:絕對地址與相對地址。如果你把程序恰好下載到了鏈接時指定的地址,那麼加載域就是運行域,絕對地址就是相對地址。如果不是,那麼絕對地址與相對地址是不同的。當程序運行在運行域的時候,絕對地址等於相對地址。
注意: ldr 這個指令有兩種身份,一種是普通數據裝載指令,用法ldr r0,[r1]或者#define pWTCON 0x53000000 ldr r0,=pWTCON,這條指令就是把0x53000000賦值給r0,相對的就是str。一種就是僞指令,加載標號處的絕對地址,用法ldr r0, = label1。 相對應的就是adr,加載相對地址
       弄清楚了加載域與運行域,相對地址與絕對地址的關係。我們就要修改start.S使之正確的運行在內存中,下面就該解決第三個問題了。
三. 直接下載到內存中運行的問題
       要使得裸機程序能在內存中運行,實現先解決u-boot直接下載到內存中的運行的問題。我以前移植的u-boot是不能下載到內存中運行的,問題原因以前也沒有注意,現在要用到了,一起解決吧。但是,找了很久一直不知道是什麼原因,後來在開發板光盤中的u-boot的移植手冊中發現了端倪,就是因爲絕對地址與相對地址的關係的問題。問題出在了lowlevel_init.S上,這是一段初始化內存的代碼。關鍵代碼如下: 

_TEXT_BASE:
	.word	TEXT_BASE
.globl lowlevel_init
lowlevel_init:
	/* memory control configuration */
	/* make r0 relative the current location so that it */
	/* reads SMRDATA out of FLASH rather than memory ! */
	ldr     r0, =SMRDATA    @取SMADATA的絕對地址
	ldr	r1, _TEXT_BASE  @TEXT_BASE = 0x33f80000 所以r1 = 0x33f80000
	sub	r0, r0, r1      
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4
0:
	ldr     r3, [r0], #4
	str     r3, [r1], #4
	cmp     r2, r0
	bne     0b

	/* everything is fine now */
	mov	pc, lr

	.ltorg
/* the literal pools origin */

SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30
        擁有以上的代碼的u-boot是不能運行下載到內存中運行的,因爲ldr r0, =SMRDATA取的SMADATA的絕對地址,他和0x33f80000相減算出來的是SMRDATA相對於第一條絕對地址的偏移,這個只適用於程序下載到0x0處運行的情況。對於程序下載到內存的其他地方是不行的,所以因爲地址的不同,賦值給內存初始化寄存器的內容,就不是最後那些數據,數據是隨機的。這導致內存就不能工作,程序就死到這裏了。下面是修改後的代碼:
_TEXT_BASE:
	.word	0x33f80000

.globl lowlevel_init
lowlevel_init:
	/* memory control configuration */
	/* make r0 relative the current location so that it */
	/* reads SMRDATA out of FLASH rather than memory ! */
	ldr     r0, =SMRDATA           @取SMRDATA的絕對地址
	ldr	r1, =lowlevel_init     @取lowlevel_init的絕對地址

	sub	r0, r0, r1             @相減之後,r0裏保存的是SMRDATA相對與lowlevel_init標號的偏移
	adr	r3, lowlevel_init      @取lowlevel_init的相對地址,這個隨下載地址不同而不同
	/* r3 <- current position of code */
	add 	r0, r0, r3             @加上偏移之後就是SMRDATA的真實地址
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4
       以上修改之後u-boot就可以在內存中運行了,我們的啓動代碼也可以在內存中運行了。這個修改是友善之臂u-boot移植文檔修改的,我感覺這樣做有點羅嗦了,直接一個adr r0,SMRDATA就可以取到他的相對地址,事實證明的確是這樣。所以這段程序就是這樣了:
lowlevel_init:
	adr 	r0, SMRDATA           
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4
        接下來就寫一個小小的測試程序,驗證一下。就像程序員每次學習新語言時,第一個跑的程序都是hello world!一樣,我們搞嵌入式的每次在新的架構上運行的都是流水燈。以下是流水燈的程序:
//延時函數
void delay(int a)  
{
       int i,j;
       for(i=0;i<a;i++)
          for(j=0;j<100;j++)
              ;

}
int Main(void)
{
    int light,i;
    
    //定義PB5~PB8爲輸出
    rGPBCON = (0x1<<10)|(0x1<<12)|(0x1<<14)|(0x1<<16);
    //使PB上拉功能失效
    rGPBUP  = 0x7ff;
    
    light = 0x10;
    light = light<<1;
    
    rGPBDAT = ~light;//第一個LED被點亮
    delay(5000);//延時一段時間
    //主程序死循環
    while(1) {
           //從一端向另一端
           for (i=0;i<3;i++) {
                  light = light<<1;
                  rGPBDAT = ~light;
                  delay(5000);
           }

           //返回
           for (i=0;i<3;i++) {
                  light = light>>1;
                  rGPBDAT = ~light;
                  delay(5000);  
           }
    } 
}
        就是這樣一個簡單的程序,實驗的現象竟然是四個燈全亮,找了好久不知道是什麼原因,註釋掉循環之後可以點亮一個燈,說明程序運行是正確的,但就是循環不起作用,首先我想到的是CPU頻率的問題,確定一下頻率是沒有問題的,因爲我增大循環的的次數還是沒有效果。最後終於找出了問題,我在編譯的時候加了一個O2的選項,導致編譯器把我的程序優化了。所以循環延時不起作用。所以以後編寫嵌入式程序的時候這個優化還是要注意了。到此程序總算是可以用supervivi的D選項下載到內存中運行了。
  注:編譯成功後的代碼在我的資源裏:   http://download.csdn.net/detail/yaozhenguo2006/3749581.


  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章