问题背景
项目使用STM32F030,需要通过IAP进行固件升级,在FLASH里面要烧录两份代码:一个Boot loader,一个用户应用程序APP。在开发应用程序APP时,外设中断不能正确响应进到对应的中断函数。
解决方案
此原因是在Cortex-M3内核的MCU上可以通过设置SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;该寄存器的值来实现中断向量表的重定义。但在STM32F0xx系列以Cortex-M0为内核的单片机中却怎么也找不到这个设置中断向量表的寄存器,用户可以通过以下方法来实现中断向量表重定义。(注意这种情况不止在STM32F0xx系列芯片上存在,在其他一些国产32位Cortex-M0内核也有此问题,比如MM32L073,在SMT32新的Cortex-M0+内核的芯片已经提供了SCB->VTOR这个寄存器)
在STM32F030CC的Reference manual中找到以下说明:
Physical remap
Once the boot mode is selected, the application software can modify the memory accessible in the code area.This modification is performed by programming the MEM_MODE bits in the SYSCFG configuration register 1 (SYSCFG_CFGR1). Unlike Cortex® M3 and M4, the M0 CPU does not support the vector table relocation. For application code which is located in a different address than 0x0800 0000, some additional code must be added in order to be able to serve the application interrupts. A solution will be to relocate by software the vector table to the internal SRAM:
• Copy the vector table from the Flash (mapped at the base of the application load address) to the base address of the SRAM at 0x2000 0000.
• Remap SRAM at address 0x0000 0000, using SYSCFG configuration register 1.
• Then once an interrupt occurs, the Cortex®-M0 processor will fetch the interrupt handler start address from the relocated vector table in SRAM, then it will jump to execute the interrupt handler located in the Flash.
This operation should be done at the initialization phase of the application. Please refer to AN4065 and attached IAP code from www.st.com for more details.
翻译如下:
物理重映射
一旦选择了引导模式,应用软件就可以修改代码中可访问的内存区域。这个通过对SYSCFG配置寄存器1(SYSCFG_CFGR1)中的MEM_MODE模式位进行编程来执行修改。与Cortex®M3和M4不同,M0 CPU不支持向量表重定位。对于位于不同于0x0800 0000的地址的应用程序代码,必须添加一些附加代码,以便能够为该应用程序中断提供服务。解决方案是通过软件将矢量表重新定位到内部SRAM:
•将矢量表从闪存(映射到应用程序加载地址的基地址)复制到SRAM的基地址0x2000 0000。
•使用SYSCFG配置寄存器1在地址0x0000 0000处重新映射SRAM。
•一旦发生中断,Cortex®-M0处理器将从SRAM中重新定位的向量表中获取中断处理程序的起始地址,然后跳转执行闪存中的中断处理程序。
此操作应在应用程序的初始化阶段完成。请参考AN4065和随附的IAP代码www.st.com更多细节。
基本思想:
1、将中断向量表放入到RAM的起始地址(只需要在应用程序中保留RAM起始地址开始的0x100大小的内存区不使用即可),可根据实际中断向量表的大小作调整,多了没事,少了不行。
2、在bootload中将应用程序的中断向量表从Flash中拷贝到RAM中。(在app程序中去拷贝应该也是可以的?)
3、设置STM32F0xx中断向量表位于RAM中。
在用户应用程序中,按照以上方法,添加以下两行代码(在跳转APP之前执行):
memcpy((void*)0x20000000, (void*)0x08004000, VECTOR_SIZE);
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
其中,0x2000 0000是SRAM的起始地址,这个不需要改动。
而之后的两个参数需要根据实际情况作出修改。0x0800 4000是应用程序的起址地址,从这里开始的VECTOR_SIZE字节,存放是的应用程序的中断向量表。VECTOR_SIZE是指中断向量表的大小,具体多大可以在startup.s文件里计算得到。以下以startup_stm32f030.s为例作说明:
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__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 0 ; Reserved
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 0 ; Reserved
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_IRQHandler ; ADC1
DCD TIM1_BRK_UP_TRG_COM_IRQHandler ; TIM1 Break, Update, Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD 0 ; Reserved
DCD TIM3_IRQHandler ; TIM3
DCD 0 ; Reserved
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
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =__initial_sp ; set stack pointer
MSR MSP, R0
我们只需关注其中的一小部分。从29行开始,直到75行,每一个DCD都代表一个中断向量(所谓中断向量,说得明白点,其实就是某个中断服务程序的入口地址)。例如第74行的:
DCD USART1_IRQHandler ; USART1
这里的“USART1_IRQHandler"其实就是UART1中断服务程序USART1_IRQHandler这个函数,同时,它也代表这个函数的入口地址。
以上代码即定义了这样一张表,这张表包括45个元素,每个元素是一个长度为4字节的地址。除了第一个地址是SP(堆栈指针)外,其它的地址都是某个中断服务程序的入口地址。
那么,回到我们要解决的问题上来,之前memcpy函数中的第三个参数VECTOR_SIZE,针对本例,就应该是45*4=180(0xB4)个字节。
在执行完以上两行代码后,若发生中断,CPU就会去SRAM(即0x2000 0000处)取中断向量了,所以,以0x2000 0000作为起始地址之后的VECTOR_SIZE个字节就不能被改动了。为了达到这VECTOR_SIZE个字节不被修改的目的,如下两种方法可以实现。
•在工程文件内修改SRAM的起始地址以及长度,预留出空间存储中断向量表,如下图
•如果使用了分散加载文件,则在分散加载文件中修改SRAM的起始地址及长度也能达到目的。
至此,STM32F0系列Cortex-M0内核芯片中断向量表重映射的问题已解决。
注意:关于STM32F0的IAP,ST官方有套参考代码,即STM32F0xx_AN4065_FW_V1.0.0例程。
参考:
https://www.cnblogs.com/outs/p/4948134.html
https://blog.csdn.net/findaway123/article/details/41602931
http://mcu.eetrend.com/content/2018/100010135.html