ARM開發經驗


<script type="text/javascript">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>
前一段時間做了arm的一些開發,主要是編寫了arm的啓動軟件和移植了uCOS-II到arm7。我做事情喜歡深入簡出,及從最簡單,最原理的方面先做一個框架,然後在這個框架裏面進行補充。我還是一個很喜歡和別人討論的人,希望有人可以給我提出意見和建議。我的這個心得很初級,都是一些基本的東西。現在拿出來和大家分享,希望在我畢業之前能給大家留一些紀念。 

由於這些東西發paper實在是沒有價值,但是我感覺可以作爲arm開發的入門。由於我的水平和經驗有限,錯誤也是難免的。但是如果不拿出來和大家分享,就算有錯誤我也發現不了,是麼?呵呵。我現試試發連載的第一篇,看看有多少價值,如果大家覺得有價值,我會繼續連載的。  

前言 
這個文檔是我學習ARM編程的總結和心得。閱讀這個文檔的人應當首先閱讀ADS1.2的幫助文檔及相關內容。這個文檔不會對編譯器及連接器做出詳細的說明,在需要的時候會指出具體內容在相關資料的章節。同時閱讀這個文檔的人需要了解ARM指令集和一些ARM彙編的基本內容以及C和C++的相關編程內容。同時還需要了解ARM的流水線結構及一些基本的編程知識。同時爲了方便查閱英文文檔,所有的相關術語都使用英文原文。 

第一章 STARTUP1 ARM的啓動 
一般的嵌入式系統在主程序執行之前都需要執行一些初始化的過程以創造嵌入式程序運行的環境,尤其是一些高級的嵌入式系統,由於核心芯片使用內存映射、內存保護等機制以及編程使用高級語言C,C++甚至JAVA語言,都需要先創建一個適合程序運行的硬件環境,然後初始化或者配置或者剪裁run-time library, 這些工作都必須在主程序運行前完成,所以一個startup程序或者程序組對於一個嵌入式系統來說是非常重要的。要編寫startup程序,需要對編譯器、鏈接器和彙編器的細節有一定的瞭解,同時對ARM芯片硬件本身的地址分配以及memory mapping機制也需要有一些瞭解。 

2 ARM 程序的工作過程 
首先由各種source file經過編譯產生object文件,然後object文件經過鏈接生成Image文件,然後通過ICE的方法,根據描述文件的指定下載到目標板上的固態存儲器指定地址當中,比如flash,EEPROM, ROM等等。在程序執行之前,根據某些描述文件,將需要讀寫數據的部分讀出放入動態存儲器比如RAM當中,然後程序從ROM開始執行。或者有時爲了提高程序的運行速度,也可以將所有的程序(有一些root的部分除外,以後會提及)通過一個描述文件放入指定的RAM當中,然後程序從RAM開始執行,但是這樣會耗費大量的動態存儲器,所以大部分程序會取折中的方法,將需要快速運行的部分和要讀寫的部分放入RAM中(一般讀固態存儲器的過程和動態存儲器的過程是一樣的,但是寫就不同了,所以讀寫的部分一定要放到RAM中),而只讀的部分和對速度要求不是那麼高的部分放入固態存儲器。同時ARM結構的異常向量表規定放在地址爲0x00000000開始的地址空間上,而一般的CPU爲了提高異常相應速度,會將這個向量段remap到其他的RAM當中,所以在描述文件當中必須精確指定異常向量跳轉程序的地址到remap的地方。在application程序執行前,還需要由一些文件描述application程序執行的環境。比如系統工作時鐘,總線頻率。現在一般嵌入式編程語言爲C,C++等。如果在使用它們的時候使用的runtime-library,那麼在程序執行前還需要爲這些庫函數初始化heap。然後ARM可能工作在不同的模式,還需要爲不同的工作模式設置stack。這樣,描述鏈接地址的文件,以及在application運行前所有的初始化程序就是startup程序組。 

3 STARTUP分類 
這樣,將startup程序所完成的功能分類。一類是鏈接地址描述,一類是各種初始化的程序。根據不同的應用,描述文件和初始化程序的內容以及結構和複雜程度都會不同。但是基本上,它們都必須實現以下功能。 

3.1 描述文件實現功能 

描述文件可以是鏈接命令行上簡單的幾個字符,也可以是一個非常複雜的文件,但是它必須完成如下功能: 

; 指定程序下載的地址 

; 指定程序執行的地址 

3.2 初始化程序實現的功能 

初始化程序根據不同的應用,其結構和複雜度也不同,但是它必須完成如下基本功能: 

; 異常向量初始化 

; 內存環境初始化 

; 其他硬件環境初始化 

4 描述文件 
要編寫描述文件,必須知道ARM Image文件的組成及ARM Image文件執行的機理。 

4.1 ARM Image的結構 

一個ARM Image structure由linker在以下幾個方面定義: 

 組成它的regions 和 output sections 

 當Image 下載的時候這些regions 和 sections 在內存中的位置 

 當Image 執行時這些regions和sections在內存中的位置 

4.1.1 ARM Image的組成 

一個ARM Image被保存在可執行文件當中,它的層次結構可以包括Image,regions,output sections和input sections。 

 一個Image由一個或多個regions組成,每個region包括一個或多個output sections 

 每個output section由一個或多個input sections組成 

 Input sections是一個object file中的code和data信息。 

Image的結構如下圖: 

1 附圖: 

NOTE Input section,output section和region的定義見ADS_LinkerGuide 3-3頁。 

同時Input section 有幾種屬性,分別爲readonly,read-write,zero-initialized。分別稱爲RO,RW和ZI。屬性來源於AREA後的attr屬性。 

比如CODE是RO,DATA是RW,NOINT默認爲ZI,即用0值初始化,但是可以選擇不進行0值初始化。ZI屬性僅僅來源於SPACE, DCB, DCD, DCDU, DCQ, DCQU, DCW, 或者DCWU。由以上定義,ZI屬性的包含於RW屬性,它是有初始值的RW數據。又例如在C語言中,代碼爲RO,靜態變量和全局變量是RW,ZI的。 


4.1.2 Image 的Load view 和 execution view 

在下載的時候Image regions被放置在memory map當中,而在執行Image前,或許你需要將一些regions放置在它們執行時的地址上,並建立起ZI regions。例如,你初始化的RW數據需要從它在下載時的在ROM中的地址處移動到執行時RAM的地址處,附圖: 


NOTE Load view 和execution view的詳細定義見ADS_LinkerGuide 3-4。 

以上的描述包括二個內容,一是要指定各個section在load view和execution view時的地址即memory map,二是要在執行前根據這些地址進行section的初始化。 

4.1.3制定Memory map 

制定memory map的方法基本上有二種,一是在link時使用命令行選項,並在程序執行前利用linker pre-define symbol使用彙編語言制定section的段初始化,二是使用scatter file。以上二種方法依應用程序的複雜度而定,一針對簡單的情況,二針對複雜的情況。 

4.1.1.1 利用linker pre-define symbol使用匯編程序 

這是簡單的方法,針對簡單的memory map。在link時使用選項-ro, -rw, 等等指定memory map的地址。詳細說明參看ADS_LinkerGuide中命令行選項說明。然後利用匯編使用pre-define symbol,來進行各種段的定位。Linker pre-define定義如下: 


由前面對ZI的說明,Image$$RW$$Limit = Image$$ZI$$Limit。 


這些都是linker預先定義的外部變量,在使用的時候可以用IMPORT引入。下面給出一個例子。 

假設linker 選項爲:-ro-base 0x40000000 -rw-base 0x40003000。程序和只讀變量(const 變量)大小爲0x84,這樣RO section的大小爲0x84 bytes。Data的大小爲0x04 bytes,並且data被初始化,則RW section的大小爲0x04,ZI section的大小爲0x04。這樣程序在load view,地址是這樣的: 

0x40000000開始到地址0x40000080,是RO section部分(程序從0x40000000開始),Image$$RO$$Limit = 0x40000084. 

0x40000084地址開始到地址0x40000084,是RW section部分。 

在execution view,由linker的選項,各個section的地址是這樣的: 

RO section的地址不變。 

RW section的起始地酚Φ蔽?x40003000,則Image$$RW$$Base = 0x40003000。 

因爲全部的0x04 bytes data被初始化,所以Image$$RW$$Limit = Image$$ZI$$Limt = 0x40003004。 

現在要做的就是將RW section移到以0x40003000開始的地方,並且創造一個ZI section。 

一個更通用的做法是: 

首先比較Image$$RO$$Limit和mage$$RW$$Base,如果相等,說明execution view下RW section的地址和load view 下RW section的地址相同,這樣,不需要移動RW section;如果不等,說明需要移動RW section 到它在execution view中的地方。然後將Image$$ZI$$Base地址到Image$$ZI$$Limt地址的內容清零。 

示例代碼如下: 

;讀入linker pre-define symbols 

IMPORT Image$$RO$$Limit 

IMPORT Image$$RW$$Base 

IMPORT Image$$ZI$$Base 

IMPORT Image$$ZI$$Limit 

; .......一些其他的代碼或僞指令 

;R0讀入section load address 

LDR R0,= Image$$RO$$Limit 

;R1讀入section execution address 

LDR R1,= Image$$RW$$Base 

;R2讀入execution section 後的緊跟的word address 

LDR R2,= Image$$ZI$$Base 

;檢查RW section的地址在load view和execution view下 

;是否相等,如果相等,就不移動RW section,直接建立 

;ZI section 

CMP R0,R1 

BEQ do_zi_init 

;否則就copy RW section到execution view下指定的地址 

BL copy 

; ...... 

; ...... 

;copy 是一個用於copy的子函數,它把從R0中的地址開始的 

;section copy到R1中的地址開始的section,這個section的 

;上限地址後緊跟的word address保存在R2中 

Copy 

CMP R1,R2 

LDRCC R3,[R0],#4 

STRCC R3,[R1],#4 

BCC copy 

MOV PC,LR 

; ...... 

; ...... 

;do_zi_int子函數是爲創建ZI section做一些準備工作 

do_zi_int 

;將ZI section開始的地址裝入R1 

LDR R1,= Image$$ZI$$Base 

;將ZI section結束後緊跟的word address裝入R2 

LDR R2,= Image$$ZI$$Limit 

;將ZI section 需要的初始化量裝入R3 

MOV R3,#0 

BL zi_int 

; ...... 

; ...... 

;zi_int子函數用於建立並初始化ZI section,ZI section的 

;開始地址儲存在R1,ZI section結束後緊跟的word address 

;地址儲存在R2 

zi_int 

CMP R1,R2 

STRCC R3,[R1],#4 

BCC zi_int 

MOV PC,LR 

; ...... 

這個方法針對比較簡單的應用,如果需要進行一個比較複雜的memory map,如下圖,那麼這個方法就不適用了。爲了解決複雜memory map的問題需要用到scatter load 機制。 
發佈了9 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章