ARM Cortex-M嵌入式C基礎編程(下)
ARM Cortex-M Embedded C Fundamentals/Tutorial-Aviral Mittal
Load Region Vs Execution Region:
現在這意味着代碼或代碼的某些部分可能有不同的地址用於將它們加載到存儲器中,例如,當它們加載到ROM中時,以及當它們被執行時。加載程序的地址稱爲其“加載地址”,執行程序的地址稱爲其執行地址。
現在很明顯,如果程序的加載地址或程序的某一部分的加載地址與它們各自的執行地址不同,則必須將代碼或代碼的某一部分“重新定位”到不同的地址,即從其“加載地址”到“執行地址”
函數\uuu scatterload正是這樣做的。
除了將代碼或部分代碼從加載區域重新定位到執行區域之外,此函數還將對某些區域(如堆棧區域)進行初始化。堆棧區域通常初始化爲零,因爲用於爲堆棧內存保留空間的編譯器指令“SPACE”也會導致其初始化爲零。
雖然Keil通常會自動爲用戶插入所有的後臺代碼,以便重新定位代碼,但它也允許用戶定義稱爲散點加載文件的特定文件來定義不同的內存區域。但是,這超出了本文的範圍。這裏顯示了散點加載文件的示例。使用自定義散點加載文件,用戶可以有效地將代碼和數據“散點”到內存中的多個區域。
這現在提出了一個有趣的觀點:如果系統RAM不可用怎麼辦?在系統通電時,系統RAM很可能是可用的。XIP是這個場景的答案。
用戶可以使用自己的“入口點”繞過默認執行流。這可以通過設置鏈接器選項“–entry My_Reset_Handler”來完成。
另一種方法是使用匯編指令’ENTRY’。
隨機信息:
實際上,程序的完整執行順序如下:
-
堆棧指針從0x0000的內存內容加載
-
處理器的程序計數器加載到Reset_Handler的位置,該位置將出現在內存位置0x0000_0004
-
Reset_Handler只不過是一個跳轉到__main的程序(這是因爲在startup.s文件中,Reset_Handler已被編碼爲這樣做)
-
__main calls __scatterload; Program jumps to__scatterload
-
__scatterload -> Initialization, Zero Initialization regions to 0, load region relocation to execution addresses
-
__scatterload_null
-
__scatterload_zero_init
-
__rt_entry
-
__user_setup_stackheap (optional)
-
__user_libspace
-
__rt_entry_li
-
__rt_lib_init
-
main()
-> User Code -
__rt_lib_shutdown
上面編寫的C程序包含應用程序代碼和數據常量。當應用程序代碼和數據的編譯版本被放入微控制器的存儲器中時,它可以被放入存儲器的“根區域”或“非根區域”。根區域具有相同的加載時間和執行時間地址。非根區域具有不同的加載時間和執行時間地址。根區域包含ARM輸出的區域錶鏈接器region表包含需要初始化的非根代碼和數據區域的地址。
但是根區域是什麼?
根區域是內存空間中的一個區域,其中程序的“加載地址”只是其“執行地址”。
但是什麼是“加載地址”和什麼是“執行地址”?
加載地址只是用戶代碼將駐留在內存中的位置。例如,閃存或ROM或RAM。
但是,有時程序不能從它在內存中的位置“執行”,但在執行之前,必須將它重新定位到執行它的位置的內存區域中。這是“執行地址”。
Load Address Vs Execution Address: Why these are different at all?
原因可能有很多,其中一個解釋如下:
考慮一個例子,其中用戶代碼駐留在閃存中,並且由於閃存訪問速度慢,因此可能需要從更靠近處理器的RAM執行代碼以進行快速處理。存儲程序的閃速存儲器位置將是其“加載地址”,而複製並最終執行程序的RAM存儲器位置將是其“執行地址”。
在任何嵌入式產品中,軟件都很常見地駐留在非易失性存儲器(如閃存)上,並在執行前將代碼從閃存複製到RAM。
區域表還包含一個函數指針,該指針指示區域需要什麼樣的初始化,例如複製、歸零或解壓縮函數。
__scatterload遍歷region表並初始化各個執行時間區域。功能:
將零初始化零,初始化(ZI)區域爲零
將非根代碼和數據區域從其加載時間位置複製或解壓縮到執行時間區域。
__main總是在啓動時調用此函數,然後再調用__rt_entry。
What is Scatter-Loading?
散點加載是使用戶能夠使用文本描述指定已編譯二進制文件到鏈接器的內存映射的過程。
這是一種控制二進制文件的組件在內存映射中的位置的方法。
單詞“scatter”的來源是這樣一個事實:如果一個程序相當複雜,需要將編譯後的代碼的幾個區域放在內存的幾個區域中,那麼它實際上是“scatter”在內存中。因此,這種機制通常適用於複雜程序,但也同樣適用於簡單程序。
As per Joseph Yui:
不同的開發工具有不同的方式來指定微控制器系統中程序和數據存儲器的佈局。在ARM工具鏈中,可以使用名爲散點加載文件的文件類型,或者在Keil MDK-ARM的情況下,散點加載文件可以由mVision開發環境自動生成。
When to use Scatter-Loding?
根據ARM文檔:
實現嵌入式系統通常需要分散加載,因爲它們使用ROM、RAM和內存映射外設。
需要或非常有用分散加載的情況:
Complex memory maps
必須放在許多不同內存區域中的代碼和數據需要詳細說明將數據段放在內存空間的何處。
不同類型的存儲
許多系統包含各種物理內存設備,如flash、ROM、SDRAM和fast SRAM。散點加載描述可以將代碼和數據與最合適的內存類型相匹配。例如,可以將中斷代碼放入快速SRAM中以提高中斷響應時間,但可能會將不經常使用的配置信息放入較慢的閃存中。
內存映射外設
散點加載描述可以將數據部分放在內存映射中的精確地址,以便可以訪問內存映射的外圍設備。
在固定位置的函數
即使修改並重新編譯了周圍的應用程序,也可以將函數放在內存中的同一位置。這對於跳轉表的實現很有用。
使用符號標識堆和堆棧
當應用程序鏈接時,可以爲堆和堆棧位置定義符號。
What is a debug-adaptor:
爲了將程序代碼下載到微控制器,並執行諸如暫停和單步執行等調試操作,您可能需要調試適配器將PC上的USB連接轉換爲微控制器使用的調試通信協議。大多數C編譯器供應商都有自己的調試適配器產品。例如,Keil有ULINK產品系列,IAR提供I-Jet產品。
一些開發包已經在板上內置了USB調試適配器。
軟件設備驅動程序:
用於讀/寫外設寄存器的軟件。
目標對象文件:
在大多數情況下,項目包含許多單獨編譯的文件。編譯過程完成後,每個源文件都會有一個對應的目標文件。爲了生成最終組合的可執行映像,需要單獨的鏈接過程。在鏈接階段之後,IDE還可以生成其他文件格式的程序圖像,以便將圖像編程到設備。
閃存編程:
幾乎所有的Cortex -M微控制器都使用閃存來存儲程序。創建程序圖像後,我們需要將程序下載到微控制器的閃存中。要做到這一點,你需要一個調試適配器,如果你使用的微控制器板沒有一個內置。實際的flash編程過程可能相當複雜,但這些通常由IDE完全處理,您只需單擊鼠標即可執行整個編程過程。注意,如果您願意,也可以將應用程序下載到SRAM並從中執行它們
執行程序並調試:
編譯好的程序下載到微控制器後,您可以運行該程序並查看它是否工作。您可以使用IDE中的調試環境來停止處理器(通常稱爲halt),並檢查系統的狀態以確保其正常工作。如果它不能正常工作,您可以使用各種調試功能(如單步執行)詳細檢查程序操作。所有這些操作都需要一個調試適配器(或者一個內置在開發工具包中的適配器,如果有的話)來連接正在測試的IDE和微控制器。如果發現一個軟件錯誤,那麼您可以編輯程序代碼,重新編譯項目,將代碼下載到微控制器,然後再次測試
在編譯程序的執行過程中,可以通過各種I/O機制(如UART接口或LCD模塊)輸出信息來檢查程序的執行狀態和結果。本書中的一些例子將展示如何實現其中一些方法。
上面的流程與基於GNU’gcc’的流程不同,它看起來如下:
主要區別在於連接過程。基於GNU的過程是單步的,而第一步有單獨的鏈接過程。