轉自:https://www.cnblogs.com/smulngy/p/5700283.html
首先說一下什么是IAP。IAP(In Application Programming)即在應用編程,IAP是用戶自己的程序在運行過程中對User Flash的部分區域進行燒寫,目的是為了在產品發布后可以方便地通過預留的通信口對產品中的固件程序進行更新升級。通常實現IAP功能時,即用戶程序運行中作自身的更新操作,需要在設計固件程序時編寫兩個項目代碼,第一個項目程序不執行正常的功能操作,而只是通過某種通信方式(如USB、USART)接收程序或數據,執行對第二部分代碼的更新;第二個項目代碼才是真正的功能代碼。
以上內容摘自原子的開發指南。說的通俗一點,要做IAP功能(也可以說成是遠程升級功能),需要有兩段程序一個是IAP程序(也可以稱為BootLoader),另一個是APP程序(主應用程序)。通過USB、串口、CAN等通訊方式向STM32發送要升級的程序文件數據(按自定的協議),IAP程序中將接收到的數據寫到APP程序的地址實現將APP程序的升級。這是大致的流程。
此文檔只做互相跳轉的總結不包含接收數據、FLASH寫入等操作。
說到IAP升級不得不說兩個圖(圖片引自原子的開發指南)
第一個是正常運行時的流程圖
STM32的FLASH地址起始於0x08000000,程序文件就從此地址開始寫入。此外STM32內部通過“中斷向量表”來響應中斷,程序啟動后,將首先從“中斷向量表”取出復位中斷向量執行復位中斷程序完成啟動,而“中斷向量表”的起始地址是0x08000004,當中斷來臨,STM32的內部硬件機制亦會自動將PC指針定位到“中斷向量表”處,並根據中斷源取出對應的中斷向量執行中斷服務程序。
根據上圖分析啟動和運行過程,
① STM32在復位后,先從0X08000004地址取出復位中斷向量的地址,並跳轉到復位中斷服務程序,
② 在復位中斷服務程序執行完之后,會跳轉到的main函數(如使用KEIL MDK調試時一下載進程序,會發現需要運行幾次下一步才會跳轉到main函數的位置)
③ main函數一般都是超循環體(while(1)死循環),在main函數執行過程中,如果收到中斷請求(發生重中斷),此時STM32強制將PC指針指回中斷向量表處
④ 根據中斷源進入相應的中斷服務程序,
⑤ 在執行完中斷服務程序以后,程序再次返回main函數執行。
第二個是加入IAP功能后的流程圖
根據上圖分析加入IAP后的起動和運行過程
① STM32復位后,還是從0X08000004地址取出復位中斷向量的地址,並跳轉到復位中斷服務程序,在運行完復位中斷服務程序之后跳轉到IAP的main函數,如將IAP看作是一個APP的話,那么此部分和正常起動是一樣的,此步相當於正常運行的①和②。
② 在執行完IAP以后(如需要將新的APP代碼寫入STM32的FLASH或沒有更新直接跳轉。APP的復位中斷向量起始地址為0X08000004+N+M),跳轉至APP的復位向量表
③ 取出APP的復位中斷向量的地址,並跳轉執行新程序的復位中斷服務程序,隨后跳轉至APP的main函數
④ 同樣main函數為一個超循環,並且注意到此時STM32的FLASH,在不同位置上,共有兩個中斷向量表。在main函數執行過程中,如果CPU得到一個中斷請求,PC指針仍強制跳轉到地址0X08000004中斷向量表處,而不是APP程序的中斷向量表。
⑤ 程序再根據我們設置的中斷向量表偏移量,跳轉到對應中斷源的APP中斷服務程序中,
⑥ 在執行完中斷服務程序后,程序返回main函數繼續運行。
注意:IAP和APP跳轉過程中,是通過PC指針定位進行跳轉,所有的寄存器都保持原有狀態,跳轉過程中並不是做了復位。
下面是IAP和APP互相跳轉程序:
/****************************************************************************** IAP 跳轉到 APP 之間跳轉函數 ******************************************************************************/ void IAP_APP_Jump (void) { INT32U SpInitVal; //要跳轉到程序的SP初值. INT32U JumpAddr; //要跳轉到程序的地址.即,要跳轉到程序的入口 void (*pFun)(void); //定義一個函數指針.用於指向APP程序入口 RCC_DeInit(); //關閉外設 NVIC_DeInit (); //恢復NVIC為復位狀態.使中斷不再發生. SpInitVal = *(INT32U *)IAP_ADDR;//IAP_ADDR IAP的棧頂地址(0x08000000) //跳轉到APP時 APP_ADDR AAP的棧頂地址(如:0x08003800) JumpAddr = *(INT32U *)( IAP_ADDR + 4); //設置復位中斷向量(如上面流程分析) __set_MSP(SpInitVal); //設置SP.,堆棧棧頂地址 pFun = (void (*)(void))JumpAddr; //生成跳轉函數.將復位中斷向量地址做為函數指針 (*pFun) (); //執行函數,實現跳轉.不再返回. }
在IAP和APP中都需要進行中斷向量表的設置,如正常程序中設置一樣。
再介紹一下在集成開發環境下APP程序起始地址設置
Keil MDK環境下點擊Options for TargetàTarget選項卡
在IAP和APP均為無系統時,上面程序能夠很好實現互相跳轉。但在IAP無系統,而APP使用UCOS系統時出現了較多問題(IAR環境)。如下一一記錄各種問題的解決過程。
但在BSP_Init()函數中確實有對復位中斷向量進行了設置。
NVIC_SetVectorTable(NVIC_VectTab_FLASH,(APP1_ADDR+4));
說明一下,這里將中斷向量表設置為 (APP1_ADDR+4)(偏移4個字節即為復位中斷向量),而有的程序中設置為APP1_ADDR。實際上兩種設置是一樣的,在
NVIC_SetVectorTable()函數中執行下句
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80); 所以可以看出是否偏移4字節是一樣的。
經測試發現需要在調用OSStart()的前一句重新設置復位中斷向量才能正確設置。
2、 經上一步修改后,IAP可以跳轉到APP中,APP也能跳轉到IAP中,但再重IAP就不能再跳回APP了。修改跳轉函數
/*********************************************************************** 函數功能:跳轉到IAP函數 ***********************************************************************/ void JumpToIAP(void) { INT32U IapSpInitVal; INT32U IapJumpAddr; void (*pIapFun)(void); RCC_DeInit(); NVIC_DeInit(); __disable_irq(); //關中斷() // APP如跳轉前關中斷,跳轉到IAP后,IAP初始化后要打開中斷 IapSpInitVal = *(INT32U *)IAP_ADDR; IapJumpAddr = *(INT32U *)(IAP_ADDR + 4); __set_CONTROL(0); //進入用戶級線程模式 進入軟中斷后才可以回到特權級線程模式 //APP如使用系統如ucos必須要有此過程否則跳到IAP后,無法再次跳到APP __set_MSP (IapSpInitVal); pIapFun = (void (*)(void))IapJumpAddr; (*pIapFun) (); } /*********************************************************************** IAP 跳轉到 APP 函數 ***********************************************************************/ void Jumpto_APP(void) { INT32U IapSpInitVal; INT32U IapJumpAddr; void (*pIapFun)(void); RCC_DeInit();//關閉外設 NVIC_DeInit(); __disable_irq(); //關中斷()如IAP關中斷 APP如果沒用UCOS系統,APP //初始化后要開中斷,用UCOS后,在起動任務后會開中斷 IapSpInitVal = *(INT32U *)APP1_ADDR; IapJumpAddr = *(INT32U *)(APP1_ADDR + 4); if((IapSpInitVal & 0x2FFE0000)==0x20000000)//檢查棧頂地址是否合法. { __set_MSP (IapSpInitVal); pIapFun = (void (*)(void))IapJumpAddr; (*pIapFun) (); } }