第一章 背景知識
什麼是IAP?
IAP的知識網上的各種資料也說的比較明白,在此簡單介紹一下。IAP( In Application Programming)即在線應用編程,也就是用戶可以使用自己的程序對單片機的User Flash的某一區域(一般爲存放自己程序的區域)進行燒寫。在真正的工作中產品發佈後,可以很方便的使用預留的通信接口(串口、USB、網口、藍牙等)來完成程序的升級,從而避免了把機器拆開使用下載器燒寫程序。
要實現IAP功能一般要設計兩部分代碼,
- 一是BootLoader程序,這部分程序存儲在FLASH的某一位置,主要用來引導、升級App程序;
- 二是App程序,這個程序纔是實現產品的功能程序。通過BootLoader來完成對App程序的更新升級,這就是IAP功能。
最終要實現的是:
- 單片機每次上電會先運行Boot程序,檢查標誌位如果標誌位爲FLAG_TO_APP則直接跳轉到App程序運行,如果標誌位爲FLAG_TO_BOOT,則運行Boot程序準備升級。
- App程序在運行時,正常情況下正常完成其功能,當接收到升級的指令後(升級指令一般來自某個外設的中斷,如上位機從串口發來升級指令,或者上位機從網絡發來升級指令)會在FLASH中的某處空間(參數區)寫下升級的標誌位FLAG_TO_BOOT,並且加載Boot程序,Boot程序會接受新的程序文件並且存儲在相應的FLASH空間裏,完成升級後會在標誌位的空間寫下FLAG_TO_APP,並且運行新的程序。
一、stm32的內存映射
參考博文:STM32 IAP 在線升級詳解
操作前我們先來說一下內存映射:
下圖在stm32f100芯片手冊的29頁,我們只截取關鍵部分
注意: 根據啓動方式不同,地址空間中從0x0000 0000到0x07FF FFFF這段空間,可以是flash空間或system memory的映射(別名)。
一般而言,flash內存的開始地址是0x0800 0000.
所以我們需要先查看一下misc.h文件中的中斷向量表的初始位置宏定義爲 NVIC_VectTab_Flash 0x0800 0000
還要設置編譯器keil 中的 options for target 的target選項中的 IROM1地址 爲0x0800 0000 大小爲 0x20000即128K; IRAM1地址爲0x2000 0000 大小爲0x2000;
(提示:這一項IROM1 地址 即爲當前程序下載到flash的地址的起始位置)
二、解析STM32的啓動方式
具體的啓動文件和解讀,請見另一篇博文:stm32–啓動文件(.s)與啓動過程
當前的嵌入式應用程序開發過程裏,並且C語言成爲了絕大部分場合的最佳選擇。如此一來main函數似乎成爲了理所當然的起點——因爲C程序往往從main函數開始執行。但一個經常會被忽略的問題是:微控制器(單片機)上電後,是如何尋找到並執行main函數的呢?很顯然微控制器無法從硬件上定位main函數的入口地址,因爲使用C語言作爲開發語言後,變量/函數的地址便由編譯器在編譯時自行分配,這樣一來main函數的入口地址在微控制器的內部存儲空間中不再是絕對不變的。相信讀者都可以回答這個問題,答案也許大同小異,但肯定都有個關鍵詞,叫“啓動文件”,用英文單詞來描述是“Bootloader”。
無論性能高下,結構簡繁,價格貴賤,每一種微控制器(處理器)都必須有啓動文件,啓動文件的作用便是負責執行微控制器從“復位”到“開始執行main函數”中間這段時間(稱爲啓動過程)所必須進行的工作。最爲常見的51,AVR或MSP430等微控制器當然也有對應啓動文件,但開發環境往往自動完整地提供了這個啓動文件,不需要開發人員再行干預啓動過程,只需要從main函數開始進行應用程序的設計即可。
話題轉到STM32微控制器,無論是keil uvision4還是IAR EWARM開發環境,ST公司都提供了現成的直接可用的啓動文件,程序開發人員可以直接引用啓動文件後直接進行C應用程序的開發。這樣能大大減小開發人員從其它微控制器平臺跳轉至STM32平臺,也降低了適應STM32微控制器的難度(對於上一代ARM的當家花旦ARM9,啓動文件往往是第一道難啃卻又無法逾越的坎)。
相對於ARM上一代的主流ARM7/ARM9內核架構,新一代Cortex內核架構的啓動方式有了比較大的變化。ARM7/ARM9內核的控制器在復位後,CPU會從存儲空間的絕對地址0x000000取出第一條指令執行復位中斷服務程序的方式啓動,即固定了復位後的起始地址爲0x000000(PC = 0x000000)同時中斷向量表的位置並不是固定的。
而Cortex-M3內核則正好相反,有3種情況:
- 1、 通過boot引腳設置可以將中斷向量表定位於SRAM區,即起始地址爲0x2000000,同時復位後PC指針位於0x2000000處;
- 2、 通過boot引腳設置可以將中斷向量表定位於FLASH區,即起始地址爲0x8000000,同時復位後PC指針位於0x8000000處(最常用的方式);
- 3、 通過boot引腳設置可以將中斷向量表定位於內置Bootloader區(system memory:0x1FFF F000—0x1FFF F7FF),本文不對這種情況做論述;
而Cortex-M3內核規定,起始地址必須存放堆頂指針,而第二個地址則必須存放復位中斷入口向量地址,這樣在Cortex-M3內核復位後,會自動從起始地址的下一個32位空間取出復位中斷入口向量,跳轉執行復位中斷服務程序。對比ARM7/ARM9內核,Cortex-M3內核則是固定了中斷向量表的位置而起始地址是可變化的。
三、stm32的啓動過程
(1)沒有IAP,只有APP時的正常啓動流程:
STM32的FLASH地址起始於0x08000000,程序文件就從此地址開始寫入。此外STM32內部通過“中斷向量表”來響應中斷,程序啓動後,將首先從“中斷向量表”取出復位中斷向量執行復位中斷程序完成啓動,而“中斷向量表”的起始地址是0x08000004,當中斷來臨,STM32的內部硬件機制亦會自動將PC指針定位到“中斷向量表”處,並根據中斷源取出對應的中斷向量執行中斷服務程序。
根據上圖分析啓動和運行過程,
① STM32在復位後,先從0X08000004地址取出復位中斷向量的地址,並跳轉到復位中斷服務程序,
② 在復位中斷服務程序執行完之後,會跳轉到的main函數(如使用KEIL MDK調試時一下載進程序,會發現需要運行幾次下一步纔會跳轉到main函數的位置)
③ main函數一般都是超循環體(while(1)死循環),在main函數執行過程中,如果收到中斷請求(發生重中斷),此時STM32強制將PC指針指回中斷向量表處
④ 根據中斷源進入相應的中斷服務程序,
⑤ 在執行完中斷服務程序以後,程序再次返回main函數執行。
(2)加入IAP後的啓動流程
根據上圖分析加入IAP後的起動和運行過程
① STM32復位後,還是從0X08000004地址取出復位中斷向量的地址,並跳轉到復位中斷服務程序,在運行完復位中斷服務程序之後跳轉到IAP的main函數,如將IAP看作是一個APP的話,那麼此部分和正常起動是一樣的。(此步=執行復位中斷服務程序+跳轉main,即將正常運行的①和②合併了)。
② 在執行完IAP以後(固件升級或直接跳轉),跳轉至APP的復位向量表(APP的復位中斷向量起始地址爲0X08000004+N+M)。
③ 取出APP的復位中斷向量的地址,並跳轉執行新程序的復位中斷服務程序,隨後跳轉至APP的main函數(此步=執行復位中斷服務程序+跳轉main)
④ 同樣main函數爲一個超循環,並且注意到此時STM32的FLASH,在不同位置上,共有兩個中斷向量表。在main函數執行過程中,如果CPU得到一箇中斷請求,PC指針仍強制跳轉到地址0X08000004中斷向量表處,而不是APP程序的中斷向量表。
⑤ 程序再根據我們設置的中斷向量表偏移量,跳轉到對應中斷源的APP的中斷服務程序中,
⑥ 在執行完中斷服務程序後,程序返回main函數繼續運行。
四、IAP時flash的分配
IAP時,flash空間如何分佈?
例子1:
我們在設計程序時把FLASH分成3部分,第一部分從0x08000000到0x0800FFFF共64K來存放BootLoader程序,第二部分爲0x08010000 到0x0802FFFF共128K來存放App程序,第三部分從0x08030000開始到0x803FFFF共64K來存放程序運行的標誌位和其他,如下所示:
例子2:
參考:如何使用STM32F4的BootLoader和APP程序
怎麼使用stm32寫IAP的bootloader和APP
-
假設我用的是stm32f103c8t6,它的flash的大小是64k,所以把它分成如上所示:
0x08000000 —0x0800 33FF分配給bootloader使用,大小是13k
0x0800 3400----0x080097FF分配給第一個APP的使用,大小是25k
0x08009800----0x0800 FBFF分配給第二個APP(或者緩存遠程下載的新的代碼文件)的使用,大小是25k
0x0800FC00----0x0800 FFFF 分配給user_flag和其它標誌使用,大小是1k -
在keil中設置rom的大小(因爲bootloader和每個app都是單獨的工程,所以要分別設置其rom的大小)
A、bootloader中rom大小的設置
B、APP1中rom大小的設置
C、APP2(緩衝區)中rom大小的設置
第二章 具體步驟
參考:STM32具備升級功能的bootloader及APP/IAP的實現
附件:示例工程鏈接
https://github.com/zengzhaorong/stm32_IAP-demo
一、整體規劃
整體上,在flash上燒寫2個程序,bootloader和APP。
bootloader程序位於0x80000000處,即默認的程序啓動地址;
APP程序則位於bootloader程序的往後某地址,空間大小需自行定義。
flash空間規劃
STM32F103RCT6的flash大小爲256K。如我flash空間分配如下:
二、bootloader
bootloader的主要功能:校驗數據、啓動APP、升級APP。
bootloader的工作流程如下:
1、基礎功能初始化(時鐘、外設等);
主要進行一些BSP板級初始化:(僅供參考,因工程而異)
/******************************************************************/
/**
* BSP初始化函數\n
*
*/
/******************************************************************/
void BSP_Init(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
/* Initialize GPIO */
MX_GPIO_Init();
/* Initialize usart */
MX_UART_Init();
/* Initialize timer */
Timer_ParamInit();
MX_TIM3_Init();
MX_TIM4_Init();
}
2、數據校驗(參數區信息、APP程序的檢驗)
此步驟爲後續啓動或升級過程讀取一些基礎參數(存於參數區),以及校驗數據的準確性等。
流程:先讀取參數,判斷下一步是直接啓動APP還是留在bootloader等待升級。若是啓動APP則校驗APP程序數據是否正常。
- 讀取參數區的參數:如我將升級相關的參數寫在此分區(目前僅是啓動標誌,具體可自行定義),根據此標誌來判斷下一步該如何走。
/* flash參數區信息結構 */
struct param_info
{
UINT16 usStartFlag; // 啓動標誌 0x0A-跳至APP 0x0B-等待升級 0x0F-已強制啓動過
}
- 校驗數據:如我將校驗APP程序區的數據是否正常,採用CRC校驗。
3、跳轉APP或升級APP。
3.1 如何跳轉至APP呢?
跳轉函數:
/*****************************************************************/
/**
* 加載APP \n
*
*/
/******************************************************************/
void loadAPP(INT32U unLoadAddr)
{
void (*fnJump2APP)(void);
INT32U unJumpAddr;
if(((*(__IO INT32U *)unLoadAddr) & 0x2FFE0000) == 0x20000000) /* 檢查棧頂地址是否合法 */
{
printf("%s: ----------------------> run APP addr: 0x%x\r\n", __FUNCTION__, unLoadAddr);
/* 用戶代碼區第5~8字節爲程序開始地址(復位地址) */
unJumpAddr = *(__IO INT32U *)(unLoadAddr + 4);
fnJump2APP = (void (*)(void))unJumpAddr;
/* 初始化APP堆棧指針(用戶代碼區的前4個字節用於存放棧頂地址) */
__set_MSP(*(__IO INT32U *)unLoadAddr);
fnJump2APP();
}
else
{
printf("ERROR: %s: Stack top address is not valid! Can not run func!\r\n", __FUNCTION__);
while(1);
}
}
關於跳轉部分的代碼的理解(轉)
這裏重點說一下幾句經典且非常重要的代碼:
第一句: if ((((__IO uint32_t)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //判斷棧定地址值是否在0x2000 0000 - 0x 2000 2000之間
怎麼理解呢? (1)在程序裏#define ApplicationAddress 0x8003000 ,那麼*(__IO uint32_t*)ApplicationAddress) 即取0x8003000開始到0x8003003 的4個字節的值,(2) 因爲我們的應用程序APP中設置把 中斷向量表 放置在0x08003000 開始的位置;而中斷向量表裏第一個放的就是棧頂地址的值。
也就是說,這句話即通過判斷棧頂地址值是否正確(是否在0x2000 0000 - 0x 2000 2000之間,因爲棧頂地址就是整個地址空間中SRAM所在的位置) 來判斷是否應用程序已經下載了,因爲應用程序的啓動文件剛開始就去初始化棧空間,如果棧頂值對了,說應用程已經下載了並且啓動文件的初始化也執行了;
第二句: JumpAddress = (__IO uint32_t) (ApplicationAddress + 4); [ common.c文件第18行定義了: pFunction Jump_To_Application;]
ApplicationAddress + 4 即爲0x0800 3004 ,裏面放的是中斷向量表的第二項“復位地址” JumpAddress = (__IO uint32_t) (ApplicationAddress + 4); 之後此時JumpAddress
第三句: Jump_To_Application = (pFunction) JumpAddress;
此時Jump_To_Application指向了復位函數所在的地址。
( startup_stm32f10x_md_lv. 文件中別名 typedef void (pFunction)(void);
void (pFunction)(void); 是聲明一個函數指針,加上一個typedef 之後 pFunction只不過是類型 void ()(void) 的一個別名)
第四 、五句: __set_MSP((__IO uint32_t*) ApplicationAddress); \設置主函數棧指針
Jump_To_Application(); \執行復位函數
3.2 升級APP
升級,即是將新的程序數據替換舊的程序數據,因此,只需在程序數據所在區域擦除舊數據再寫上新數據即可。
具體而言,無非就是一包一包地接收數據,然後一包一包地寫入flash。
最好分爲兩個階段,防止升級文件傳輸不完整或損壞,導致變磚:
- 文件下發階段:接收到的升級包不要直接寫入flash的APP區域,先存到另外的flash空間或外部存儲區(如SD卡);
- 更新階段:等全部接收完成,再一次性將外部存儲空間的數據寫入flash的APP區域。
其中,通信是通過串口,自己定義協議傳送數據,將程序數據拆成N個包,一包一包地傳輸,例如設定3條協議,分別是:開始升級、發送升級數據、升級完成確認(自定義啦)。
程序數據位置CPU內部flash區,因此需要以flash的讀寫擦等函數操作爲基礎。
/****************************************************************
* Func :
* Desc : 讀取CPU內部flash
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_read(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
if(pData == NULL)
return -1;
memcpy(pData, (INT8U *)unStartAddr, usSize);
return 0;
}
/****************************************************************
* Func :
* Desc : 寫入CPU內部flash (要先erase才能寫)
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_write(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
INT32 i = 0;
INT32 nRet = 0;
UINT16 usTemp1 = 0;
UINT16 usTemp2 = 0;
UINT16 usTempALL = 0;
if(usSize%2 != 0)
{
usSize += 1;
}
HAL_FLASH_Unlock(); // unlock
for(i=0; i<usSize/2; i++)
{
usTemp1 = *pData;
usTemp2 = *(pData+1);
usTempALL = ((usTemp1&0X00FF) | ((usTemp2<<8)&0XFF00));
//usTemp = ((*pData>>8)&0X00FF) | (*(pData+1)&0XFF00);
//usTemp = *(INT16U *)pData;/*這個會導致硬件崩潰*/
nRet = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, unStartAddr, usTempALL);
if(nRet != HAL_OK)
{
HAL_FLASH_Lock(); // lock
printf("ERROR: %s: program[%d %d] failed-code[%d]\n", __FUNCTION__, usTemp1, usTemp2, nRet);
return -1;
}
unStartAddr += 2;
pData += 2;
}
HAL_FLASH_Lock(); // lock
return 0;
}
/****************************************************************
* Func :
* Desc : 擦除CPU內部flash(整頁)
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_erase(UINT32 unStartAddr, UINT32 unEndAddr)
{
FLASH_EraseInitTypeDef stEraseInit;
UINT32 ucPageErr = 0;
UINT32 unTempAddr = 0;
INT32 nRet = 0;
HAL_FLASH_Unlock(); // unlock
for(unTempAddr=unStartAddr; unTempAddr<=unEndAddr; unTempAddr+=FLASH_PAGE_SIZE)
{
stEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
stEraseInit.PageAddress = unTempAddr;
stEraseInit.NbPages = 1;
nRet = HAL_FLASHEx_Erase(&stEraseInit, &ucPageErr);
if(nRet != HAL_OK)
{
HAL_FLASH_Lock();
return -1;
}
GPIO_feedDog();
}
HAL_FLASH_Lock(); // lock
return 0;
}
順便說下,STM32內部flash庫的保護問題,若不加保護,則內部程序可輕易被J-Flash等工具讀出。因此,常用的措施是:對內部flash添加讀寫保護機制。鎖定與解除函數如下:
/****************************************************************
* Func :
* Desc : 使能讀保護函數
* Input :
* Output:
* Return:
*****************************************************************/
void cpuflash_enableReadProtect(void)
{
FLASH_OBProgramInitTypeDef OBInit;
__HAL_FLASH_PREFETCH_BUFFER_DISABLE();
HAL_FLASHEx_OBGetConfig(&OBInit);
if(OBInit.RDPLevel == OB_RDP_LEVEL_0)
{
printf("%s: ------------ set ----------\n", __FUNCTION__);
OBInit.OptionType = OPTIONBYTE_RDP;
OBInit.RDPLevel = OB_RDP_LEVEL_1;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&OBInit);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
//HAL_FLASH_OB_Launch();
}
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
}
/****************************************************************
* Func :
* Desc : 失能讀保護函數
* Input :
* Output:
* Return:
*****************************************************************/
void cpuflash_disableReadProtect(void)
{
FLASH_OBProgramInitTypeDef OBInit;
__HAL_FLASH_PREFETCH_BUFFER_DISABLE();
HAL_FLASHEx_OBGetConfig(&OBInit);
if(OBInit.RDPLevel == OB_RDP_LEVEL_1)
{
printf("%s: ------------ set ----------\n", __FUNCTION__);
OBInit.OptionType = OPTIONBYTE_RDP;
OBInit.RDPLevel = OB_RDP_LEVEL_0;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&OBInit);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
//HAL_FLASH_OB_Launch();
}
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
}
三、APP程序的編寫
APP的主要功能: 1、除升級功能外的所有應用功能, 2、跳轉至bootloader準備升級。
- 中斷向量表重映射:由於APP程序的起始地址的變化,所以導致我們的中斷向量表也整體偏移了,所以需要在app程序起始添加一行代碼,否則程序異常。
例如偏移0x10000(根據實際使用做相應改動)
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x1000);
位於APP程序的main函數第一句。
- 跳轉至bootloader,有兩種方式:
1、用類似bootloader的跳轉函數,
2、直接重啓reboot(軟重啓)。
void SoftReset(void)
{
__set_FAULTMASK(1); // ¹Ø±ÕËùÓÐÖжË
NVIC_SystemReset(); // ¸´Î»
}
關於APP與IAP互跳之間的中斷處理問題
跳轉時中斷問題還是一個比較棘手的問題。。經常跳轉之後無法進入中斷,自己理解大概是,跳轉時只是強制改變了PC指正的位置,但是裏面的中斷寄存器什麼的都沒有變,這樣中斷存在,但是中斷函數什麼的都沒有了,造成程序死掉。。我在寫的過程中也遇到了問題,第一次從iap跳到app正常,但是從app跳回iap的時候由於殘留的中斷太多,在iap中程序死了。
處理方式(具體可參考文章http://dzdesigned80.blog.163.com/blog/static/203259238201272425313152/):
1、把app中的跳轉命令換成了系統復位NVIC_SystemReset();(不同的固件庫可能函數名不同)
2、跳轉之前復位或者關閉所有打開的中斷
3、跳轉後在初始化時加入RCC_DeInit();,,NVIC_DeInit ();等讓中斷恢復默認值。
第三章 如何生成、下載、保存和更新app的固件(IDE界面操作)
參考博文:stm32實現iap遠程固件更新
雖然本文標題是實現遠程固件更新,但是具體遠程方案本文不做詳細說明,重點在於介紹mcu接收到新的固件後怎麼保存更新,以及更新失敗回滾等。
下面簡單說明一下遠程的事情。
stm32的通信方式有串口,spi,iic,以及sdio等。也就是說我們的固件可以通過這些方式傳輸到mcu,不過普遍常用的是串口或者用sdio(外接sd卡)這兩種方式。簡單點還是再加一個串口網絡模塊,然後把固件存到服務器,經由串口網絡模塊透傳到mcu。比如用http協議把固件發送下來。遠程下載就這麼簡單一說。
接下來重點分析更新的事情。
固件生成
遠程更新使用的固件和我們平時燒錄程序用的固件格式有點區別,我們需要用二進制格式(.bin)文件。生成方式以mdk爲例介紹一下,只需要添加一條命令行。
在mdk工程配置選項選擇User,這個頁面是讓我們添加自定義命令行的,我們要添加的命令添加到第三個選項,即在編譯完成後執行。
下面是命令內容,需要注意的是 bin前面兩個-,app1.bin就是生成的固件,名字可以自定義,**.axf是你工程實際的.axf文件,路徑要正確。不知道你的axf在那在output頁面查看。
fromelf.exe --bin -o ../app1.bin ./**.axf
現在我們就可以生成bin文件了,但是還差一點步驟。
一般使用下mcu啓動後會自動把0x0800 0000映射到地址0x0000 0000,然後取指令執行。但是現在我們程序可以理解成分成了兩部分。
可以看到加入iap升級功能後我們app的起始地址變了,所以對應工程也要做這部分修改
如圖,我這裏把地址偏移了0x20000,同時在Linker中把“Use Memory Layout from Target Dialog”勾選,讓我們的修改生效。
如此設置以後就一切ok了
關於固件有一點需要注意,因爲起始地址修改了,所以導致我們的中斷向量表也整體偏移了,所以需要在app程序起始添加一行代碼,本文是偏移0x20000,根據實際使用做相應改動
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x2000);
固件保存
第一步:把固件下載到板子的緩存區,緩存區可以是SD卡或者flash的某塊專用區域(該區域不是bootloader和APP的區域)
)
第二步:下到板子後,我們需要把固件保存到內置flash(APP區域)對應的地址。
本步驟可分爲三個部分:
- 寫入之前,還應該加一些必要的文件完整性檢查,比如使用校驗等方式。
- 寫入flash
- 然後在flash特定區域(參數區)立一個flag,通知BootLoader程序更新固件。
如何讀寫flash第二章的代碼:
cpuflash_write{
...
}
本文上面設置的偏移是0x2 0000,所以此處寫入flash的地址也必須是0x802 0000(0x800 0000 + 0x2 0000)
固件更新
現在萬事具備了,接下來就是更新的事情了,簡單說一下更新的思路。
上電啓動後運行BootLoader程序,在bootloader中檢查是否是否需要更新,不需要的話就引導之前的app程序運行,需要更新就引導新的app程序。
引導步驟大體就是重置棧頂指針,強制跳轉app的reset復位中斷。
代碼見第二章的
void loadAPP(INT32U unLoadAddr)
{
...
}