M0的中断向量表重映射

  Stm32F030的Bootloader制作与其他ST芯片一致;但是Stm32F030的APP程序就有特殊变动:因为M0是不能设置重量向量表偏移量;本篇就讲M0的APP程序的中断向量表重映射,和还有一些会用到的冷知识,如编译工具链等。

一、M0的中断向量表重映射

  在STM32F103等cortex-m3/m4内核的单片机上可以通过设置中断向量表的偏移量,完成重映射中断向量表。

1
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;

  Stm32f0系列MCU中断矢量表的定位跟STM32其它系列相比有点差异,即M0系列没有像其它M3/M4/M0+系列所具备的中断矢量表重定位寄存器,其中断矢量表不能借助矢量重定位寄存器简单修改实现。所以Stm32f0x IAP的过程会跟其它系列的STM32芯片的IAP动作有所不同。


M0的中断向量表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
__Vectors       DCD     __initial_sp                   ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD RTC_IRQHandler ; RTC through EXTI Line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_1_IRQHandler ; EXTI Line 0 and 1
DCD EXTI2_3_IRQHandler ; EXTI Line 2 and 3
DCD EXTI4_15_IRQHandler ; EXTI Line 4 to 15
DCD TS_IRQHandler ; TS
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_3_IRQHandler ; DMA1 Channel 2 and Channel 3
DCD DMA1_Channel4_5_IRQHandler ; DMA1 Channel 4 and Channel 5
DCD ADC1_COMP_IRQHandler ; ADC1, COMP1 and COMP2
DCD TIM1_BRK_UP_TRG_COM_IRQHandler ; TIM1 Break, Update, Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM6_DAC_IRQHandler ; TIM6 and DAC
DCD 0 ; Reserved
DCD TIM14_IRQHandler ; TIM14
DCD TIM15_IRQHandler ; TIM15
DCD TIM16_IRQHandler ; TIM16
DCD TIM17_IRQHandler ; TIM17
DCD I2C1_IRQHandler ; I2C1
DCD I2C2_IRQHandler ; I2C2
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD 0 ; Reserved
DCD CEC_IRQHandler ; CEC
DCD 0 ; Reserved

M0的中断向量表重映射方法如下:

  1. 将APP的中断向量表拷贝到SRAM里面去。M0的中断向量表由48个有序字(32bit)组成,把它们从flash区0x08004000开始的中断向量表拷贝到0x2000 0000的SRAM区。
  2. 做存储地址的映射,即把SRAM映射到代码执行区的地址0X00处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define FLASH_BOOTLOADER_SIZE       (uint32_t)(0x4000)  
#define APPLICATION_ADDRESS (uint32_t)(0x08000000+FLASH_BOOTLOADER_SIZE)


#if (defined ( __CC_ARM ))
__IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
#elif (defined (__ICCARM__))
#pragma location = 0x20000000
__no_init __IO uint32_t VectorTable[48];
#elif defined ( __GNUC__ )
__IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
#elif defined ( __TASKING__ )
__IO uint32_t VectorTable[48] __at(0x20000000);
#endif


static void VectorRemap(void)
{
uint8_t i = 0;

//拷贝中断向量表
for(i = 0; i < 48; i++)
{
VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
}

/* Enable the SYSCFG peripheral clock*/
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Remap SRAM at 0x00000000 */
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
}

//主函数
int main(void)
{
VectorRemap();

//按照平常程序运行
}

  经过上述操作步骤后,当APP里发生中断时,内核就从地址0x00处的向量表取相应中断的入口地址,即相当于从0x2000 0000处的向量表取中断入口地址,当然也相当于从0x08003000处的向量表取中断入口地址,然后去执行相应中断程序。

二、编译工具链

  STM32中ARM系列编译工具链的编译宏选择(__CC_ARM、__ICCARM__、__GNUC__、__TASKING__)。这里简单介绍一下。

  1. 在 core_cm3.h 文件中,有如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #if defined ( __CC_ARM   )
    #define __ASM __asm /*!< asm keyword for ARM Compiler */
    #define __INLINE __inline /*!< inline keyword for ARM Compiler */

    #elif defined ( __ICCARM__ )
    #define __ASM __asm /*!< asm keyword for IAR Compiler */
    #define __INLINE inline /*!< inline keyword for IAR Compiler. Only avaiable in High optimization mode! */

    #elif defined ( __GNUC__ )
    #define __ASM __asm /*!< asm keyword for GNU Compiler */
    #define __INLINE inline /*!< inline keyword for GNU Compiler */

    #elif defined ( __TASKING__ )
    #define __ASM __asm /*!< asm keyword for TASKING Compiler */
    #define __INLINE inline /*!< inline keyword for TASKING Compiler */

    #endif
  2. 这几个宏都是什么含义呢?分别对应什么平台呢?

  • __CC_ARM对应的平台是:ARM RealView
    • RealView,是一套包含编译、调试和模拟的开发工具,需结合开发环境如uvision、eclipse或者CodeWarrior,形成集成开发环境来使用。
  • __ICCARM__对应的平台是:IAR EWARM
    • Embedded Workbench for ARM 是IARSystems 公司为ARM 微处理器开发的一个集成开发环境(下面简称IAR EWARM)。比较其他的ARM 开发环境,IAR EWARM 具有入门容易、使用方便和代码紧凑等特点
  • __GNUC__对应的平台是:GNU Compiler Collection
    • GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的自由软件。
  • __TASKING__对应的平台是:Altinum Designer
    • Altium Designer 是原Protel软件开发商Altium公司推出的一体化的电子产品开发系统,主要运行在Windows操作系统。这套软件通过把原理图设计、电路仿真、PCB绘制编辑、拓扑逻辑自动布线、信号完整性分析和设计输出等技术的完美融合,为设计者提供了全新的设计解决方案,使设计者可以轻松进行设计,熟练使用这一软件使电路设计的质量和效率大大提高。

三、__attribute__ ((at()) 绝对定位

  • __attribute__ ,这个是用来指定变量或结构位域的特殊属性,该关键字后的双括弧中的内容是属性说明。
  • at ,该关键字可以用来设置变量的绝对地址,也就是你可以通过这个关键字,指定某个变量处于内存里面的某个给定的地址.

  __attribute__( at(绝对地址) ) 的用法分两种,一个是绝对定位到Flash,另一种是绝对定位到RAM。

  1. 定位到flash中,一般用于固化的信息,如出厂设置的参数,上位机配置的参数,ID卡的ID号,flash标记等等。

    1
    2
    const u16 gFlashDefValue[512] __attribute__((at(0x0800F000))) = {0x1111,0x1111,0x1111,0x0111,0x0111,0x0111};//定位在flash中,其他flash补充为00
    const u16 gflashdata__attribute__((at(0x0800F000))) = 0xFFFF;
  2. 定位到RAM中,一般用于数据量比较大的缓存,如串口的接收缓存,再就是某个位置的特定变量

    1
    u8 USART2_RX_BUF[USART2_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
  • 绝对定位不能在函数中定义;局部变量是定义在栈区的,栈区由MDK自动分配、释放,不能定义为绝对地址,只能放在函数外定义。
  • 定义的长度不能超过栈或Flash的大小,否则,造成栈、Flash溢出。

四、重映射 & 系统启动

4.1 重映射

1
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);

例如F072的参考文档张SYSCFG寄存器的介绍,如下图:

MEM_MODE的介绍如下:

从以上内容我们可以得到以下信息:

  1. MEM_MODE的值在上电后由BOOT0,BOOT1的状态值决定。
  2. MEM_MODE的值决定了哪个内存映射到地址0x0000 0000 ,也就是说:
    1. 当MEM_MODE =00/10时, Main Flash映射到地址0x0000 0000,即地址0x0800 0000映射到0x0000 0000。
    2. 当MEM_MODE =01时, System Flash映射到地址0x0000 0000,也就是芯片自带的Bootloader代码部分会映射到地址0x0000 0000;例如,0x1FFF C800映射到地址0x0000 0000。
    3. 当MEM_MODE =11时, Embeded SRAM映射到地址0x0000 0000,也就是内存地址0x2000 0000映射到地址0x0000 0000。
  3. 经过映射后,系统访问地址0x0000 0000地址,就相当于直接访问映射的地址,如0x0800 0000。
  4. 由BOOT0,BOOT1的状态决定MEM_MODE的值,进而决定哪个地址映射到地址0x0000 0000,这一过程我们称之为映射。默认映射是系统自动完成的,并由BOOT0,BOOT1的状态决定。
  5. MEM_MODE位是RW的,也就是说可以修改的,如果修改其中,也就会相应的修改映射到0x0000 0000的地址,这一修改的过程,我们就叫其为重映射。重映射是通过用户代码通过修改MEM_MODE的值来完成的。

4.2 系统启动

从STM32F072的参考手册的2.5章,我们可以看到如下内容:

从以上内容我们可以得到以下有用信息:

  1. 在复位启动后,系统在系统时钟的第4个上升沿根据BOOT0,BOOT1的配置获取其值,也就是存储到寄存器SYSCFG_CFGR1的MEM_MODE位上,根据前面3.1的信息可知,这里进一步确定了0x0000 0000的映射地址。这一过程是系统自动完成的。
  2. 在系统启动后,CPU从地址0x0000 0000获取栈顶地址,然后从0x0000 0004开始执行代码。换句话说,由于0x0000 0000被映射了其他地址,获取栈顶与执行实际上都是从映射的地址上实施的。也就是从映射的地址开始执行代码,比如从地址0x08000 0004开始执行代码(如Mian Flash映射),比如0x1FFF C804(如System Flash映射,即BootLoader启动)。
    于是,我们简单整理下系统的整个启动流程:
  • -> 系统复位
  • -> CPU在系统时钟的第4个上升沿根据BOOT0,BOOT1的配置确定寄存器SYSCFG_CFGR1的MEM_MODE的值
  • -> MEM_MODE进一步决定哪个地址(Main Flash,System Flash,SRAM)映射到地址0x0000 0000.
  • -> CPU从地址0x0000 0000获取栈顶,从0x0000 0004开始执行代码,也就是从映射地址获取栈顶,从映射地址+4的地方开始执行代码。
  • 映射地址+4对于着复位中断例程(如0x08000 0004),也就是系统一开始就执行Reset_Handler,进而运行SystemInit然后进入到main函数,就这样,整个代码启动完成。

接下来就是中断产生于中断响应了。

  在Coretext-M3与Coretext-M4核中,在 System Control Block 中存在一个向量表偏移量寄存器 VTOR(0xE000ED08),系统产生中断后,内核通过这个寄存器的值来找到中断向量表的地址,进而执行中断例程代码,当然,此寄存器的值是可以修改的,它的默认值为0。

  由于STM32F0XX采用的是M0核,它是没有这个VTOR寄存器的;将M0理解成M3/M4的特殊情况,M0假设也存在VTOR这么一个虚拟寄存器,只不过它的值不能修改,固定为0罢了,而M3/M4的这个VTOR寄存器一开始时它的值也是为默认值0,只不过在程序运行到SystemInit()函数后,在代码中明确对其进行了修改。

重新整理下,STM32F0XX中断的调用过程:

  • -> 产生中断
  • -> CPU固定到地址0x0000 0000上找中断入口函数;由于映射关系,实际上是在从映射地址上寻找。
  • -> 找到并执行中断例程

五、Stm32F030的App工程配置

  关于Stm32F0的APP,只需要更改工程配置的 IROM 和 IRAM 就行了。

  IRAM存放着重映射中断向量表。1个地址存储1个byte(Falsh 和 Ram 都一样),中断向量都是uint32_t;所以 48 * (32/8) = 0xC0 。

六、友情连接

  关于Stm32F0的IAP,ST官方有套参考代码;ST官方的IAP + Ymodem代码;提取码为:aan9
  本篇文章部分内容来自于这位博主:flydream0,有些更细致的内容可以了解他写的链接文章。

-------------本文结束感谢您的阅读-------------