使用STM32举例说明:
1. 正常向量表
STM32F1 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入
STM32F1 是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表”来响应中断
程序启动后,将首先从“中断向量表”取出 复位中断向量 执行 复位中断程序 完成启动
“中断向量表”的起始地址是 0x08000004
当中断来临,STM32F1 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处
并根据中断源取出对应的中断向量执行中断服务程序
- STM32F1 在复位后,先从 0X08000004 地址取出复位中断向量的地址,跳转到复位中断服务程序
- 在复位中断服务程序执行完之后,会跳转到我们的 main 函数
- (见下代码)
- 而我们的 main 函数一般都是一个死循环
- 在 main 函数执行过程中,如果收到中断请求(发生重中断)此时 STM32F1 强制将 PC 指针指回中断向量表处
- 根据中断源进入相应的中断服务程序
- 在执行完中断服务程序以后,程序再次返回 main 函数执行
- 从下面可以清晰的看到是从复位中断函数跳转到主函数
; Reset handler
Reset_Handler PROC ; 定义处理器复位中断服务程序
EXPORT Reset_Handler ; 导出此复位处理函数,使其在链接器脚本和其他文件中可见
[WEAK] ; 标记为弱符号,允许其他地方重定义或覆盖此符号
IMPORT __main ; 引入主函数__main的声明,以便调用
IMPORT SystemInit ; 引入系统初始化函数SystemInit的声明
LDR R0, =SystemInit ; 加载SystemInit函数的地址到寄存器R0
BLX R0 ; 调用SystemInit函数进行系统初始化,BLX指令同时将状态寄存器切换到Thumb状态
LDR R0, =__main ; 加载主函数__main的地址到寄存器R0
BX R0 ; 调用主函数__main开始执行应用程序,BX指令将状态寄存器切换到ARM状态
ENDP ; 结束过程
2. 升级向量表
- STM32F1 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序
- 在运行完复位中断服务程序之后跳转到 IAP 的 main 函数
- 在执行完 IAP 以后
- (即将新的 APP 代码写入 STM32F1灰底部分。新APP的复位中断向量起始地址为 0X08000004+N+M)
- 跳转至新写入程序的复位向量表
- 取出新APP的复位中断向量的地址
- 并跳转执行新程序的复位中断服务程序
- 随后跳转至新程序的 main 函数
- main 函数为一个死循环
- 注意到此时 STM32F1 的 FLASH,在不同位置上,共有两个中断向量表
- 在 main 函数执行过程中,
- 如果 CPU 得到一个中断请求
- PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新APP的中断向量表
- 程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中
- 在执行完中断服务程序后,程序返回 main 函数继续运行
- 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始
- 必须将新程序的中断向量表相应的移动,移动的偏移量为 x
- 并且偏移量为 0X200的倍数即可
3. .APP 程序起始地址设置方法
默认的条件下,图中 IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X80000,
即从 0X08000000 开始的 512K 空间为我们的程序存储区。
而图中,我们设置起始地址(Start)为 0X08010000,即偏移量为 0X10000(64K 字节),
因而,留给 APP 用的 FLASH 空间(Size)只有 0X80000-0X10000=0X70000(448K 字节)大小了。
设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。
理论上我们只需要确保 APP 起始地址在 Bootloader之后,并且偏移量为 0X200
的倍数即可(相关知识,请参考:http://www.openedv.com/posts/list/392.htm)。这里我们选择 64K
(0X10000)字节,留了一些余量,方便 Bootloader 以后的升级修改
4. 中断向量表设置偏移
/**
* @brief Setup the microcontroller system // 设置微控制器系统
* Initialize the Embedded Flash Interface, the PLL // 初始化嵌入式闪存接口和PLL
* and update the SystemCoreClock variable. // 更新SystemCoreClock变量
* @note This function should be used only after reset. // 注意: 此函数仅应在复位后使用
* @param None // 没有参数
* @retval None // 没有返回值
*/
void SystemInit (void)
{
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG) // 如果是STM32F1系列中的某些型号
#ifdef DATA_IN_ExtSRAM // 如果数据存储在外部SRAM中
SystemInit_ExtMemCtl(); // 初始化外部内存控制
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the Vector Table location -------------------------------------*/ // 配置向量表位置
#if defined(USER_VECT_TAB_ADDRESS) // 如果用户自定义了向量表地址
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; // 设置SCB的VTOR寄存器以重定位向量表到内部SRAM
#endif /* USER_VECT_TAB_ADDRESS */
}
系统启动的时候,会首先调用 SystemInit 函数初始化时钟系统
SystemInit 还完成了中断向量表的设置,我们可以打开 SystemInit 函数
看看函数体的结尾处
#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
VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址
默 认 的 情 况
VECT_TAB_SRAM 是没有定义,
所以执行
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET
对于 FLASH APP,
我们设置为 FLASH_BASE+偏移量 0x10000,
所以我们可以在 SystemInit 函数里面修改 SCB->VTOR 的值
当然为了尽可能不修改系统级别文件,
我们可以也可以在 FLASH APP 的 main 函数最开头处添加如下代码
实现中断向量表的起始地址的重设
SCB->VTOR = FLASH_BASE | 0x10000;
以上是 FLASH APP 的情况,当使用 SRAM APP 的时候,我们设置起始地址为:
SRAM_BASE+0x1000,同样的方法,
我们在 SRAM APP 的 main 函数最开始处,添加下面代码:
SCB->VTOR = SRAM_BASE | 0x1000;
- 第二种
将中断向量表移动的方法是在程序中加入函数
void NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset);
其中参数NVIC_VectTab为中断向量表起始位置,而参数Offset则为地址偏移量,
如将中断向量表移至0x8002000处,则应调用该函数如下:
void NVIC_SetVectorTable(0x8000000, 0x2000);
同时有必要提醒
此函数只会修改STM32程序中用于存储中断向量的结构体变量,
而不会实质地改变中断向量表在闪存中的物理位置