一、背景
在嵌入式操作系統(tǒng)中,BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行??梢猿跏蓟布O(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個(gè)合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。在嵌入式系統(tǒng)中,通常并沒(méi)有像BIOS那樣的固件程序
二、實(shí)現(xiàn)思路
bootloader其實(shí)就是一段啟動(dòng)程序,它在芯片啟動(dòng)的時(shí)候首先被執(zhí)行,它可以用來(lái)做一些硬件的初始化,當(dāng)初始化完成之后跳轉(zhuǎn)到對(duì)應(yīng)的應(yīng)用程序中去。
我們可以將內(nèi)存分為兩個(gè)區(qū),一個(gè)是啟動(dòng)程序區(qū)(0x0800 0000 - 0x0800 2000 )大小為8K Bytes,剩下的為應(yīng)用程序區(qū)(0x0800 2000 - 0x0801 0000)。
芯片上電時(shí)先運(yùn)行啟動(dòng)程序,然后跳轉(zhuǎn)到應(yīng)用程序區(qū)執(zhí)行應(yīng)用程序。
三、程序跳轉(zhuǎn)
bootloader一個(gè)主要的功能就是首先程序的跳轉(zhuǎn)。在STM32中只要將要跳轉(zhuǎn)的地址直接寫(xiě)入PC寄存器,就可以跳轉(zhuǎn)到對(duì)應(yīng)的地址中去。
怎么實(shí)現(xiàn)呢?
當(dāng)我們實(shí)現(xiàn)一個(gè)函數(shù)的時(shí)候,這個(gè)函數(shù)最終會(huì)占用一段內(nèi)存,而它的函數(shù)名代表的就是這段內(nèi)存的起始地址。當(dāng)我們調(diào)用這個(gè)函數(shù)的時(shí)候,單片機(jī)會(huì)將這段
內(nèi)存的首地址(函數(shù)名對(duì)應(yīng)的地址)加載到PC寄存器中,從而跳轉(zhuǎn)到這段代碼來(lái)執(zhí)行。那么我們也可以利用這個(gè)原理,定義一個(gè)函數(shù)指針,將這個(gè)指針指向我們
想要跳轉(zhuǎn)的地址,然后調(diào)用這個(gè)函數(shù),就可以實(shí)現(xiàn)程序的跳轉(zhuǎn)了。
代碼如下:
#define APP_ADDR 0x08002000 //應(yīng)用程序首地址定義
typedef void (*APP_FUNC)(); //函數(shù)指針類(lèi)型定義
APP_FUNC jump2app; //定義一個(gè)函數(shù)指針
jump2app = ( APP_FUNC )(APP_ADDR + 4); //給函數(shù)指針賦值
jump2app(); //調(diào)用函數(shù)指針,實(shí)現(xiàn)程序跳轉(zhuǎn)
上面的代碼實(shí)現(xiàn)了我們要的跳轉(zhuǎn)功能,但是為什么要跳轉(zhuǎn)到(APP_ADDR + 4) 這個(gè)地址,而不是APP_ADDR.
首先我們要了解主控芯片的啟動(dòng)過(guò)程。以STM32為例,在芯片上電的時(shí)候,首先會(huì)從內(nèi)存地址位0x0800 0000(由啟動(dòng)模式?jīng)Q定)的地方加載棧頂?shù)刂罚?字節(jié)),從0x0800 0004的地方加載程序復(fù)位地址(4字節(jié)),然后跳轉(zhuǎn)到對(duì)應(yīng)的復(fù)位地址去執(zhí)行。
所以上面的程序會(huì)中,jump2app這個(gè)函數(shù)指針的地址為(APP_ADDR + 4),調(diào)用這個(gè)函數(shù)指針的時(shí)候,芯片內(nèi)核會(huì)自動(dòng)跳轉(zhuǎn)到這個(gè)指針指向的內(nèi)存地址,也即是應(yīng)用程序的復(fù)位地址。
四、加載棧地址
實(shí)際運(yùn)行會(huì)發(fā)現(xiàn),上面的程序可能會(huì)出現(xiàn)問(wèn)題。因?yàn)槲覀冞€缺少了一個(gè)棧地址的加載過(guò)程,也就是芯片上電的第一個(gè)動(dòng)作。這里要用到一點(diǎn)匯編的知識(shí):
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0
BX r14;
}
__asm void MSR_MSP(uint32_t addr) 是MDK嵌入式匯編形式。
MSR MSP, r0 意思是將r0寄存器中的值加載到MSP(主棧寄存器,復(fù)位時(shí)默認(rèn)使用)寄存器中,r0中保存的是參數(shù)值,即addr的值
BX r14 跳轉(zhuǎn)到連接寄存器保存的地址中,即退出函數(shù),跳轉(zhuǎn)到函數(shù)調(diào)用地址
完整的程序如下:
#define APP_ADDR 0x08002000 //應(yīng)用程序首地址定義
typedef void (*APP_FUNC)(); //函數(shù)指針類(lèi)型定義
/**
* @brief
* @param
* @retval
*/
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0
BX r14;
}
/**
* @brief
* @param
* @retval
*/
void run_app(uint32_t app_addr)
{
uint32_t reset_addr = 0;
APP_FUNC jump2app;
/* 跳轉(zhuǎn)之前關(guān)閉相應(yīng)的中斷 */
NVIC_DisableIRQ(SysTick_IRQn);
NVIC_DisableIRQ(LPUART_IRQ);
/* 棧頂?shù)刂肥欠窈戏?這里sram大小為8k) */
if(((*(uint32_t *)app_addr)&0x2FFFE000) == 0x20000000)
{
/* 設(shè)置棧指針 */
MSR_MSP(app_addr);
/* 獲取復(fù)位地址 */
reset_addr = *(uint32_t *)(app_addr+4);
jump2app = ( APP_FUNC )reset_addr;
jump2app();
}
else
{
printf("APP Not Found!n");
}
}
五、編譯設(shè)置
我們需要在設(shè)置界面將默認(rèn)(0x8000000)改為我們的應(yīng)用程序地址(0x8002000)
六、中斷向量表重映射
完成了上面的工作,實(shí)際測(cè)試發(fā)現(xiàn)程序還是無(wú)法正確運(yùn)行。原因是我們沒(méi)有進(jìn)行中斷向量表的重映射。向量表映射?什么時(shí)候有做過(guò)這個(gè)工作,我們來(lái)看一下:
.s文件里有如下代碼:
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
這代碼表示,程序在執(zhí)行main函數(shù)之前,會(huì)先執(zhí)行SystemInit這個(gè)函數(shù)。下面看看這個(gè)函數(shù):
/**
* @brief Setup the microcontroller system.
* @param None
* @retval None
*/
void SystemInit (void)
{
/*!< Set MSION bit */
RCC->CR |= (uint32_t)0x00000100U;
/*!< Reset SW[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], MCOSEL[2:0] and MCOPRE[2:0] bits */
RCC->CFGR &= (uint32_t) 0x88FF400CU;
/*!< Reset HSION, HSIDIVEN, HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFF6U;
/*!< Reset HSI48ON bit */
RCC->CRRCR &= (uint32_t)0xFFFFFFFEU;
/*!< Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFFU;
/*!< Reset PLLSRC, PLLMUL[3:0] and PLLDIV[1:0] bits */
RCC->CFGR &= (uint32_t)0xFF02FFFFU;
/*!< Disable all interrupts */
RCC->CIER = 0x00000000U;
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
從上面的代碼可以看到,這個(gè)函數(shù)主要是做了時(shí)鐘的初始化和中斷初始化,還有就是中斷向量表的映射,就是最后那一段代碼
再看看FLASH_BASE 和 VECT_TAB_OFFSET的定義:
這里默認(rèn)映射地址就是FLASH的初始地址,所以只要將其改成我們程序的起始地址就行了: SCB->VTOR = 0x08002000
編譯,運(yùn)行,下載.
七、總結(jié)
程序跳轉(zhuǎn)完成,對(duì)于bootloader來(lái)說(shuō)也就完成了一大半。剩下的就是根據(jù)自己的需求去完善相應(yīng)功能了,比如我的在線升級(jí)功能,就要在bootloader里做固件接收和校驗(yàn)的功能。這里有一點(diǎn)需要特別注意的是,跳轉(zhuǎn)程序之前最好把你用到的中斷都關(guān)了,不然跳轉(zhuǎn)之后的程序沒(méi)有對(duì)應(yīng)的中斷處理函數(shù),那就又可能使得程序進(jìn)入死循環(huán)中。
審核編輯:符乾江
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19126瀏覽量
305302 -
Boot
+關(guān)注
關(guān)注
0文章
149瀏覽量
35840
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論