STM32F0xx在增加IAP后APP為什么在main函數(shù)中要重映射SRAM
掃描二維碼
隨時(shí)隨地手機(jī)看文章
1 前言
在使用F0的片子在增加IAP后,我們經(jīng)常發(fā)現(xiàn),原來(lái)的APP必須增加一段代碼,將中斷向量表從內(nèi)部FLASH拷貝到SRAM后再執(zhí)行REMAP到SRAM,這樣操作后APP才能正常運(yùn)行,這一過(guò)程一直困擾著蝶粉們,為什么需要這樣呢?本文將針對(duì)這一問(wèn)題為大家解惑。
2 問(wèn)題詳細(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
如上所示,在沒(méi)有加入IAP之前這部分代碼不需要的,而加入IAP后,這部分代碼在F0中就必須添加了,不然APP會(huì)運(yùn)行部正常! 從代碼中可以看出,增加的代碼執(zhí)行了從內(nèi)部FLASH中拷貝中斷向量表到內(nèi)存,然后重映射SRAM,可是,蝶粉們不禁要問(wèn),為什么要這么操作?這個(gè)又是什么原因造成的?
3 原因分析3.1 重映射
在搞懂這個(gè)問(wèn)題之前我們首先先來(lái)看看什么是重映射。例如F072的參考文檔張SYSCFG寄存器的介紹,如下圖:
MEM_MODE的介紹如下:
從以上內(nèi)容我們可以得到以下信息:
1. MEM_MODE的值在上電后有BOOT0,BOOT1的狀態(tài)值決定。
2. MEM_MODE的值決定了哪個(gè)內(nèi)存映射到地址0x0000 0000
也就是說(shuō):
? 當(dāng)MEM_MODE =00/10時(shí),Main Flash映射到地址0x0000 0000,即地址0x0800 0000映射到0x0000 0000.
? 當(dāng)MEM_MODE =01時(shí),System Flash映射到地址0x0000 0000,也就是芯片自帶的Bootloader代碼部分會(huì)映射到地址0x0000 0000,即0x1FFF C800映射到地址0x0000 0000.
? 當(dāng)MEM_MODE =11時(shí),Embeded SRAM映射到地址0x0000 0000,也就是內(nèi)存地址0x2000 0000映射到地址0x0000 0000.
3. 經(jīng)過(guò)映射后,系統(tǒng)訪問(wèn)地址0x0000 0000地址,就相當(dāng)于直接訪問(wèn)映射的地址,如0x0800 0000.
4. 由BOOT0,BOOT1的狀態(tài)決定MEM_MODE的值,進(jìn)而決定哪個(gè)地址映射到地址0x0000 0000,這一過(guò)程我們稱之為映射。默認(rèn)映射是系統(tǒng)自動(dòng)完成的,并由BOOT0,BOOT1的狀態(tài)決定。
5. MEM_MODE位是RW的,也就是說(shuō)可以修改的,如果修改其中,也就會(huì)相應(yīng)的修改映射到0x0000 0000的地址,這一修改的過(guò)程,我們就叫其為重映射。重映射是通過(guò)用戶代碼通過(guò)修改MEM_MODE的值來(lái)完成的。
從STM32F072的參考手冊(cè)的2.5章,我們可以看到如下內(nèi)容:
從以上內(nèi)容我們可以得到以下有用信息:
1. 在復(fù)位啟動(dòng)后,系統(tǒng)在系統(tǒng)時(shí)鐘的第4個(gè)上升沿根據(jù)BOOT0,BOOT1的配置獲取其值,也就是存儲(chǔ)到寄存器SYSCFG_CFGR1的MEM_MODE位上,根據(jù)前面3.1的信息可知,這里進(jìn)一步確定了0x0000 0000的映射地址。這一過(guò)程是系統(tǒng)自動(dòng)完成的。
2. 在系統(tǒng)啟動(dòng)后,CPU從地址0x0000 0000獲取棧頂?shù)刂?,然后?x0000 0004開(kāi)始執(zhí)行代碼。換句話說(shuō),由于0x0000 0000被映射了其他地址,獲取棧頂與執(zhí)行實(shí)際上都是從映射的地址上實(shí)施的。也就是從映射的地址開(kāi)始執(zhí)行代碼,比如從地址0x08000 0004開(kāi)始執(zhí)行代碼(如Mian Flash映射),比如0x1FFF C804(如System Flash映射,即BootLoader啟動(dòng)).
于是,我們簡(jiǎn)單整理下系統(tǒng)的整個(gè)啟動(dòng)流程:
-> 系統(tǒng)復(fù)位
-> CPU在系統(tǒng)時(shí)鐘的第4個(gè)上升沿根據(jù)BOOT0,BOOT1的配置確定寄存器SYSCFG_CFGR1的MEM_MODE的值
-> MEM_MODE進(jìn)一步?jīng)Q定哪個(gè)地址(Main Flash,System Flash,SRAM)映射到地址0x0000 0000.
-> CPU從地址0x0000 0000獲取棧頂,從0x0000 0004開(kāi)始執(zhí)行代碼,也就是從映射地址獲取棧頂,從映射地址+4的地方開(kāi)始執(zhí)行代碼。
->映射地址+4對(duì)于著復(fù)位中斷例程(如0x08000 0004),也就是系統(tǒng)一開(kāi)始就執(zhí)行Reset_Handler,進(jìn)而運(yùn)行SystemInit然后進(jìn)入到main函數(shù),就這樣,整個(gè)代碼啟動(dòng)完成。
接下來(lái)就是中斷產(chǎn)生于中斷響應(yīng)了。
3.3 中斷與中斷向量表從ARM官網(wǎng)上的信息得知(http://infocenter.arm.com/help/index.jsp),在Coretext-M3與Coretext-M4核中,在System Control Block中存在一個(gè)向量表偏移量寄存器 VTOR(0xE000ED08),系統(tǒng)產(chǎn)生中斷后,內(nèi)核通過(guò)這個(gè)寄存器的值來(lái)找到中斷向量表的地址,進(jìn)而執(zhí)行中斷例程代碼,當(dāng)然,此寄存器的值是可以修改的,它的默認(rèn)值為0。實(shí)際上在大部分的M3和M4的工程中,一般都是在SystemInit函數(shù)中對(duì)此寄存器的值進(jìn)行設(shè)置,當(dāng)此之前,它的值為默認(rèn)值0,由于映射關(guān)系,實(shí)際上就是指向映射地址,比如0x0800 0000. 如下圖所示:
但是,由于STM32F0XX采用的是M0核,它是沒(méi)有這個(gè)VTOR寄存器的,那么它又是怎么找到中斷向量表的地址的呢?同樣在ARM官網(wǎng)上我們找到如下信息:
也就是說(shuō),對(duì)于M0來(lái)說(shuō),中斷向量表的地址固定在地址0x0000 0000上!
對(duì)此,我們也可以這么理解,將M0理解成M3/M4的特殊情況,M0假設(shè)也存在VTOR這么一個(gè)虛擬寄存器,只不過(guò)它的值不能修改,固定為0罷了,而M3/M4的這個(gè)VTOR寄存器一開(kāi)始時(shí)它的值也是為默認(rèn)值0,只不過(guò)在程序運(yùn)行到SystemInit()函數(shù)后,在代碼中明確對(duì)其進(jìn)行了修改。
我們整理下STM32F0XX中斷的調(diào)用過(guò)程:
-> 產(chǎn)生中斷
-> CPU固定到地址0x0000 0000上找中斷入口函數(shù),由于映射關(guān)系,實(shí)際上是在從映射地址上尋找。
-> 找到并執(zhí)行中斷例程
此時(shí),可以回到我們一開(kāi)始的問(wèn)題上來(lái)了.
3.4 回到問(wèn)題分析3.4.1 從IAP跳轉(zhuǎn)到APP的過(guò)程分析從IAP跳轉(zhuǎn)到APP時(shí)到底發(fā)生了什么呢?首先我們來(lái)看看這個(gè)跳轉(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
如上,首先測(cè)試APP地址是否存在用戶代碼,如果存在,則首先將APPLICATION_ADDRESS + 4,作為跳轉(zhuǎn)地址,然后從APPLICATION_ADDRESS取棧頂并賦值給SP寄存器,最后跳轉(zhuǎn)。
于是我們可以得出結(jié)論:
跳轉(zhuǎn)代碼后,PC指針變了,SP棧指針也修改了,但是,中斷向量表的位置并沒(méi)有修正為APP的中斷向量表位置,還是采用IAP的中斷向量表位置。如果此時(shí)系統(tǒng)產(chǎn)生中斷,CPU還會(huì)固定從0x0000 0000地址中去找中斷入口,由于從IAP到APP,內(nèi)存映射并沒(mé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采用的第二種方法,也是唯一可以修改尋找中斷向量方式的方法。
通過(guò)將SRAM