引言
MM32F5微控制器基于Arm STAR-MC1微控制器,最高主頻可達(dá)120MHz,集成了FPU單元和DSP擴(kuò)展指令集,有不錯(cuò)的算力。但片內(nèi)集成的128KB的RAM和256KB的FLASH,如果想支持代碼量比較大的軟件框架,就可能會(huì)力不從心,例如,TensorFlow Lite或者基于MicroPython的OpenMV這樣的應(yīng)用就需要更多的內(nèi)存空間做緩存。但MM32F5微控制器帶有FSMC接口和QSPI接口并支持基于QSPI的XIP(eXecute In Place,就地執(zhí)行),可以分別外擴(kuò)SRAM和FLASH存儲(chǔ)器,這就為擴(kuò)展存儲(chǔ)資源提供了可能。在本文中,將介紹使用FSMC接口外接SRAM擴(kuò)展內(nèi)存的過(guò)程。在后續(xù)的文章中,在后續(xù)文章中,還會(huì)繼續(xù)介紹使用QSPI對(duì)接qspiflash存儲(chǔ)器實(shí)現(xiàn)外擴(kuò)FLASH的過(guò)程。
硬件電路
MM32F5微控制器上集成了FSMC(Flexable Static Memory Controller)接口,可以外接并口的SRAM存儲(chǔ)器。
在PLUS-F5270開(kāi)發(fā)板上,對(duì)應(yīng)外擴(kuò)了一個(gè)1MB大小的PSRAM存儲(chǔ)器作為擴(kuò)展內(nèi)存,如圖x所示。
圖x FSMC對(duì)接SRAM存儲(chǔ)器
軟件設(shè)計(jì)
使用FSMC接口外擴(kuò)的SRAM存儲(chǔ)設(shè)備之前,必須先激活微控制器的FSMC接口,包括啟用對(duì)FSMC接口外設(shè)的訪問(wèn)開(kāi)關(guān)、配置FSMC接口對(duì)應(yīng)的外部引腳,以及配置FSMC的時(shí)鐘源和工作模式等操作。基于這樣的使用前提,一般情況下使用外擴(kuò)SRAM,都是在應(yīng)用程序中激活FSMC硬件外設(shè)接口,之后通過(guò)在指定地址分配內(nèi)存,或者訪問(wèn)絕對(duì)地址的方式訪問(wèn)新擴(kuò)展出來(lái)的內(nèi)存,但此時(shí)默認(rèn)的主內(nèi)存還是片內(nèi)的SRAM。這種使用擴(kuò)展SRAM的方式對(duì)于規(guī)模較小或者綁定具體應(yīng)用的項(xiàng)目,因?yàn)樯婕暗綄?duì)代碼的改動(dòng)以及對(duì)存儲(chǔ)管理的工作量較小并且明確,在一定程度上是可以接受的。但對(duì)于移植已有現(xiàn)有的項(xiàng)目,或者是規(guī)模較大的框架性軟件,開(kāi)發(fā)者通常不愿意(也不建議)深入到代碼庫(kù)中去人為指定每個(gè)可能的全局變量的絕對(duì)地址,僅將管理的目標(biāo)地址區(qū)間從片內(nèi)SRAM轉(zhuǎn)移到了外擴(kuò)的SRAM而已,而希望能夠一如既往地讓編譯器自動(dòng)管理內(nèi)存的分配機(jī)制。
編譯器自動(dòng)管理內(nèi)存,就涉及到在芯片上電初始化過(guò)程中對(duì)編譯器運(yùn)行時(shí)環(huán)境的初始化過(guò)程中對(duì)堆棧進(jìn)行初始化,配置棧頂和棧底、堆底和堆頂指針等,也包括將內(nèi)存中BSS段的數(shù)據(jù)清零,將DATA段數(shù)據(jù)的初值從FLASH搬運(yùn)到SRAM中等。這些操作的過(guò)程,大多被封裝在集成開(kāi)發(fā)環(huán)境自帶的庫(kù)中(例如Keil的__main
函數(shù),經(jīng)過(guò)一系列同編譯器相關(guān)的準(zhǔn)備工作后才跳轉(zhuǎn)到用戶的main()
函數(shù)),不開(kāi)放給用戶修改,而其中使用的和計(jì)算出的內(nèi)存地址,也都是在編譯過(guò)程中預(yù)先定義的。
- 如果用戶強(qiáng)行在鏈接命令文件中指定默認(rèn)的主內(nèi)存空間為外擴(kuò)存儲(chǔ),那么在芯片啟動(dòng)過(guò)程中,預(yù)定義的初始化運(yùn)行時(shí)環(huán)境的操作,將會(huì)在未初始化好FSMC接口等硬件的時(shí)候直接訪問(wèn)FSMC擴(kuò)展出的內(nèi)存空間,必然出錯(cuò)??赡軙?huì)提示的錯(cuò)誤是hardfault,標(biāo)記為訪問(wèn)了無(wú)效的地址。此時(shí),若是用戶在集成開(kāi)發(fā)環(huán)境的
__main
函數(shù)之前的SystemInit()
函數(shù)中先激活FSMC等外擴(kuò)SRAM相關(guān)的硬件也是可行的,但必須要注意,這個(gè)過(guò)程中,除了CPU中僅有的寄存器外,不能使用任何棧內(nèi)存,因?yàn)榇藭r(shí)燒寫(xiě)在默認(rèn)的中斷向量表首位的棧頂?shù)刂匪赶虻目臻g還是不可訪問(wèn)的。具體來(lái)說(shuō),就需要用匯編命令完成對(duì)所有相關(guān)硬件外設(shè)的初始化操作,這確實(shí)是一個(gè)考驗(yàn)人耐心的事情。這里簡(jiǎn)單看一下SDK中的復(fù)位中斷服務(wù)程序中的啟動(dòng)程序代碼,見(jiàn)代碼x。
代碼x SDK中的啟動(dòng)程序代碼
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ù)高超的工程師可能會(huì)想到一些巧妙的做法,能不能先用缺省的片內(nèi)SRAM支持編譯工具鏈的初始化過(guò)程,然后在應(yīng)用程序中初始化FSMC外設(shè)(此時(shí)仍使用片內(nèi)SRAM),然后再試圖重建內(nèi)存管理系統(tǒng),將芯片系統(tǒng)中內(nèi)存相關(guān)的指針人為重建在外擴(kuò)SRAM中呢?且不說(shuō)這是一個(gè)極其麻煩的過(guò)程,需要把編譯工具鏈中的每個(gè)同內(nèi)存相關(guān)的配置變量都翻出來(lái)重新人為計(jì)算并賦值一遍,一個(gè)明顯的限制在于,所有將要放在外擴(kuò)的大SRAM中的數(shù)據(jù)必須在較小的片內(nèi)SRAM中必須預(yù)先存放一份副本,之后在應(yīng)用程序運(yùn)行的過(guò)程中轉(zhuǎn)移到外擴(kuò)SRAM。此時(shí),在編譯階段,編譯器會(huì)限定整個(gè)程序能使用的存儲(chǔ)空間不能大于片內(nèi)SRAM的大小,否則編譯器仍然會(huì)報(bào)錯(cuò)并拒絕生成可執(zhí)行文件。這就限制了能夠直接使用外擴(kuò)SRAM的空間,同最常用的將外擴(kuò)SRAM當(dāng)成輔助存儲(chǔ)空間的做法沒(méi)有實(shí)質(zhì)區(qū)別。
使用bootloader初始化硬件環(huán)境的思路
為了讓用戶的工程直接在一個(gè)可用的外擴(kuò)SRAM上建立存儲(chǔ)管理系統(tǒng),一個(gè)可行的設(shè)計(jì),是使用額外使用一個(gè)bootloader工程(或者在芯片內(nèi)部的電路實(shí)現(xiàn)上直接用一段ROM承載bootloader工程中的操作),在使用少量片內(nèi)SRAM的條件下,通過(guò)常規(guī)的調(diào)用驅(qū)動(dòng)API的方式(而不用匯編語(yǔ)句序列),先準(zhǔn)備好使用FSMC的硬件環(huán)境,例如配置時(shí)鐘系統(tǒng)、引腳復(fù)用功能、FSMC接口外設(shè)等等。在bootloader工程最后的部分,直接跳轉(zhuǎn)到一個(gè)約定的、存放用戶application工程的地址,開(kāi)始自行application工程。在application是一個(gè)完全獨(dú)立的工程,不用激活FSMC就可以直接使用外擴(kuò)的SRAM,因此可以利用編譯工具鏈直接在外擴(kuò)的SRAM上重建存儲(chǔ)管理系統(tǒng)。在application工程中,用戶將完全不用干預(yù)內(nèi)存的分配情況,就像之前一樣完全交由編譯器自行管理;由于不再使用bootloader工程,片內(nèi)的SRAM可以作為獨(dú)立的一塊可用的存儲(chǔ)空間(就像之前在片內(nèi)SRAM看外擴(kuò)SRAM一樣),繼續(xù)為應(yīng)用程序提供存儲(chǔ)服務(wù)。
進(jìn)一步分析,探討把bootloader工程放到ROM中的可能性。程序一旦寫(xiě)入ROM中,就不能有任何改變了。但在配置外擴(kuò)SRAM的時(shí)候,仍需要人為指定外擴(kuò)SRAM映射的地址范圍(開(kāi)始地址和空間大?。?,這個(gè)設(shè)定在不同應(yīng)用場(chǎng)景中可能會(huì)不一樣,受成本和功能的權(quán)衡,可能有時(shí)候會(huì)用或大或小的存儲(chǔ)器設(shè)備,因具體選型不同配置參數(shù)也會(huì)隨之發(fā)生變化,因此不適合直接固化在ROM中。除非是對(duì)應(yīng)合封的芯片,需要固定規(guī)格的SRAM芯片的晶元已經(jīng)同微控制器一起被封在芯片內(nèi)部,倒是不失為一種高集成的SOC解決方案?;蛘咭部梢杂妙愃苹卣{(diào)的方式,由用戶在某種基礎(chǔ)的協(xié)議下向ROM中的小程序提供外擴(kuò)SRAM芯片專屬的配置,例如在手冊(cè)里說(shuō)明在特定的用戶可編程的FLASH存儲(chǔ)區(qū)中存放了關(guān)于SRAM的配置信息,也是可行的,但處理過(guò)程就多了幾個(gè)步驟。沒(méi)有擴(kuò)展多種SRAM的情況下,實(shí)在沒(méi)必要在芯片里設(shè)計(jì)這么一塊ROM。不過(guò),這種方式在外擴(kuò)FLASH的時(shí)候確實(shí)用到了,在后續(xù)的文章中將會(huì)提及,外擴(kuò)FLASH的各廠家設(shè)計(jì)生產(chǎn)的nor FLASH型號(hào)芯片在使用上存在差異,在flashless的芯片中必須在ROM中設(shè)計(jì)程序首先識(shí)別外擴(kuò)spiflash芯片的型號(hào),從而使用對(duì)應(yīng)的配置信息初始化spiflash芯片,到時(shí)也將會(huì)有一番細(xì)致地闡述。
接下來(lái),將詳細(xì)介紹創(chuàng)建bootloader工程和用戶在application工程中開(kāi)發(fā)應(yīng)用的實(shí)現(xiàn)過(guò)程和應(yīng)用要點(diǎn)。
創(chuàng)建bootloader工程
頂級(jí)執(zhí)行流程函數(shù)main()
在實(shí)現(xiàn)最簡(jiǎn)單功能的bootloader工程中,缺省使用片內(nèi)的SRAM作為主內(nèi)存設(shè)備,僅僅需要完成的工作包括:
- 初始化外擴(kuò)SRAM的接口FSMC
- 在NVIC_VTOR寄存器中重定位中斷向量表的基地址。后續(xù)application工程中的中斷向量表將位于自己可執(zhí)行二進(jìn)制文件的最開(kāi)始。application工程執(zhí)行過(guò)程中,將通過(guò)NVIC_VTOR寄存器和中斷向量表項(xiàng)的偏移值確定實(shí)際的中斷服務(wù)程序入口地址。
- 為application工程的棧指針寄存器SP(MSP/PSP)賦初值。這個(gè)初值即為application工程中的中斷向量表的第一個(gè)表項(xiàng)中存放的數(shù)值,這個(gè)值是由application工程的鏈接器算出來(lái)的。
最后跳轉(zhuǎn)到application可執(zhí)行程序的位置,后續(xù)執(zhí)行application工程。
為了確定從芯片上電到執(zhí)行application程序這段時(shí)間,bootloader確實(shí)按照預(yù)期正常工作,在本例實(shí)現(xiàn)的bootloader工程中使用了一個(gè)GPIO控制的小燈指示執(zhí)行過(guò)程中可能出現(xiàn)的錯(cuò)誤:
- 初始化配置指示燈亮
- 如果順利執(zhí)行到跳轉(zhuǎn)到application的前一步,那么就可以熄滅指示燈,順利進(jìn)入跳轉(zhuǎn)過(guò)程
- 如果在bootloader執(zhí)行的過(guò)程中遇到任何問(wèn)題,例如可以增加一個(gè)驗(yàn)證外擴(kuò)SRAM可以工作的檢測(cè)過(guò)程,就在原地等待,此時(shí)指示燈將保持常亮
本例創(chuàng)建的bootloader工程中的main()
函數(shù),見(jiàn)代碼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. */
}
用戶實(shí)際開(kāi)發(fā)application工程時(shí),是不應(yīng)該感受到這個(gè)附加的bootloader工程的,因此,bootloader的執(zhí)行時(shí)間應(yīng)盡量短,執(zhí)行完畢后應(yīng)盡量復(fù)原至芯片上電復(fù)位的狀態(tài)。在本例中,為了盡量加速bootloader工程的運(yùn)行,在BOARD_Init()
函數(shù)中初始化啟用的PLL,從芯片內(nèi)部的8MHz時(shí)鐘源倍頻到120MHz,用最快速度執(zhí)行完bootloader的語(yǔ)句后,在臨跳轉(zhuǎn)到application工程之前,又將系統(tǒng)時(shí)鐘復(fù)原成原來(lái)上電缺省使用的8MHz內(nèi)部時(shí)鐘,盡量還原到芯片剛上電后進(jìn)入用戶程序的狀態(tài)。但同外擴(kuò)內(nèi)存相關(guān)的外設(shè)資源(引腳、時(shí)鐘等),則必須保持激活狀態(tài)。
驗(yàn)證固件函數(shù)app_check_image()
關(guān)于驗(yàn)證bootloader跳轉(zhuǎn)的硬件環(huán)境,本例做得比較簡(jiǎn)單,僅在app_check_image()
函數(shù)中檢查即將在application中使用的棧頂指針值和復(fù)位向量入口(中斷向量表的前兩個(gè)表項(xiàng))。如果希望做得更嚴(yán)謹(jǐn)一些,可以再把即將使用的外擴(kuò)SRAM存儲(chǔ)空間都遍歷一遍,寫(xiě)入數(shù)據(jù)之后再讀出來(lái)查看是否一致,以確認(rèn)即將使用的SRAM也是有效的。本例創(chuàng)建的app_check_image()
函數(shù),見(jiàn)代碼x。
代碼x 實(shí)現(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()
,見(jiàn)代碼x。
代碼x 實(shí)現(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ù)中,通過(guò)傳入的即將跳轉(zhuǎn)到可執(zhí)行二進(jìn)制代碼區(qū)的首地址,提取位于可執(zhí)行文件程序開(kāi)始位置的中斷向量表的前兩個(gè)表項(xiàng),分別為棧頂SP指針的初始值和PC指針的初始(復(fù)位中斷服務(wù)程序入口地址),然后用各自不同的方法將它們賦值到硬件寄存器中生效:MSP和PSP寄存器可以直接使用匯編語(yǔ)句賦值,而PC指針不能由程序直接操作,但通過(guò)函數(shù)跳轉(zhuǎn)命令實(shí)際可以載入新的PC值。
關(guān)于跳轉(zhuǎn)之前是否重新配置SP指針(微控制器內(nèi)核中的MSP和PSP寄存器),這里也有一些考慮:
- 如果在application工程的啟動(dòng)程序中,有重置棧指針的操作,那么在bootloader工程中就沒(méi)必要從application工程的文件中提取棧地址并重置棧頂指針了。但實(shí)際上大多數(shù)工程的啟動(dòng)程序中都沒(méi)有這個(gè)步驟,而是依賴于微控制內(nèi)核硬件的自動(dòng)行為,從中斷向量表的第一個(gè)表項(xiàng)中提取棧頂?shù)刂纷鳛閮?nèi)核棧指針的初值。
- 從完全模擬芯片啟動(dòng)行為的角度上看,在進(jìn)入application之前,仍然需要給application工程一個(gè)位于主內(nèi)存空間中的缺省棧指針,就像bootloader工程中上電后執(zhí)行的第一條指令時(shí),硬件就已經(jīng)自動(dòng)從中斷向量表的第一個(gè)表項(xiàng)中提取了棧頂?shù)刂焚x給棧指針。從bootloader跳轉(zhuǎn)到application的過(guò)程中,微控制器不會(huì)自動(dòng)將application中斷向量表的第一個(gè)存放棧頂?shù)刂分?,硬件自?dòng)為棧指針賦值的操作僅僅,只好由bootloader預(yù)先準(zhǔn)備好。
關(guān)于重置SP指針的影響,這里也要特別說(shuō)明。當(dāng)在app_jump_to_image()
函數(shù)中執(zhí)行__set_MSP(sp_base)
語(yǔ)句時(shí),當(dāng)前的棧指針就已經(jīng)變了,此時(shí),當(dāng)前函數(shù)中使用的局部變量,還保存在原有的棧中,使用變化后的棧頂指針已經(jīng)無(wú)法訪問(wèn)原有棧中的內(nèi)容了。因此,之后再使用的sp_base
和pc_base
變量都被定義成全局變量,存放在外部?jī)?nèi)存(仍位于片內(nèi)SRAM中),而不是棧中。至于addr
變量,是來(lái)自于函數(shù)傳參,被存放在內(nèi)核的Rn寄存器中,不受棧指針變化的影響。
通過(guò)把pc_base
賦值給PC寄存器,微控制器內(nèi)核就轉(zhuǎn)而執(zhí)行新的PC指針指向的程序,從而完成了跳轉(zhuǎn)到新程序的功能。
調(diào)試
改好代碼之后,編譯工程,就可以直接下載可執(zhí)行文件到芯片中了。這個(gè)下載過(guò)程同正常下載工程沒(méi)有任何區(qū)別,還是將可執(zhí)行文件的二進(jìn)制代碼下載到片內(nèi)FLASH存儲(chǔ)器上。
此時(shí),如果片內(nèi)FLASH中還沒(méi)有下載可用application文件到約定的地址上,有一定幾率被app_check_image()函數(shù)檢測(cè)為無(wú)效目標(biāo)程序,直接卡在原地,并用指示燈常量警示用戶。也可能碰巧通過(guò)了檢測(cè),bootloader工程最后跳轉(zhuǎn)之后將會(huì)“跑飛”。
如果片內(nèi)FLASH中已經(jīng)下載了可用的application工程文件,例如后續(xù)重新調(diào)試bootloader添加新功能的開(kāi)發(fā)過(guò)程,處理器內(nèi)核執(zhí)行了bootloader的跳轉(zhuǎn)語(yǔ)句之后,就已經(jīng)跳出了bootloader工程的控制范圍,進(jìn)入了application工程的執(zhí)行序列,屆時(shí)還需要配合application工程聯(lián)合調(diào)試。
約定分配bootloader和application工程的存儲(chǔ)空間
MM32F5微控制器內(nèi)部存儲(chǔ)空間分布,如表x所示。
bootloader工程和application工程的可執(zhí)行文件都存放在片內(nèi)FLASH存儲(chǔ)器上。bootloader使用片內(nèi)SRAM作為主SRAM,application使用外擴(kuò)SRAM作為主SRAM,映射到FSMC Bank3的0x68000000 - 0x68100000的1MB大小的空間上。
bootloader工程的鏈接命令文件
bootloader工程和application工程的可執(zhí)行文件都存放在片內(nèi)FLASH存儲(chǔ)器上。芯片上電后,缺省先執(zhí)行bootloader工程,故bootloader工程的程序位于片內(nèi)FLASH存儲(chǔ)空間的首部,預(yù)留16KB。bootloader使用部分片內(nèi)SRAM作為主SRAM,預(yù)留64KB,用戶也可以根據(jù)實(shí)際需要調(diào)整。
在bootloader工程的鏈接命令文件中有關(guān)于存放程序文件地址空間的定義。見(jià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)用中沒(méi)有特別的需要,application工程可占用剩余的所有FLASH存儲(chǔ)空間。application使用外擴(kuò)SRAM存儲(chǔ)作為主SRAM,映射到FSMC Bank3的0x68000000 - 0x68100000的1MB大小的空間上。
在application工程的鏈接命令文件中有關(guān)于存放程序文件地址空間的定義。見(jià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工程中開(kāi)發(fā)應(yīng)用
試驗(yàn)并查看分配在外擴(kuò)SRAM的存儲(chǔ)空間
前文說(shuō)到,用戶在application工程中開(kāi)發(fā)應(yīng)用,不需要專門(mén)配置外擴(kuò)SRAM相關(guān)硬件的操作,即可直接使用外擴(kuò)SRAM存儲(chǔ)作為主存儲(chǔ)器。不過(guò),在application工程中仍需要調(diào)整一下鏈接文件,將FLASH存儲(chǔ)空間定義到片內(nèi)FLASH存儲(chǔ)器上除了bootloader已經(jīng)在首部占用的其余空間,將RAM空間定義到外擴(kuò)存儲(chǔ)器映射的存儲(chǔ)空間中。這部分操作,已經(jīng)在application工程的鏈接命令文件中配置好了,不需要用戶在代碼層面做任何特殊的設(shè)置。
在樣例工程application中定義全局變量 uint8_t ch;
見(jiàn)代碼x。
代碼x 在application工程中定義全局變量
#include "board_init.h"
uint8_t ch;
int main(void)
{
BOARD_Init();
printf("application.rn");
while (1)
{
ch = getchar();
putchar(ch);
}
}
編譯項(xiàng)目后,可以查看其中的project.map
文件中,編譯器自動(dòng)為全局變量分配的內(nèi)存位于外擴(kuò)存儲(chǔ)的內(nèi)存空間中。見(jiàn)代碼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工程的運(yùn)行時(shí)全局變量數(shù)據(jù)區(qū)(RW_RAW)、堆空間(ARM_LIB_HEAP)、棧空間(ARM_LIB_STACK)也都位于0x6800_0000開(kāi)始的外擴(kuò)內(nèi)存區(qū)間。
clock_init()
雖說(shuō)在application工程中不需要用戶為使用外擴(kuò)SRAM做任何特殊的設(shè)置,但由于使用了SDK代碼包,還是有一點(diǎn)編程要點(diǎn)要注意。SDK的編程規(guī)范里,有要求在芯片上電啟動(dòng)過(guò)程中,用戶在使用硬件外設(shè)之前,要使用硬件復(fù)位操作復(fù)位將要使用外設(shè)模塊,以確保每次進(jìn)入main()函數(shù)時(shí),硬件外設(shè)的狀態(tài)都是從確定的初始狀態(tài)開(kāi)始工作的。但在本例中,是通過(guò)bootloader引導(dǎo)進(jìn)入的application,有一些已經(jīng)激活的外設(shè)必須保持工作狀態(tài),例如FSMC以及對(duì)應(yīng)使用的GPIO等外設(shè)模塊,是不能在application工程中復(fù)位硬件的,否則,之前bootloader的準(zhǔn)備工作就白費(fèi)了,整個(gè)工程也不能正常工作。這里務(wù)必要再次確認(rèn)clock_init.c文件中BOARD_InitBootClocks()函數(shù)中,關(guān)閉對(duì)FSMC和GPIO外設(shè)的復(fù)位操作,或者不要額外操作亦可。見(jiàn)代碼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í)行文件到芯片中了。這個(gè)下載過(guò)程同正常下載工程沒(méi)有任何區(qū)別,還是將可執(zhí)行文件的二進(jìn)制代碼下載到片內(nèi)FLASH存儲(chǔ)器上。由于在設(shè)計(jì)鏈接命令文件時(shí)已經(jīng)做好約定,application工程使用的片內(nèi)FLASH存儲(chǔ)區(qū)同bootloader工程是錯(cuò)開(kāi)的,所以下載application工程的可執(zhí)行文件到片內(nèi)FLASH中不會(huì)沖掉之前下載的bootloader工程,但切記不要做全片擦除。
下載application工程后,可正常使用單步調(diào)試。這里可以理解一個(gè)要點(diǎn):當(dāng)集成開(kāi)發(fā)環(huán)境中啟動(dòng)調(diào)試模式時(shí),會(huì)在本工程的main()函數(shù)或者復(fù)位服務(wù)程序的第一句下一個(gè)斷點(diǎn),當(dāng)用戶啟動(dòng)“運(yùn)行”操作后,才開(kāi)始執(zhí)行后續(xù)的程序。
- 在原來(lái)沒(méi)有bootloader引導(dǎo)的工程中,芯片上電后,直接進(jìn)入用戶軟件的管轄范圍后,遇到預(yù)設(shè)的斷點(diǎn)就停了下來(lái)。
- 帶有bootloader引導(dǎo)的application工程中,芯片上電后,先執(zhí)行bootloader代碼(沒(méi)有預(yù)設(shè)斷點(diǎn)),后來(lái)跳轉(zhuǎn)到application工程后,遇到了application中預(yù)設(shè)的斷點(diǎn),才開(kāi)始停下來(lái)。
這個(gè)過(guò)程是集成開(kāi)發(fā)環(huán)境工具自動(dòng)執(zhí)行的,用戶在使用上不會(huì)有任何區(qū)別,只是可能會(huì)感受到啟動(dòng)調(diào)試后到可以再次啟動(dòng)“運(yùn)行”操作中間等待的時(shí)間稍微長(zhǎng)了一點(diǎn)。而這段等待的時(shí)間,正是bootloader在運(yùn)行呢。
結(jié)論
本文探討了基于MM32F5微控制器的FSMC接口外接SRAM存儲(chǔ)器的用法,試圖尋找一種讓編譯器自動(dòng)管理外擴(kuò)內(nèi)存的開(kāi)發(fā)方法。使用bootloader工程引導(dǎo)application工程組合的方式,可以解決這個(gè)問(wèn)題:在bootloader工程中初始化外擴(kuò)SRAM的相關(guān)硬件,使得application工程可以在編譯過(guò)程中就可以將外擴(kuò)SRAM用起來(lái)。最終用戶在application工程中開(kāi)發(fā)自己的應(yīng)用,可以直接使用外擴(kuò)的大SRAM作為主內(nèi)存,同時(shí)也可以將片內(nèi)較小的SRAM作為輔助存儲(chǔ)繼續(xù)使用。
評(píng)論