STM32F0xx在增加IAP后APP為什么在main函數(shù)中要重映射SRAM
1 前言
在使用F0的片子在增加IAP后,我們經(jīng)常發(fā)現(xiàn),原來的APP必須增加一段代碼,將中斷向量表從內(nèi)部FLASH拷貝到SRAM后再執(zhí)行REMAP到SRAM,這樣操作后APP才能正常運(yùn)行,這一過程一直困擾著蝶粉們,為什么需要這樣呢?本文將針對這一問題為大家解惑。
2 問題詳細(xì)描述比如F0的下面這部分代碼:
#defineAPPLICATION_ADDRESS(uint32_t)0x08004000/*Privatemacro-------------------------------------------------------------*//*Privatevariables---------------------------------------------------------*/#if(defined(__CC_ARM))__IOuint32_tVectorTable[48]__attribute__((at(0x20000000)));#elif(defined(__ICCARM__))#pragmalocation=0x20000000__no_init__IOuint32_tVectorTable[48];#elifdefined(__GNUC__)__IOuint32_tVectorTable[48]__attribute__((section(".RAMVectorTable")));#endifintmain(void){uint32_ti=0;HAL_Init();/*Configurethesystemclockto48MHz*/SystemClock_Config();/*RelocatebysoftwarethevectortabletotheinternalSRAMat0x20000000***//*CopythevectortablefromtheFlash(mappedatthebaseoftheapplicationloadaddress0x08004000)tothebaseaddressoftheSRAMat0x20000000.*/for(i=0;i<48;i++){VectorTable[i]=*(__IOuint32_t*)(APPLICATION_ADDRESS+(i<<2));}/*EnabletheSYSCFGperipheralclock*/__HAL_RCC_SYSCFG_CLK_ENABLE();/*RemapSRAMat0x00000000*/__HAL_SYSCFG_REMAPMEMORY_SRAM();/*Addyourowncodehere...*///…}123456789101112131415161718192021222324252627282930313233343536373839123456789101112131415161718192021222324252627282930313233343536373839
如上所示,在沒有加入IAP之前這部分代碼不需要的,而加入IAP后,這部分代碼在F0中就必須添加了,不然APP會運(yùn)行部正常! 從代碼中可以看出,增加的代碼執(zhí)行了從內(nèi)部FLASH中拷貝中斷向量表到內(nèi)存,然后重映射SRAM,可是,蝶粉們不禁要問,為什么要這么操作?這個又是什么原因造成的?
3 原因分析3.1 重映射
在搞懂這個問題之前我們首先先來看看什么是重映射。例如F072的參考文檔張SYSCFG寄存器的介紹,如下圖:
MEM_MODE的介紹如下:
從以上內(nèi)容我們可以得到以下信息:
1. MEM_MODE的值在上電后有BOOT0,BOOT1的狀態(tài)值決定。
2. MEM_MODE的值決定了哪個內(nèi)存映射到地址0x0000 0000
也就是說:
? 當(dāng)MEM_MODE =00/10時,Main Flash映射到地址0x0000 0000,即地址0x0800 0000映射到0x0000 0000.
? 當(dāng)MEM_MODE =01時,System Flash映射到地址0x0000 0000,也就是芯片自帶的Bootloader代碼部分會映射到地址0x0000 0000,即0x1FFF C800映射到地址0x0000 0000.
? 當(dāng)MEM_MODE =11時,Embeded SRAM映射到地址0x0000 0000,也就是內(nèi)存地址0x2000 0000映射到地址0x0000 0000.
3. 經(jīng)過映射后,系統(tǒng)訪問地址0x0000 0000地址,就相當(dāng)于直接訪問映射的地址,如0x0800 0000.
4. 由BOOT0,BOOT1的狀態(tài)決定MEM_MODE的值,進(jìn)而決定哪個地址映射到地址0x0000 0000,這一過程我們稱之為映射。默認(rèn)映射是系統(tǒng)自動完成的,并由BOOT0,BOOT1的狀態(tài)決定。
5. MEM_MODE位是RW的,也就是說可以修改的,如果修改其中,也就會相應(yīng)的修改映射到0x0000 0000的地址,這一修改的過程,我們就叫其為重映射。重映射是通過用戶代碼通過修改MEM_MODE的值來完成的。
從STM32F072的參考手冊的2.5章,我們可以看到如下內(nèi)容:
從以上內(nèi)容我們可以得到以下有用信息:
1. 在復(fù)位啟動后,系統(tǒng)在系統(tǒng)時鐘的第4個上升沿根據(jù)BOOT0,BOOT1的配置獲取其值,也就是存儲到寄存器SYSCFG_CFGR1的MEM_MODE位上,根據(jù)前面3.1的信息可知,這里進(jìn)一步確定了0x0000 0000的映射地址。這一過程是系統(tǒng)自動完成的。
2. 在系統(tǒng)啟動后,CPU從地址0x0000 0000獲取棧頂?shù)刂?,然后?x0000 0004開始執(zhí)行代碼。換句話說,由于0x0000 0000被映射了其他地址,獲取棧頂與執(zhí)行實(shí)際上都是從映射的地址上實(shí)施的。也就是從映射的地址開始執(zhí)行代碼,比如從地址0x08000 0004開始執(zhí)行代碼(如Mian Flash映射),比如0x1FFF C804(如System Flash映射,即BootLoader啟動).
于是,我們簡單整理下系統(tǒng)的整個啟動流程:
-> 系統(tǒng)復(fù)位
-> CPU在系統(tǒng)時鐘的第4個上升沿根據(jù)BOOT0,BOOT1的配置確定寄存器SYSCFG_CFGR1的MEM_MODE的值
-> MEM_MODE進(jìn)一步?jīng)Q定哪個地址(Main Flash,System Flash,SRAM)映射到地址0x0000 0000.
-> CPU從地址0x0000 0000獲取棧頂,從0x0000 0004開始執(zhí)行代碼,也就是從映射地址獲取棧頂,從映射地址+4的地方開始執(zhí)行代碼。
->映射地址+4對于著復(fù)位中斷例程(如0x08000 0004),也就是系統(tǒng)一開始就執(zhí)行Reset_Handler,進(jìn)而運(yùn)行SystemInit然后進(jìn)入到main函數(shù),就這樣,整個代碼啟動完成。
接下來就是中斷產(chǎn)生于中斷響應(yīng)了。
3.3 中斷與中斷向量表從ARM官網(wǎng)上的信息得知(http://infocenter.arm.com/help/index.jsp),在Coretext-M3與Coretext-M4核中,在System Control Block中存在一個向量表偏移量寄存器 VTOR(0xE000ED08),系統(tǒng)產(chǎn)生中斷后,內(nèi)核通過這個寄存器的值來找到中斷向量表的地址,進(jìn)而執(zhí)行中斷例程代碼,當(dāng)然,此寄存器的值是可以修改的,它的默認(rèn)值為0。實(shí)際上在大部分的M3和M4的工程中,一般都是在SystemInit函數(shù)中對此寄存器的值進(jìn)行設(shè)置,當(dāng)此之前,它的值為默認(rèn)值0,由于映射關(guān)系,實(shí)際上就是指向映射地址,比如0x0800 0000. 如下圖所示:
但是,由于STM32F0XX采用的是M0核,它是沒有這個VTOR寄存器的,那么它又是怎么找到中斷向量表的地址的呢?同樣在ARM官網(wǎng)上我們找到如下信息:
也就是說,對于M0來說,中斷向量表的地址固定在地址0x0000 0000上!
對此,我們也可以這么理解,將M0理解成M3/M4的特殊情況,M0假設(shè)也存在VTOR這么一個虛擬寄存器,只不過它的值不能修改,固定為0罷了,而M3/M4的這個VTOR寄存器一開始時它的值也是為默認(rèn)值0,只不過在程序運(yùn)行到SystemInit()函數(shù)后,在代碼中明確對其進(jìn)行了修改。
我們整理下STM32F0XX中斷的調(diào)用過程:
-> 產(chǎn)生中斷
-> CPU固定到地址0x0000 0000上找中斷入口函數(shù),由于映射關(guān)系,實(shí)際上是在從映射地址上尋找。
-> 找到并執(zhí)行中斷例程
此時,可以回到我們一開始的問題上來了.
3.4 回到問題分析3.4.1 從IAP跳轉(zhuǎn)到APP的過程分析從IAP跳轉(zhuǎn)到APP時到底發(fā)生了什么呢?首先我們來看看這個跳轉(zhuǎn)代碼:
/*Testifusercodeisprogrammedstartingfromaddress"APPLICATION_ADDRESS"*/if(((*(__IOuint32_t*)APPLICATION_ADDRESS)&0x2FFE0000)==0x20000000){/*Jumptouserapplication*/JumpAddress=*(__IOuint32_t*)(APPLICATION_ADDRESS+4);JumpToApplication=(pFunction)JumpAddress;/*Initializeuserapplication'sStackPointer*/__set_MSP(*(__IOuint32_t*)APPLICATION_ADDRESS);JumpToApplication();}12345678910111234567891011
如上,首先測試APP地址是否存在用戶代碼,如果存在,則首先將APPLICATION_ADDRESS + 4,作為跳轉(zhuǎn)地址,然后從APPLICATION_ADDRESS取棧頂并賦值給SP寄存器,最后跳轉(zhuǎn)。
于是我們可以得出結(jié)論:
跳轉(zhuǎn)代碼后,PC指針變了,SP棧指針也修改了,但是,中斷向量表的位置并沒有修正為APP的中斷向量表位置,還是采用IAP的中斷向量表位置。如果此時系統(tǒng)產(chǎn)生中斷,CPU還會固定從0x0000 0000地址中去找中斷入口,由于從IAP到APP,內(nèi)存映射并沒有改變,因此,實(shí)際上還是從0x0800 0000上去找中斷入口,也就是還是在IAP的中斷向量表上尋找,繼續(xù)執(zhí)行IAP的中斷例程,進(jìn)而引發(fā)hard fault,這顯然不是我們想要的。
這里的關(guān)鍵是,如何將中斷向量表的尋找位置從0x0800 0000修改到0x0800 3000(假設(shè)為APP的地址)? 從之前的分析我們可以得出有兩種方法:
1 修改寄存器VTOR的值
2 內(nèi)存重映射
M3/M4核的MCU顯然采用了第一種方法,因此,在M3/M4的MCU上,即使添加了IAP,也不需要增加本文第二章所描述的代碼。而這部分代碼,正是M0采用的第二種方法,也是唯一可以修改尋找中斷向量方式的方法。
通過將SRAM