IAP技術原理
更新記錄
version | status | description | date | author |
---|---|---|---|---|
V1.0 | C | Create Document | 2018.10.17 | John Wan |
V2.0 | M | 對中斷向量表的理解有誤,以及理清判斷程序是否下載的原理 | 2018.10.19 | John Wan |
status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。
IAP與ISP的概念及原理
ISP簡介
ISP:In System Programming 在系統中編程,通過芯片專用的串行編程接口對其內部的程序存儲器進行擦寫。
ISP原理
通過專用的程序下載器或程序仿真器(JTAG/ST-Link等)連接到目標板的調試接口,並通過PC端的軟件操作目標板的程序存儲器。
IAP簡介
IAP:In Application Programming 在應用中編程,通過調用特定的bootloader程序,對程序存儲器的指定段進行讀/寫操作,從而實現對目標板的程序的修改。
IAP原理
從軟件層面上將芯片的程序存儲器划分為兩個區域,其中區域①使用ISP的方式燒錄特定的bootloader程序,該程序只具備
兩種與業務無關的功能:
- 1)通過某種通信方式(USB、USART、CAN等)接收程序或數據的功能;
- 2)對存儲器的擦寫功能;
區域②則存放真正的項目代碼(app),而該區域的代碼則是通過區域①進行更改。
注:只要程序存儲器足夠,可以存放多個APP程序
IAP優勢
方便遠程進行app更新。
IAP的設計
硬件平台:STM32F103ZET6 ,Flash:512K,內置SRAM:64K
1、程序啟動流程
存儲器的啟動配置
以典型的Flash啟動為例
圖1的三種啟動方式,從系統存儲器啟動項,里面固化廠家出廠的代碼,不可重寫。
結合圖1與圖2,代碼區始終從地址0x0000 0000開始,那么設置成Flash啟動之后,Flash的起始地址0x0800 0000就映射到了0x0000 0000,那么原本保存在0x0000 0000處的“中斷向量表”也相應可以通過0x0800 0000訪問(中斷向量表是在芯片的啟動文件中,隨着Keil MDK的編譯,將中斷向量表放在執行文件的開始(可查看.hex
與.bin
),又因為代碼執行的開始地址與Flash的起始地址形成了映射,所以才能在代碼執行的開始直接進入中斷向量表)。基於該原理,其它地址啟動的app程序首先要做的就是在程序的開始進行向量表的映射。
“中斷向量表”的功能主要是用來存儲相應中斷服務程序入口地址,當中斷來臨時,PC指針會從該表中尋找對應中斷服務程序入口地址。
向量表的第一個4字節保存的是用戶程序堆棧空間的棧頂地址。程序啟動時,先從0x0800 0004地址處去取復位中斷向量的地址,並跳轉到復位中斷服務程序,執行完之后跳轉到IAP的main函數,如圖表①所示。如果在IAP過程接收到中斷請求,則PC指針強制指向中斷向量表處,根據中斷源進入相應的中斷服務程序。執行完之后,再次返回到main函數。
在執行完IAP后,跳轉至新寫入程序的復位向量表,取出新程序的復位中斷向量的地址,並跳轉執行新程序的復位中斷服務程序,隨后跳轉至新程序的main函數,如圖標號②和③所示。
在APP的main函數運行過程中,如果接收到中斷請求,PC指針仍強制跳轉到0x0800 0004(第一個中斷向量表),如圖標號④所示;程序再根據設置的中斷向量表偏移量,跳轉到對應中斷源新的中斷服務程序中,如圖標號⑤所示;執行完之后,返回main函數繼續運行,如圖標號⑥所示。
相應的其它啟動配置,例如內置SRAM啟動,確保映射了向量表,其余流程與之相同
2、中斷向量表的重定位
通過可編程的向量表偏移寄存器(VTOR)設定該程序的向量表所處的位置
注:該書翻譯的是Cortex-M3的r2p0版本
可通過ST的官方固件庫(CMSIS)控制
CMSIS 3.5, misc.c文件
/**
* @brief Sets the vector table location and Offset.
* @param NVIC_VectTab: specifies if the vector table is in RAM or FLASH memory.
* This parameter can be one of the following values:
* @arg NVIC_VectTab_RAM
* @arg NVIC_VectTab_FLASH
* @param Offset: Vector Table base offset field. This value must be a multiple
* of 0x200.
* @retval None
*/
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
{
/* Check the parameters */
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
}
在這里注意兩點:
- 向量表的起始地址要求
向量表的大小擴展為下一個 2 的整數次冪,且新向量表的起始地址必須對齊該值
向量表中每個向量占用4個字節,然后乘上系統中的向量數,再將總字節數向上擴展到2的整數次冪,以stm32f103vet6為例,共有60(中斷) + 16(系統異常) = 76,向上擴展為2的整數次冪為128,從而向量表的起始地址必須是能被 128 * 4 = 512 = 0x200 整除,即 0x0,0x200,0x400......之類地址。
- 固件庫中 (Offset & (uint32_t)0x1FFFFF80)的由來?
- 首先看VTOR寄存器
了解到r2p1版本VTOR寄存器有效的位為bit[7:31],其中bit[29]決定的是向量表位於代碼(0)或SRAM(1)中,bit[7:28]決定的是向量表的起始地址,因此上面提到的地址理應為0x1FFFF FF80。
3、IAP跳轉APP函數分析
3.1 執行流程:
- 判斷用戶是否已經下載程序
- 中斷配置初始化
- Flash加鎖
- 設置MSP指針
- 進行跳轉
3.2 完整代碼
#define APP_FLASH_ADDR 0x08004000
/*********************************************************************
*函數名:iap_jump_to_app
*描述 :執行跳入APP
*輸入 :appAddr
*輸出 :無
*返回值:無
*說明:
***********************************************************************/
u8 iap_jump_to_app(u32 appAddr)
{
u32 app_msp_addr; //
u32 app_jump_addr;
void (*pAppFun)(void); //定義一個函數指針,用於指向app程序入口
app_msp_addr = (*(vu32 *)APP_FLASH_ADDR); //取棧頂地址保存的數據
app_jump_addr = (*(vu32 *)(APP_FLASH_ADDR + 4)); //取出APP程序復位中斷向量的地址
if ((app_msp_addr & 0x2FFE0000) != 0x20000000) //檢查app棧頂地址是否合法,判斷是否已經下載程序
return 1;
nvic_reset(); //恢復NVIC為復位狀態,使中斷不再發生
FLASH_Lock();
__set_MSP(app_msp_addr); //設置MSP指針指向app向量表的棧頂地址保存的地址
pAppFun = (void (*)(void))app_jump_addr; //生成跳轉函數
(*pAppFun)(); //跳轉,PC指針執行復位
return 0;
}
3.3 問題解析:判斷程序已經下載?
從棧頂地址取出的數據即用戶代碼的堆棧地址,堆棧地址指向RAM,而RAM的地址在下面解釋。
參考圖7,Cortex-M3將 [0x2000 0000, 0x3FFF FFFF] 地址划定為SRAM區域,而在STM32F10xxx中SRAM的區域為64K,那么SRAM的實際有效地址就為 [0x2000 0000, 0x2000 FFFF];因此檢測棧頂地址是否合法及判斷棧頂地址是否在規定的SRAM地址區域內。有效位的檢查只需要看后面4位。即
上式中:
if ((app_msp_addr & 0x2FFE0000) != 0x20000000) //檢查app棧頂地址是否合法,判斷是否已經下載程序
return 1;
准確的應修改為:
if ((app_msp_addr & 0xFFFF0000) != 0x20000000)
return 1;
- 為什么棧頂地址的區域是在SRAM中?
SRAM:靜態隨機存取存儲器,Static Random-Access Memory,相當於內存的作用。代碼就是在該區域跑。因此代碼的堆棧也是在該區域。在Keil MDK進行設置時,也就是處於Target
選項右下角的IRAM1
內容,這里決定了編譯生成的執行文件的變量地址及堆棧地址所能存在的區域范圍。
- 多種檢查方式判定app程序的下載狀態
-
檢查接收到的app程序的棧頂地址是否符合要求 —— 從而判斷程序是否下載。
那么就是從app程序的向量表中,獲取棧頂地址,然后判斷該棧頂地址是否在設定的區域內。例如上面的Flash啟動例子,從 0x0800 0000處取得棧頂地址,判定該地址是否在Keil MDK里面設置的SRAM區域。 -
檢查接收到的app程序的中斷復位向量地址是否符合要求 —— 從而判斷接收到程序是Flash啟動類型還是SRAM啟動類型
可以從接收到的程序的向量表的第二個4字節獲取中斷復位向量的地址,然后判斷該向量地址所處的區域是在Flash還是SRAM中,從而確定該程序應該存放的地方。
3.4 問題解析:NVIC的復位
防止在執行跳轉的過程中,被其它中斷打斷。
也可使用:
位於 core_cm3.c的
__set_FAULTMASK(1); //屏蔽所有中斷
函數原型:
/**
* @brief Set the Fault Mask value
*
* @param faultMask faultMask value
*
* Set the fault mask register
*/
void __set_FAULTMASK(uint32_t faultMask)
{
__ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) );
}
另:參考《ARM Cortex-M3與Cortex-M4權威指南CnR3》的7.10.2節 P183,FAULTMASK
相比於另外兩個寄存器,它實際上會將當前優先級修改為-1
,這樣甚至是HardFault
處理也會被屏蔽,當其置位時,只有優先級比它更高的NMI
異常處理才能執行。
3.5 “__set_MSP(app_msp_addr);”的解析
該函數的原型如下:
位於 core_cm3.c
/**
* @brief Set the Main Stack Pointer
*
* @param topOfMainStack Main Stack Pointer
*
* Assign the value mainStackPointer to the MSP
* (main stack pointer) Cortex processor register
*/
__ASM void __set_MSP(uint32_t mainStackPointer)
{
msr msp, r0
bx lr
}
MSR
指令的格式為:
MSR{條件} 程序狀態寄存器(CPSR戒SPSR)_<域>,操作數
MSR
指令用於將操作數的內容傳送到程序狀態寄存器的特定域中。其中,操作數可以為通用寄存器或立即數。<域>用於設置程序狀態寄存器中需要操作的位,32位的程序狀態寄存器可分為4個域:
位[31:24]為條件標志位域,用f表示;
位[23:16]為狀態位域,用s表示;
位[15:8]為擴展位域,用x表示;
位[7:0]為控制位域,用c表示;
該指令通常用於恢復或改變程序狀態寄存器的內容,在使用時,一般要在MSR指令中指明將要操作的域。
指令示例:
MSR CPSR,R0 ;傳送R0的內容到CPSR
MSR SPSR,R0 ;傳送R0的內容到SPSR
MSR CPSR_c,R0 ;傳送R0的內容到SPSR,但僅僅修改CPSR中的控制位域
“bx lr”
:等同於“MOV pc,lr”
即跳轉到lr中存放的地址處。
連接寄存器r14(LR)
,在ARM體系結構中有兩種特殊用途:
- 保存子程序返回地址。使用
BL
或BLX
時,跳轉指令自動把返回地址放入r14
中;子程序通過把r14
復制到PC來實現返回,通常用下列指令之一:
MOV PC, LR
BX LR
- 當異常發生時,異常模式的
r14
用來保存異常返回地址,將r14
如棧可以處理嵌套中斷。LR
中保存的值等於異常發生時PC的值減4(或者減2),因此在各種異常模式下可以根據LR
的值返回到異常發生前的相應位置繼續執行。
4、APP跳轉IAP函數分析
4.1 完整代碼
#define IAP_ADDR 0x08000000
/********************************************************************
函數: 運行IAP程序. //
輸入: 無
返回: 無.不再返回.
說明:
由於APP是在IAP的基礎上運行的,因此,IAP一定是有效的,這里不再作IAP有效性檢查.
APP跳IAP
************************************************************************/
void app_jump_to_iap(void)
{
u32 IapSpInitVal; //IAP程序的SP初值.
u32 IapJumpAddr; //IAP程序的跳轉地址.即,IAP程序的入口.
void (*pIapFun)(void); //定義一個函數指針.用於指向APP程序入口.
//__set_FAULTMASK(1); //屏蔽所有中斷
//NVIC_SystemReset(); //軟件復位
RCC_DeInit();
nvic_reset(); //恢復NVIC為復位狀態.使中斷不再發生.
__set_CONTROL(0); //將PSP指針切換為MSP指針
IapSpInitVal = *(vu32 *)IAP_ADDR; //取APP的SP初值.
IapJumpAddr = *(vu32 *)(IAP_ADDR + 4); //取程序入口.
__set_MSP(IapSpInitVal); //設置SP.
pIapFun = (void (*)(void))IapJumpAddr; //生成跳轉函數.
(*pIapFun)(); //跳轉.不再返回.
}
4.2 基本的流程與IAP跳轉APP一致。
4.3 “_set_CONTROL(0);”的作用:
該函數的原型如下:
位於 core_cm3.c
/**
* @brief Set the Control Register value
*
* @param control Control value
*
* Set the control register
*/
__ASM void __set_CONTROL(uint32_t control)
{
msr control, r0
bx lr
}
CONTROL寄存器:
環境:APP程序用了UCOSII系統,IAP程序是裸機
參考《ARM Cortex-M3與Cortex-M4權威指南CnR3》的 3.2.7節 與 10.2節:
其中:
主棧指針(MSP)為默認指針,用於處理模式,也可用於線程模式。
進程棧指針(PSP),只能用於線程模式,專門為了支持OS,保護進程安全。
不帶OS程序運行時,默認的狀態是特權模式,使用MSP指針;
而ucosii運行在特權模式下,線程使用的PSP指針.
因為APP的程序是上OS的,而IAP程序是裸機的,那么在APP程序運行時,由於處於線程模式則使用的是PSP指針,而要跳轉的IAP程序由於沒上OS,理應使用的是MSP指針,因此在這需要進行使用指針的切換
如果沒有"__set_CONTROL(0);"
跳轉進入IAP時,運行IAP的代碼狀態是特權模式,使用PSP
指針,在配置NVIC寄存器是,出現hardfault
,因此要么在跳進IAP之前在APP的代碼里面使用該語句將指針切換為MSP
,要么在IAP代碼的起始處調用該語句,將指針切換為MSP
,才能夠正常來回跳轉。
4.4 "NVIC_SystemReset();"的功能
軟件的方式觸發自復位
/**
* @brief Initiate a system reset request.
*
* Initiate a system reset request to reset the MCU
*/
static __INLINE void NVIC_SystemReset(void)
{
SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk); /* Keep priority group unchanged */
__DSB(); /* Ensure completion of memory access */
while(1); /* wait until reset */
}
寄存器的參考《Cortex_M3_Devices_Generic_User_Guide_UG0552_A_》 4.3.5節
功能說明參考《ARM Cortex-M3與Cortex-M4權威指南CnR3》 13.4節
上式執行的是SYSRESETREQ
類型的復位,在Cortex-M3
里描述為產生到微控制器系統復位控制邏輯的系統復位請求,而系統復位控制邏輯又不屬於處理器設計,得根據微處理器的廠家來定,然后查詢
參考《STM32F10xxx_20xxx_21xxx_L1xxx_Programming_Mannual_PM0056_ENV6》 4.4.5節
STM32將該復位請求設置為復位除調試邏輯以外的所有
5、Keil MDK的設置
5.1 地址的設置
以上面Flash的例子IAP的程序占用16K,APP的地址從0x0800 4000開始,Keil MDK的設置
類似的如果是以SRAM啟動,則相應的修改左邊的IROM1
區域為啟動地址以及區域大小。
IROM1
區域決定代碼的存儲區域,IRAM1
區域決定運行內存和棧的存儲區域,
如果使用Jlink進行調試,那么在Jlink的Flash Download設置項,start 與 size 根據想要的調試的程序地址進行更改。
5.2 ".bin"文件
5.2.1 ".bin"文件與".hex"文件的區別
最大的區別是hex文件帶地址,bin文件不帶地址,因此bin文件的體積較小。具體的差異百度。
5.2.2 ".bin"文件的生成
使用Keil MDK里面的fromelf.exe
工具,位於ARM\ARMCC\bin
文件夾下。
具體的語法查看正點原子的《MDK如何生成bin文件》.doc
使用fromelf.exe
工具的目的是將生成.axf
文件轉換為.bin
文件,
總體分為四個步驟:
- 選擇
fromelf.exe
所在的目錄,例如:F:\tool\Keil_v5\ARM\ARMCC\bin\fromelf.exe
; - 配置
fromelf.exe
的語法選項,例如:--bin -o
; - 選擇生成的文件路徑及文件名,例如:
..\OBJ\RTC.bin
; - 選擇可執行文件路徑及文件名,例如:
..\OBK\RTC.axf
。
因此整體的語句為:
F:\tool\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\RTC.bin ..\OBK\RTC.axf
注意:
- 整體語句是填在
MDK
的Options for target -> User -> After Build/Rebuild -> Run #1
里面,並勾選; - 文件的路徑:如果是
..\
表示項目文件所在目錄的上一級目錄下查找,也可輸入完整路徑以固定路徑形式查找; - 文件的名字:可執行文件.axf的名字與路徑與項目編譯所生成文件的名字與路徑一致,即
MDK
在Options for target -> Output
的設置項-> Name of Executable
與-> Select Folder for Objects
。生成的文件.bin路徑及文件名不作要求; - 生成的文件與可執行文件兩者的語法位置順序是根據
fromelf.exe
的語法來的,上面的整體語句也可寫成:
F:\tool\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin ..\OBK\RTC.axf -o ..\OBJ\RTC.bin
參考資料
官網資料:
- 《Cortex_M3_Devices_Generic_User_Guide_UG0552_A_》
- 《Cortex_M3_Processor_Technical_Reference_Mannual_I_r2p1》
- 《STM32F10xxx中文參考手冊_V10》
- 《STM32F10x_StdPeriph_Lib_V3.5.0》庫文件
書籍:
- 《STM32F1開發指南-庫函數版本_V3.1》——正點原子 第五十二章 串口IAP實驗 P711
- 《ARM Cortex-M3與Cortex-M4權威指南CnR3》——Joseph Yiu著 吳常玉 譯
- 《ARM Cortex-M3權威指南CnR2》——Joseph Yiu著 宋岩 譯
博客:
IAP升級功能編寫初期的一些困惑與疑問---完成功能后的總結
STM32IAP升級-----編寫IAP升級遇到的問題總結