IAP技术原理
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升级遇到的问题总结