前言
中断向量表里的中断跳转地址在编译后就定下来了,那我们写的程序有办法改变吗?答案是肯定的。
本文以一个实例介绍如何实现。下面先介绍理论原理。
向量表
当发生了异常并且要响应它时,CM3 需要定位其处理例程的入口地址。这些入口地址存储在所谓的“(异常)向量表”中。缺省情况下,CM3 认为该表位于零地址处,且各向量占用4 字节,因此每个表项占用4 字节。
向量表重定位
因为地址0 处应该存储引导代码,所以它通常是Flash 或者是ROM 器件,并且它们的值不得在运行时改变。然而,为了动态重分发中断,CM3 允许向量表重定位——从其它地
址处开始定位各异常向量。这些地址对应的区域可以是代码区,但也可以是RAM 区。在RAM区就可以修改向量的入口地址了。为了实现这个功能,NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08 处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32 个中断,则共有32+16(系统异常)=48 个向量,向上增大到2 的整次幂后值为64,因此地址
地址必须能被64*4=256 整除,从而合法的起始地址可以是:0x0, 0x100, 0x200 等。
如果需要动态地更改向量表,则对于任何器件来说,向量表的起始处都必须包含以下向量:
- 主堆栈指针(MSP)的初始值
- 复位向量
- 列表内容
- NMI
- 硬 fault 服务例程
后两者也是必需的,因为有可能在引导过程中发生这两种异常。
可以在SRAM 中开出一块用于存储向量表。然后在引导完成后,就可以启用内存中的向量表,从而实现向量可动态调整的功能。
实例分析:MultiTimer.c中的向量表重定位
MultiTimer.c —>mt_delayMs—>init_multi_timer—>InstallIRQHandler
void InstallIRQHandler(IRQn_Type irq, uint32_t irqHandler)
{
/* Addresses for VECTOR_TABLE and VECTOR_RAM come from the linker file */
#if defined(__CC_ARM) //__CC_ARM ---KEIL
extern uint32_t Image$$VECTOR_ROM$$Base[];
extern uint32_t Image$$VECTOR_RAM$$Base[];
extern uint32_t Image$$RW_m_data$$Base[];
#define __VECTOR_TABLE Image$$VECTOR_ROM$$Base
#define __VECTOR_RAM Image$$VECTOR_RAM$$Base
#define __RAM_VECTOR_TABLE_SIZE (((uint32_t)Image$$RW_m_data$$Base - (uint32_t)Image$$VECTOR_RAM$$Base))
#elif defined(__ICCARM__) //__ICCARM ---IAR
extern uint32_t __RAM_VECTOR_TABLE_SIZE[];
extern uint32_t __VECTOR_TABLE[];
extern uint32_t __VECTOR_RAM[];
#elif defined(__GNUC__)
extern uint32_t __VECTOR_TABLE[];
extern uint32_t __VECTOR_RAM[];
extern uint32_t __RAM_VECTOR_TABLE_SIZE_BYTES[];
uint32_t __RAM_VECTOR_TABLE_SIZE = (uint32_t)(__RAM_VECTOR_TABLE_SIZE_BYTES);
#endif /* defined(__CC_ARM) */
uint32_t n;
__disable_irq();
if (SCB->VTOR != (uint32_t)__VECTOR_RAM)
{
/* Copy the vector table from ROM to RAM */
for (n = 0; n < ((uint32_t)__RAM_VECTOR_TABLE_SIZE) / sizeof(uint32_t); n++)
{
__VECTOR_RAM[n] = __VECTOR_TABLE[n];
}
/* Point the VTOR to the position of vector table */
SCB->VTOR = (uint32_t)__VECTOR_RAM;
}
/* make sure the __VECTOR_RAM is noncachable */
__VECTOR_RAM[irq + 16] = irqHandler;
__enable_irq();
}
system_QN908X.c
void SystemInit(void)
{
SCB->VTOR = (uint32_t)&__Vectors;
}
startup_QN908X.s
__Vectors EQU __vector_table
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
DCD MemManage_Handler
DCD BusFault_Handler
DCD UsageFault_Handler
SCB->VTOR 在SystemInit里被设置成flash中异常地址。SCB->VTOR = (uint32_t)&__Vectors;
__disable_irq();
if (SCB->VTOR != (uint32_t)__VECTOR_RAM)
{
/* Copy the vector table from ROM to RAM */
for (n = 0; n < ((uint32_t)__RAM_VECTOR_TABLE_SIZE) / sizeof(uint32_t); n++)
{
__VECTOR_RAM[n] = __VECTOR_TABLE[n];
}
/* Point the VTOR to the position of vector table */
SCB->VTOR = (uint32_t)__VECTOR_RAM;
}
/* make sure the __VECTOR_RAM is noncachable */
__VECTOR_RAM[irq + 16] = irqHandler;
__enable_irq();
所以上面代码的意思就是:
如果现在SCB->VTOR 指向的不是RAM中的中断向量地址,就通过赋值的方法将ROM中的中断向量地址一个一个的赋值到RAM中去,__VECTOR_RAM[n] = __VECTOR_TABLE[n];
然后SCB->VTOR中赋值 已经赋值好的RAM中的中断向量表的首地址,SCB->VTOR = (uint32_t)__VECTOR_RAM;
这样下次异常发生时,就直接跳到RAM中断向量表的首地址处,再匹配对应的中断向量。
总结
中断向量表里的中断跳转地址在编译后就定下来了,SCB->VTOR向量可动态调整就是让我们的程序运行后还能改变向量的跳转地址。方法就是:在RAM重建一个中断向量表,在想改变的位置重新赋值新的跳转地址。