1 前言
在使用F0的片子在增加IAP后,我们经常发现,原来的APP必须增加一段代码,将中断向量表从内部FLASH拷贝到SRAM后再执行REMAP到SRAM,这样操作后APP才能正常运行,这一过程一直困扰着蝶粉们,为什么需要这样呢?本文将针对这一问题为大家解惑。
2 问题详细描述
比如F0的下面这部分代码:
<code class="language-C++ hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">define</span> APPLICATION_ADDRESS (uint32_t)0x08004000</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Private macro -------------------------------------------------------------*/</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Private variables ---------------------------------------------------------*/</span>
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">if</span> (defined ( __CC_ARM ))</span>
__IO uint32_t VectorTable[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">48</span>] __attribute__((at(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x20000000</span>)));
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">elif</span> (defined (__ICCARM__))</span>
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">pragma</span> location = 0x20000000</span>
__no_init __IO uint32_t VectorTable[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">48</span>];
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">elif</span> defined ( __GNUC__ )</span>
__IO uint32_t VectorTable[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">48</span>] __attribute__((section(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">".RAMVectorTable"</span>)));
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">endif</span></span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> main(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span>)
{
uint32_t i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
HAL_Init();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Configure the system clock to 48 MHz */</span>
SystemClock_Config();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Copy the vector table from the Flash (mapped at the base of the application
load address 0x08004000) to the base address of the SRAM at 0x20000000. */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span>(i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">48</span>; i++)
{
VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>));
}
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Enable the SYSCFG peripheral clock*/</span>
__HAL_RCC_SYSCFG_CLK_ENABLE();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Remap SRAM at 0x00000000 */</span>
__HAL_SYSCFG_REMAPMEMORY_SRAM();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Add your own code here...
*/</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//…</span>
}
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li></ul>
如上所示,在没有加入IAP之前这部分代码不需要的,而加入IAP后,这部分代码在F0中就必须添加了,不然APP会运行部正常! 从代码中可以看出,增加的代码执行了从内部FLASH中拷贝中断向量表到内存,然后重映射SRAM,可是,蝶粉们不禁要问,为什么要这么操作?这个又是什么原因造成的?
3 原因分析
3.1 重映射
在搞懂这个问题之前我们首先先来看看什么是重映射。例如F072的参考文档张SYSCFG寄存器的介绍,如下图:
MEM_MODE的介绍如下:
从以上内容我们可以得到以下信息:
1. MEM_MODE的值在上电后有BOOT0,BOOT1的状态值决定。
2. MEM_MODE的值决定了哪个内存映射到地址0x0000 0000
也就是说:
当MEM_MODE =00/10时,Main Flash映射到地址0x0000 0000,即地址0x0800 0000映射到0x0000 0000.
当MEM_MODE =01时,System Flash映射到地址0x0000 0000,也就是芯片自带的Bootloader代码部分会映射到地址0x0000 0000,即0x1FFF C800映射到地址0x0000 0000.
当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的值来完成的。
3.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函数,就这样,整个代码启动完成。
接下来就是中断产生于中断响应了。
3.3 中断与中断向量表
从ARM官网上的信息得知(http://infocenter.arm.com/help/index.jsp ),在Coretext-M3与Coretext-M4核中,在System Control Block中存在一个向量表偏移量寄存器 VTOR(0xE000ED08),系统产生中断后,内核通过这个寄存器的值来找到中断向量表的地址,进而执行中断例程代码,当然,此寄存器的值是可以修改的,它的默认值为0。实际上在大部分的M3和M4的工程中,一般都是在SystemInit函数中对此寄存器的值进行设置,当此之前,它的值为默认值0,由于映射关系,实际上就是指向映射地址,比如0x0800 0000. 如下图所示:
![M4](https://img-blog.csdn.net/20161013165951978)
但是,由于STM32F0XX采用的是M0核,它是没有这个VTOR寄存器的,那么它又是怎么找到中断向量表的地址的呢?同样在ARM官网上我们找到如下信息:
也就是说,对于M0来说,中断向量表的地址固定在地址0x0000 0000上!
对此,我们也可以这么理解,将M0理解成M3/M4的特殊情况,M0假设也存在VTOR这么一个虚拟寄存器,只不过它的值不能修改,固定为0罢了,而M3/M4的这个VTOR寄存器一开始时它的值也是为默认值0,只不过在程序运行到SystemInit()函数后,在代码中明确对其进行了修改。
我们整理下STM32F0XX中断的调用过程:
-> 产生中断
-> CPU固定到地址0x0000 0000上找中断入口函数,由于映射关系,实际上是在从映射地址上寻找。
-> 找到并执行中断例程
此时,可以回到我们一开始的问题上来了.
3.4 回到问题分析
3.4.1 从IAP跳转到APP的过程分析
从IAP跳转到APP时到底发生了什么呢?首先我们来看看这个跳转代码:
<code class="language-C hljs applescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">/* Test <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> user code <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> programmed starting <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">from</span> address <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"APPLICATION_ADDRESS"</span> */
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">(*(__IO uint32_t*)</span>APPLICATION_ADDRESS) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x2FFE0000</span> ) == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x20000000</span>)
{
/* Jump <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> user <span class="hljs-type" style="box-sizing: border-box;">application</span> */
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>);
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user <span class="hljs-type" style="box-sizing: border-box;">application</span>'s Stack Pointer */
__set_MSP<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">(*(__IO uint32_t*)</span> APPLICATION_ADDRESS);
JumpToApplication();
}
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
如上,首先测试APP地址是否存在用户代码,如果存在,则首先将APPLICATION_ADDRESS + 4,作为跳转地址,然后从APPLICATION_ADDRESS取栈顶并赋值给SP寄存器,最后跳转。
于是我们可以得出结论:
跳转代码后,PC指针变了,SP栈指针也修改了,但是,中断向量表的位置并没有修正为APP的中断向量表位置,还是采用IAP的中断向量表位置。如果此时系统产生中断,CPU还会固定从0x0000 0000地址中去找中断入口,由于从IAP到APP,内存映射并没有改变,因此,实际上还是从0x0800 0000上去找中断入口,也就是还是在IAP的中断向量表上寻找,继续执行IAP的中断例程,进而引发hard fault,这显然不是我们想要的。
3.4.2 问题分析
这里的关键是,如何将中断向量表的寻找位置从0x0800 0000修改到0x0800 3000(假设为APP的地址)? 从之前的分析我们可以得出有两种方法:
1 修改寄存器VTOR的值
2 内存重映射
M3/M4核的MCU显然采用了第一种方法,因此,在M3/M4的MCU上,即使添加了IAP,也不需要增加本文第二章所描述的代码。而这部分代码,正是M0采用的第二种方法,也是唯一可以修改寻找中断向量方式的方法。
通过将SRAM重映射到地址0x0000 0000,那么,M0系统产生中断后,CPU还是从地址0x0000 0000寻找中断入口,但是,实际上不再是寻址0x0800 0000,而是寻址0x2000 0000,这么一来,接下来我们就只需要将中断向量表整个拷贝到SRAM上,也就是0x2000 0000上,就这样,CPU就可以正常寻址中断向量表了。
4 结论
在使用STM32F0XX时,若增加IAP,则必须在APP中将中断向量表拷贝到SRAM,并将系统重映射到SRAM,这样才能使中断恢复正常工作。