引言
MM32F5微控制器基于Arm STAR-MC1微控制器,最高主頻可達(dá)120MHz,集成了FPU單元和DSP擴(kuò)展指令集,有不錯的算力。但片內(nèi)集成的128KB的RAM和256KB的FLASH,如果想支持代碼量比較大的軟件框架,就可能會力不從心,例如,TensorFlow Lite或者基于MicroPython的OpenMV這樣的應(yīng)用就需要更多的內(nèi)存空間做緩存。但MM32F5微控制器帶有FSMC接口和QSPI接口并支持基于QSPI的XIP(eXecute In Place,就地執(zhí)行),可以分別外擴(kuò)SRAM和FLASH存儲器,這就為擴(kuò)展存儲資源提供了可能。在本文中,將介紹使用FSMC接口外接SRAM擴(kuò)展內(nèi)存的過程。在后續(xù)的文章中,在后續(xù)文章中,還會繼續(xù)介紹使用QSPI對接qspiflash存儲器實現(xiàn)外擴(kuò)FLASH的過程。
硬件電路
MM32F5微控制器上集成了FSMC(Flexable Static Memory Controller)接口,可以外接并口的SRAM存儲器。
在PLUS-F5270開發(fā)板上,對應(yīng)外擴(kuò)了一個1MB大小的PSRAM存儲器作為擴(kuò)展內(nèi)存,如圖x所示。
圖x FSMC對接SRAM存儲器
軟件設(shè)計
使用FSMC接口外擴(kuò)的SRAM存儲設(shè)備之前,必須先激活微控制器的FSMC接口,包括啟用對FSMC接口外設(shè)的訪問開關(guān)、配置FSMC接口對應(yīng)的外部引腳,以及配置FSMC的時鐘源和工作模式等操作。基于這樣的使用前提,一般情況下使用外擴(kuò)SRAM,都是在應(yīng)用程序中激活FSMC硬件外設(shè)接口,之后通過在指定地址分配內(nèi)存,或者訪問絕對地址的方式訪問新擴(kuò)展出來的內(nèi)存,但此時默認(rèn)的主內(nèi)存還是片內(nèi)的SRAM。這種使用擴(kuò)展SRAM的方式對于規(guī)模較小或者綁定具體應(yīng)用的項目,因為涉及到對代碼的改動以及對存儲管理的工作量較小并且明確,在一定程度上是可以接受的。但對于移植已有現(xiàn)有的項目,或者是規(guī)模較大的框架性軟件,開發(fā)者通常不愿意(也不建議)深入到代碼庫中去人為指定每個可能的全局變量的絕對地址,僅將管理的目標(biāo)地址區(qū)間從片內(nèi)SRAM轉(zhuǎn)移到了外擴(kuò)的SRAM而已,而希望能夠一如既往地讓編譯器自動管理內(nèi)存的分配機(jī)制。
編譯器自動管理內(nèi)存,就涉及到在芯片上電初始化過程中對編譯器運行時環(huán)境的初始化過程中對堆棧進(jìn)行初始化,配置棧頂和棧底、堆底和堆頂指針等,也包括將內(nèi)存中BSS段的數(shù)據(jù)清零,將DATA段數(shù)據(jù)的初值從FLASH搬運到SRAM中等。這些操作的過程,大多被封裝在集成開發(fā)環(huán)境自帶的庫中(例如Keil的__main
函數(shù),經(jīng)過一系列同編譯器相關(guān)的準(zhǔn)備工作后才跳轉(zhuǎn)到用戶的main()
函數(shù)),不開放給用戶修改,而其中使用的和計算出的內(nèi)存地址,也都是在編譯過程中預(yù)先定義的。
- 如果用戶強(qiáng)行在鏈接命令文件中指定默認(rèn)的主內(nèi)存空間為外擴(kuò)存儲,那么在芯片啟動過程中,預(yù)定義的初始化運行時環(huán)境的操作,將會在未初始化好FSMC接口等硬件的時候直接訪問FSMC擴(kuò)展出的內(nèi)存空間,必然出錯。可能會提示的錯誤是hardfault,標(biāo)記為訪問了無效的地址。此時,若是用戶在集成開發(fā)環(huán)境的
__main
函數(shù)之前的SystemInit()
函數(shù)中先激活FSMC等外擴(kuò)SRAM相關(guān)的硬件也是可行的,但必須要注意,這個過程中,除了CPU中僅有的寄存器外,不能使用任何棧內(nèi)存,因為此時燒寫在默認(rèn)的中斷向量表首位的棧頂?shù)刂匪赶虻目臻g還是不可訪問的。具體來說,就需要用匯編命令完成對所有相關(guān)硬件外設(shè)的初始化操作,這確實是一個考驗人耐心的事情。這里簡單看一下SDK中的復(fù)位中斷服務(wù)程序中的啟動程序代碼,見代碼x。
代碼x SDK中的啟動程序代碼
Reset_Handler:
ldr r0, =__INITIAL_SP
msr psp, r0
ldr r0, =__STACK_LIMIT
msr msplim, r0
msr psplim, r0
#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
ldr r0, =__STACK_SEAL
ldr r1, =0xFEF5EDA5U
strd r1,r1,[r0,#0]
#endif
bl SystemInit
bl __main
.fnend
.size Reset_Handler, . - Reset_Handler
- 一些技術(shù)高超的工程師可能會想到一些巧妙的做法,能不能先用缺省的片內(nèi)SRAM支持編譯工具鏈的初始化過程,然后在應(yīng)用程序中初始化FSMC外設(shè)(此時仍使用片內(nèi)SRAM),然后再試圖重建內(nèi)存管理系統(tǒng),將芯片系統(tǒng)中內(nèi)存相關(guān)的指針人為重建在外擴(kuò)SRAM中呢?且不說這是一個極其麻煩的過程,需要把編譯工具鏈中的每個同內(nèi)存相關(guān)的配置變量都翻出來重新人為計算并賦值一遍,一個明顯的限制在于,所有將要放在外擴(kuò)的大SRAM中的數(shù)據(jù)必須在較小的片內(nèi)SRAM中必須預(yù)先存放一份副本,之后在應(yīng)用程序運行的過程中轉(zhuǎn)移到外擴(kuò)SRAM。此時,在編譯階段,編譯器會限定整個程序能使用的存儲空間不能大于片內(nèi)SRAM的大小,否則編譯器仍然會報錯并拒絕生成可執(zhí)行文件。這就限制了能夠直接使用外擴(kuò)SRAM的空間,同最常用的將外擴(kuò)SRAM當(dāng)成輔助存儲空間的做法沒有實質(zhì)區(qū)別。
使用bootloader初始化硬件環(huán)境的思路
為了讓用戶的工程直接在一個可用的外擴(kuò)SRAM上建立存儲管理系統(tǒng),一個可行的設(shè)計,是使用額外使用一個bootloader工程(或者在芯片內(nèi)部的電路實現(xiàn)上直接用一段ROM承載bootloader工程中的操作),在使用少量片內(nèi)SRAM的條件下,通過常規(guī)的調(diào)用驅(qū)動API的方式(而不用匯編語句序列),先準(zhǔn)備好使用FSMC的硬件環(huán)境,例如配置時鐘系統(tǒng)、引腳復(fù)用功能、FSMC接口外設(shè)等等。在bootloader工程最后的部分,直接跳轉(zhuǎn)到一個約定的、存放用戶application工程的地址,開始自行application工程。在application是一個完全獨立的工程,不用激活FSMC就可以直接使用外擴(kuò)的SRAM,因此可以利用編譯工具鏈直接在外擴(kuò)的SRAM上重建存儲管理系統(tǒng)。在application工程中,用戶將完全不用干預(yù)內(nèi)存的分配情況,就像之前一樣完全交由編譯器自行管理;由于不再使用bootloader工程,片內(nèi)的SRAM可以作為獨立的一塊可用的存儲空間(就像之前在片內(nèi)SRAM看外擴(kuò)SRAM一樣),繼續(xù)為應(yīng)用程序提供存儲服務(wù)。
進(jìn)一步分析,探討把bootloader工程放到ROM中的可能性。程序一旦寫入ROM中,就不能有任何改變了。但在配置外擴(kuò)SRAM的時候,仍需要人為指定外擴(kuò)SRAM映射的地址范圍(開始地址和空間大小),這個設(shè)定在不同應(yīng)用場景中可能會不一樣,受成本和功能的權(quán)衡,可能有時候會用或大或小的存儲器設(shè)備,因具體選型不同配置參數(shù)也會隨之發(fā)生變化,因此不適合直接固化在ROM中。除非是對應(yīng)合封的芯片,需要固定規(guī)格的SRAM芯片的晶元已經(jīng)同微控制器一起被封在芯片內(nèi)部,倒是不失為一種高集成的SOC解決方案。或者也可以用類似回調(diào)的方式,由用戶在某種基礎(chǔ)的協(xié)議下向ROM中的小程序提供外擴(kuò)SRAM芯片專屬的配置,例如在手冊里說明在特定的用戶可編程的FLASH存儲區(qū)中存放了關(guān)于SRAM的配置信息,也是可行的,但處理過程就多了幾個步驟。沒有擴(kuò)展多種SRAM的情況下,實在沒必要在芯片里設(shè)計這么一塊ROM。不過,這種方式在外擴(kuò)FLASH的時候確實用到了,在后續(xù)的文章中將會提及,外擴(kuò)FLASH的各廠家設(shè)計生產(chǎn)的nor FLASH型號芯片在使用上存在差異,在flashless的芯片中必須在ROM中設(shè)計程序首先識別外擴(kuò)spiflash芯片的型號,從而使用對應(yīng)的配置信息初始化spiflash芯片,到時也將會有一番細(xì)致地闡述。
接下來,將詳細(xì)介紹創(chuàng)建bootloader工程和用戶在application工程中開發(fā)應(yīng)用的實現(xiàn)過程和應(yīng)用要點。
創(chuàng)建bootloader工程
頂級執(zhí)行流程函數(shù)main()
在實現(xiàn)最簡單功能的bootloader工程中,缺省使用片內(nèi)的SRAM作為主內(nèi)存設(shè)備,僅僅需要完成的工作包括:
- 初始化外擴(kuò)SRAM的接口FSMC
- 在NVIC_VTOR寄存器中重定位中斷向量表的基地址。后續(xù)application工程中的中斷向量表將位于自己可執(zhí)行二進(jìn)制文件的最開始。application工程執(zhí)行過程中,將通過NVIC_VTOR寄存器和中斷向量表項的偏移值確定實際的中斷服務(wù)程序入口地址。
- 為application工程的棧指針寄存器SP(MSP/PSP)賦初值。這個初值即為application工程中的中斷向量表的第一個表項中存放的數(shù)值,這個值是由application工程的鏈接器算出來的。
最后跳轉(zhuǎn)到application可執(zhí)行程序的位置,后續(xù)執(zhí)行application工程。
為了確定從芯片上電到執(zhí)行application程序這段時間,bootloader確實按照預(yù)期正常工作,在本例實現(xiàn)的bootloader工程中使用了一個GPIO控制的小燈指示執(zhí)行過程中可能出現(xiàn)的錯誤:
- 初始化配置指示燈亮
- 如果順利執(zhí)行到跳轉(zhuǎn)到application的前一步,那么就可以熄滅指示燈,順利進(jìn)入跳轉(zhuǎn)過程
- 如果在bootloader執(zhí)行的過程中遇到任何問題,例如可以增加一個驗證外擴(kuò)SRAM可以工作的檢測過程,就在原地等待,此時指示燈將保持常亮
本例創(chuàng)建的bootloader工程中的main()
函數(shù),見代碼x。
代碼x bootloader工程中的main() 函數(shù)
/* define the memory range would be used in application firmware. */
#define BOARD_APP_EXEC_ROM_OFFSET (0x4000) /* 16KB. */
#define BOARD_APP_EXEC_ROM_BASE (0x08000000 + BOARD_APP_EXEC_ROM_OFFSET)
#define BOARD_APP_EXEC_ROM_LIMIT (0x08000000 + 0x80000)
#define BOARD_APP_EXEC_RAM_BASE (0x68000000) /* ext sram base address. */
#define BOARD_APP_EXEC_RAM_LIMIT (BOARD_APP_EXEC_RAM_BASE + 0x100000)
...
int main(void)
{
/* setup the boot clock, pins. */
BOARD_Init();
/* prepare a led to tell if everything is ok. */
app_led_init();
app_led_on();
/* setup the fsmc interface hardware for ext sram. */
app_init_sram();
/* check if the ext sram is ready. */
if (0u != app_check_image((void *)BOARD_APP_EXEC_ROM_BASE))
{
while (1); /* error: unavailable application firmware binary. */
}
/* restore as much as possible */
CLOCK_ResetToDefault();
/* turn off the led to tell every is finally ok. */
app_led_off();
/* jump to application. */
app_jump_to_image((void *)BOARD_APP_EXEC_ROM_BASE);
while (1); /* never run to this place. */
}
用戶實際開發(fā)application工程時,是不應(yīng)該感受到這個附加的bootloader工程的,因此,bootloader的執(zhí)行時間應(yīng)盡量短,執(zhí)行完畢后應(yīng)盡量復(fù)原至芯片上電復(fù)位的狀態(tài)。在本例中,為了盡量加速bootloader工程的運行,在BOARD_Init()
函數(shù)中初始化啟用的PLL,從芯片內(nèi)部的8MHz時鐘源倍頻到120MHz,用最快速度執(zhí)行完bootloader的語句后,在臨跳轉(zhuǎn)到application工程之前,又將系統(tǒng)時鐘復(fù)原成原來上電缺省使用的8MHz內(nèi)部時鐘,盡量還原到芯片剛上電后進(jìn)入用戶程序的狀態(tài)。但同外擴(kuò)內(nèi)存相關(guān)的外設(shè)資源(引腳、時鐘等),則必須保持激活狀態(tài)。
驗證固件函數(shù)app_check_image()
關(guān)于驗證bootloader跳轉(zhuǎn)的硬件環(huán)境,本例做得比較簡單,僅在app_check_image()
函數(shù)中檢查即將在application中使用的棧頂指針值和復(fù)位向量入口(中斷向量表的前兩個表項)。如果希望做得更嚴(yán)謹(jǐn)一些,可以再把即將使用的外擴(kuò)SRAM存儲空間都遍歷一遍,寫入數(shù)據(jù)之后再讀出來查看是否一致,以確認(rèn)即將使用的SRAM也是有效的。本例創(chuàng)建的app_check_image()
函數(shù),見代碼x。
代碼x 實現(xiàn)app_check_image()函數(shù)
void app_check_image(void * addr)
{
uint32_t * vectorTable = (uint32_t *)addr;
/* validate the addr for sp. */
if ((vectorTable[0] < BOARD_APP_EXEC_RAM_BASE) || (vectorTable[0] > BOARD_APP_EXEC_RAM_LIMIT ))
{
return 1u; /* unavailable sram area. */
}
/* validate the addr for pc. */
if ((vectorTable[1] < BOARD_APP_EXEC_ROM_BASE) || (vectorTable[1] > BOARD_APP_EXEC_ROM_LIMIT ))
{
return 2u; /* unavailable sram area. */
}
return 0u;
}
跳轉(zhuǎn)函數(shù)app_jump_to_image()
最關(guān)鍵的跳轉(zhuǎn)函數(shù)app_jump_to_image()
,見代碼x。
代碼x 實現(xiàn)app_jump_to_image()函數(shù)
typedef void(*func_0_t)(void);
volatile uint32_t sp_base;
volatile uint32_t pc_base;
void app_jump_to_image(void * addr)
{
uint32_t * vectorTable = (uint32_t *)addr;
sp_base = vectorTable[0];
pc_base = vectorTable[1];
/* set new MSP and PSP.
* when the SP is changed, the address of variables in stack would be remapped according to the new SP.
*/
__set_MSP(sp_base);
__set_PSP(sp_base);
#if __VTOR_PRESENT == 1
SCB- >VTOR = (uint32_t)addr; /* the func's param is kept in R1 register, which would not be changed per the SP update. */
#endif
/* jump to application. */
((func_0_t)(pc_base))();
//pc_func();
/* the code should never reach here. */
while (1)
{}
}
在app_jump_to_image()
函數(shù)中,通過傳入的即將跳轉(zhuǎn)到可執(zhí)行二進(jìn)制代碼區(qū)的首地址,提取位于可執(zhí)行文件程序開始位置的中斷向量表的前兩個表項,分別為棧頂SP指針的初始值和PC指針的初始(復(fù)位中斷服務(wù)程序入口地址),然后用各自不同的方法將它們賦值到硬件寄存器中生效:MSP和PSP寄存器可以直接使用匯編語句賦值,而PC指針不能由程序直接操作,但通過函數(shù)跳轉(zhuǎn)命令實際可以載入新的PC值。
關(guān)于跳轉(zhuǎn)之前是否重新配置SP指針(微控制器內(nèi)核中的MSP和PSP寄存器),這里也有一些考慮:
- 如果在application工程的啟動程序中,有重置棧指針的操作,那么在bootloader工程中就沒必要從application工程的文件中提取棧地址并重置棧頂指針了。但實際上大多數(shù)工程的啟動程序中都沒有這個步驟,而是依賴于微控制內(nèi)核硬件的自動行為,從中斷向量表的第一個表項中提取棧頂?shù)刂纷鳛閮?nèi)核棧指針的初值。
- 從完全模擬芯片啟動行為的角度上看,在進(jìn)入application之前,仍然需要給application工程一個位于主內(nèi)存空間中的缺省棧指針,就像bootloader工程中上電后執(zhí)行的第一條指令時,硬件就已經(jīng)自動從中斷向量表的第一個表項中提取了棧頂?shù)刂焚x給棧指針。從bootloader跳轉(zhuǎn)到application的過程中,微控制器不會自動將application中斷向量表的第一個存放棧頂?shù)刂分担布詣訛闂V羔樫x值的操作僅僅,只好由bootloader預(yù)先準(zhǔn)備好。
關(guān)于重置SP指針的影響,這里也要特別說明。當(dāng)在app_jump_to_image()
函數(shù)中執(zhí)行__set_MSP(sp_base)
語句時,當(dāng)前的棧指針就已經(jīng)變了,此時,當(dāng)前函數(shù)中使用的局部變量,還保存在原有的棧中,使用變化后的棧頂指針已經(jīng)無法訪問原有棧中的內(nèi)容了。因此,之后再使用的sp_base
和pc_base
變量都被定義成全局變量,存放在外部內(nèi)存(仍位于片內(nèi)SRAM中),而不是棧中。至于addr
變量,是來自于函數(shù)傳參,被存放在內(nèi)核的Rn寄存器中,不受棧指針變化的影響。
通過把pc_base
賦值給PC寄存器,微控制器內(nèi)核就轉(zhuǎn)而執(zhí)行新的PC指針指向的程序,從而完成了跳轉(zhuǎn)到新程序的功能。
調(diào)試
改好代碼之后,編譯工程,就可以直接下載可執(zhí)行文件到芯片中了。這個下載過程同正常下載工程沒有任何區(qū)別,還是將可執(zhí)行文件的二進(jìn)制代碼下載到片內(nèi)FLASH存儲器上。
此時,如果片內(nèi)FLASH中還沒有下載可用application文件到約定的地址上,有一定幾率被app_check_image()函數(shù)檢測為無效目標(biāo)程序,直接卡在原地,并用指示燈常量警示用戶。也可能碰巧通過了檢測,bootloader工程最后跳轉(zhuǎn)之后將會“跑飛”。
如果片內(nèi)FLASH中已經(jīng)下載了可用的application工程文件,例如后續(xù)重新調(diào)試bootloader添加新功能的開發(fā)過程,處理器內(nèi)核執(zhí)行了bootloader的跳轉(zhuǎn)語句之后,就已經(jīng)跳出了bootloader工程的控制范圍,進(jìn)入了application工程的執(zhí)行序列,屆時還需要配合application工程聯(lián)合調(diào)試。
約定分配bootloader和application工程的存儲空間
MM32F5微控制器內(nèi)部存儲空間分布,如表x所示。
bootloader工程和application工程的可執(zhí)行文件都存放在片內(nèi)FLASH存儲器上。bootloader使用片內(nèi)SRAM作為主SRAM,application使用外擴(kuò)SRAM作為主SRAM,映射到FSMC Bank3的0x68000000 - 0x68100000的1MB大小的空間上。
bootloader工程的鏈接命令文件
bootloader工程和application工程的可執(zhí)行文件都存放在片內(nèi)FLASH存儲器上。芯片上電后,缺省先執(zhí)行bootloader工程,故bootloader工程的程序位于片內(nèi)FLASH存儲空間的首部,預(yù)留16KB。bootloader使用部分片內(nèi)SRAM作為主SRAM,預(yù)留64KB,用戶也可以根據(jù)實際需要調(diào)整。
在bootloader工程的鏈接命令文件中有關(guān)于存放程序文件地址空間的定義。見代碼x。
代碼 x bootloader工程的鏈接命令文件
/*--------------------- FLASH Configuration ----------------------------------
; < h > FLASH Configuration
; < o0 > FLASH Base Address < 0x0-0xFFFFFFFF:8 >
; < o1 > FLASH Size (in Bytes) < 0x0-0xFFFFFFFF:8 >
; < /h >
*----------------------------------------------------------------------------*/
#define __ROM_BASE 0x08000000
#define __ROM_SIZE 0x00004000
/*--------------------- Embedded RAM Configuration ---------------------------
; < h > RAM Configuration
; < o0 > RAM Base Address < 0x0-0xFFFFFFFF:8 >
; < o1 > RAM Size (in Bytes) < 0x0-0xFFFFFFFF:8 >
; < /h >
*----------------------------------------------------------------------------*/
#define __RAM_BASE 0x30000000
#define __RAM_SIZE 0x00010000
application工程的鏈接命令文件
application工程的可執(zhí)行文件也保存在片內(nèi)FLASH上,位于bootloader程序文件之后。若在應(yīng)用中沒有特別的需要,application工程可占用剩余的所有FLASH存儲空間。application使用外擴(kuò)SRAM存儲作為主SRAM,映射到FSMC Bank3的0x68000000 - 0x68100000的1MB大小的空間上。
在application工程的鏈接命令文件中有關(guān)于存放程序文件地址空間的定義。見代碼x。
代碼x application工程的鏈接命令文件
/*--------------------- FLASH Configuration ----------------------------------
; < h > FLASH Configuration
; < o0 > FLASH Base Address < 0x0-0xFFFFFFFF:8 >
; < o1 > FLASH Size (in Bytes) < 0x0-0xFFFFFFFF:8 >
; < /h >
*----------------------------------------------------------------------------*/
#define __ROM_BASE 0x08004000
#define __ROM_SIZE 0x0003C000
/*--------------------- Embedded RAM Configuration ---------------------------
; < h > RAM Configuration
; < o0 > RAM Base Address < 0x0-0xFFFFFFFF:8 >
; < o1 > RAM Size (in Bytes) < 0x0-0xFFFFFFFF:8 >
; < /h >
*----------------------------------------------------------------------------*/
#define __RAM_BASE 0x68000000
#define __RAM_SIZE 0x00100000
在application工程中開發(fā)應(yīng)用
試驗并查看分配在外擴(kuò)SRAM的存儲空間
前文說到,用戶在application工程中開發(fā)應(yīng)用,不需要專門配置外擴(kuò)SRAM相關(guān)硬件的操作,即可直接使用外擴(kuò)SRAM存儲作為主存儲器。不過,在application工程中仍需要調(diào)整一下鏈接文件,將FLASH存儲空間定義到片內(nèi)FLASH存儲器上除了bootloader已經(jīng)在首部占用的其余空間,將RAM空間定義到外擴(kuò)存儲器映射的存儲空間中。這部分操作,已經(jīng)在application工程的鏈接命令文件中配置好了,不需要用戶在代碼層面做任何特殊的設(shè)置。
在樣例工程application中定義全局變量 uint8_t ch;
見代碼x。
代碼x 在application工程中定義全局變量
#include "board_init.h"
uint8_t ch;
int main(void)
{
BOARD_Init();
printf("application.rn");
while (1)
{
ch = getchar();
putchar(ch);
}
}
編譯項目后,可以查看其中的project.map
文件中,編譯器自動為全局變量分配的內(nèi)存位于外擴(kuò)存儲的內(nèi)存空間中。見代碼x。
代碼x 編譯application工程生成的project.map文件
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
...
__stdin 0x68000000 Data 4 stdin.o(.data)
__stdout 0x68000004 Data 4 stdout.o(.data)
ch 0x68000008 Data 1 main.o(.bss.ch)
Image$$ARM_LIB_STACK$$ZI$$Base 0x680ff000 Number 0 anon$$obj.o ABSOLUTE
Image$$ARM_LIB_STACK$$ZI$$Limit 0x68100000 Number 0 anon$$obj.o ABSOLUTE
...
Execution Region RW_RAM (Exec base: 0x68000000, Load base: 0x08005724, Size: 0x0000000c, Max: 0x000fe000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x68000000 0x08005724 0x00000004 Data RW 310 .data mc_w.l(stdin.o)
0x68000004 0x08005728 0x00000004 Data RW 311 .data mc_w.l(stdout.o)
0x68000008 - 0x00000001 Zero RW 20 .bss.ch main.o
Execution Region ARM_LIB_HEAP (Exec base: 0x68000010, Load base: 0x0800572c, Size: 0x00001000, Max: 0x00001000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x68000010 - 0x00001000 Zero RW 1 ARM_LIB_HEAP.bss anon$$obj.o
Execution Region ARM_LIB_STACK (Exec base: 0x680ff000, Load base: 0x0800572c, Size: 0x00001000, Max: 0x00001000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x680ff000 - 0x00001000 Zero RW 2 ARM_LIB_STACK.bss anon$$obj.o
從代碼x中也可以看到,application工程的運行時全局變量數(shù)據(jù)區(qū)(RW_RAW)、堆空間(ARM_LIB_HEAP)、棧空間(ARM_LIB_STACK)也都位于0x6800_0000開始的外擴(kuò)內(nèi)存區(qū)間。
clock_init()
雖說在application工程中不需要用戶為使用外擴(kuò)SRAM做任何特殊的設(shè)置,但由于使用了SDK代碼包,還是有一點編程要點要注意。SDK的編程規(guī)范里,有要求在芯片上電啟動過程中,用戶在使用硬件外設(shè)之前,要使用硬件復(fù)位操作復(fù)位將要使用外設(shè)模塊,以確保每次進(jìn)入main()函數(shù)時,硬件外設(shè)的狀態(tài)都是從確定的初始狀態(tài)開始工作的。但在本例中,是通過bootloader引導(dǎo)進(jìn)入的application,有一些已經(jīng)激活的外設(shè)必須保持工作狀態(tài),例如FSMC以及對應(yīng)使用的GPIO等外設(shè)模塊,是不能在application工程中復(fù)位硬件的,否則,之前bootloader的準(zhǔn)備工作就白費了,整個工程也不能正常工作。這里務(wù)必要再次確認(rèn)clock_init.c文件中BOARD_InitBootClocks()函數(shù)中,關(guān)閉對FSMC和GPIO外設(shè)的復(fù)位操作,或者不要額外操作亦可。見代碼x。
代碼x application工程的BOARD_InitBootClocks()函數(shù)
void BOARD_InitBootClocks(void)
{
CLOCK_ResetToDefault();
CLOCK_BootToHSE120MHz();
/* UART1. */
RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, true);
RCC_ResetAPB2Periphs(RCC_APB2_PERIPH_UART1);
/* GPIOA. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOA, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOA);
/* GPIOB. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);
/* GPIOC. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOC, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOC);
/* GPIOD. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOD, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOD);
/* GPIOE. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOE, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOE);
/* GPIOF. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOF, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOF);
/* GPIOG. */
//RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOG, true);
//RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOG);
}
調(diào)試
改好代碼之后,編譯工程,就可以直接下載可執(zhí)行文件到芯片中了。這個下載過程同正常下載工程沒有任何區(qū)別,還是將可執(zhí)行文件的二進(jìn)制代碼下載到片內(nèi)FLASH存儲器上。由于在設(shè)計鏈接命令文件時已經(jīng)做好約定,application工程使用的片內(nèi)FLASH存儲區(qū)同bootloader工程是錯開的,所以下載application工程的可執(zhí)行文件到片內(nèi)FLASH中不會沖掉之前下載的bootloader工程,但切記不要做全片擦除。
下載application工程后,可正常使用單步調(diào)試。這里可以理解一個要點:當(dāng)集成開發(fā)環(huán)境中啟動調(diào)試模式時,會在本工程的main()函數(shù)或者復(fù)位服務(wù)程序的第一句下一個斷點,當(dāng)用戶啟動“運行”操作后,才開始執(zhí)行后續(xù)的程序。
- 在原來沒有bootloader引導(dǎo)的工程中,芯片上電后,直接進(jìn)入用戶軟件的管轄范圍后,遇到預(yù)設(shè)的斷點就停了下來。
- 帶有bootloader引導(dǎo)的application工程中,芯片上電后,先執(zhí)行bootloader代碼(沒有預(yù)設(shè)斷點),后來跳轉(zhuǎn)到application工程后,遇到了application中預(yù)設(shè)的斷點,才開始停下來。
這個過程是集成開發(fā)環(huán)境工具自動執(zhí)行的,用戶在使用上不會有任何區(qū)別,只是可能會感受到啟動調(diào)試后到可以再次啟動“運行”操作中間等待的時間稍微長了一點。而這段等待的時間,正是bootloader在運行呢。
結(jié)論
本文探討了基于MM32F5微控制器的FSMC接口外接SRAM存儲器的用法,試圖尋找一種讓編譯器自動管理外擴(kuò)內(nèi)存的開發(fā)方法。使用bootloader工程引導(dǎo)application工程組合的方式,可以解決這個問題:在bootloader工程中初始化外擴(kuò)SRAM的相關(guān)硬件,使得application工程可以在編譯過程中就可以將外擴(kuò)SRAM用起來。最終用戶在application工程中開發(fā)自己的應(yīng)用,可以直接使用外擴(kuò)的大SRAM作為主內(nèi)存,同時也可以將片內(nèi)較小的SRAM作為輔助存儲繼續(xù)使用。
評論