近期想研究下stm32基于BootLoader的IAP功能,要想实现这个,必须先搞明白程序跳转,在真正开搞前折腾了一下午,完成了程序跳转的简单测试。共用了两个程序,一个放在0x8000000开始的位置,暂称为Load程序;另一个放在0x8004000的位置,暂称为APP程序。两个程序分别包含以下功能:
1.Load程序:进入程序后首先向串口打印一句"This means START!”,然后在while(1)里循环打印“In While”,间隔为1秒;设立一个按键外部中断,中断发生后跳转到0x8004000的位置以运行APP程序。
2.APP程序:跳转过来后直接进到while(1)里循环打印“This is APP”,间隔为1秒,5秒后跳回Load程序。
下面先以最终的成功案例来介绍Load和APP程序。当然出于实验,也可以用在Load、APP程序中分别以不同形式操作led的方法,来检测Load和APP之间的跳转。
一、成功实验过程
1.Load程序
stm32代码下载到0x8000000开始的地址,程序跳转的意思也就是在程序运行时,通过一个跳转函数使得PC指针指到制定位置继续运行。看上去很简单但是重点需要注意跳转前后的中断的处理。
while(1)附近的程序如下
printf("This means START!\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(1000);
printf("In While\n");
重点在于按键中断中的跳转函数Jump_test,程序如下
void Reset_test(void)
{
typedef void (*iapfun)(void);
uint32_t JUMP_ADDR = 0x08004000;
uint32_t STACK_ADDR = 0x20000000;
uint32_t RESET_IRQ_ADDR = JUMP_ADDR + 4;
iapfun jump2app;
jump2app = (iapfun)*(volatile uint32_t *)RESET_IRQ_ADDR;
__set_MSP(STACK_ADDR);
__disable_irq();
jump2app();
}
RESET_IRQ_ADDR 设置为JUMP_ADDR +4是因为stm32中断向量中,复位中断的相对地址为起始地址+4,如图所示;__set_MSP(STACK_ADDR)的作用为重新初始化堆栈;同时,在跳转前__disable_irq()是为了关闭所有中断,这似的在跳转过程中不会进入“很有缘分”的中断……
2.App程序
App程序在这里就更简单了,直接上代码
int main(void)
{
__enable_irq();
SCB->VTOR = 0x8004000;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
int count = 0;
while (1)
{
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_7);
printf("This is APP\n");
if(count >= 5)
HAL_NVIC_SystemReset();
count++;
}
其中头两行的__enable_irq()和SCB->VTOR = 0x8004000要在进入main函数一开始就执行,这是为了打开跳转前关闭的中断,并且重定位向量表,否则APP程序里的与中断相关的程序会炸。重定位中断向量时,往VTOR寄存器中写入的值即为APP程序的起始地址,具体为什么要这样,请参考《ARM Cortex-M3与M4权威指南》。经过用stm32Cube自动生成的一些列初始化后,就到了APP程序的主体。可以看到当定义的count变量大于5时,直接运行HAL_NVIC_SystemReset()使系统软重启,程序又从0x8000000的位置开始执行,即为跳回了Load程序。
这里需要注意的是在APP跳回Load程序时,本以为可以类比于Load跳到APP,即用Jump_test函数,跳到0x8000000的位置,结果发现这样操作时,虽然能跳回去,但Load程序中跟中断相关的代码会出现异常。如定时器中断一直处于活跃状态,无论怎么清标志位都出不来;同时,外部按键中断也因为中断优先级的问题偶尔挂起,无论怎么清除中断挂起状态都不行。DeBug了半天,看寄存器值看的都眼花了,还是无果。干脆直接重启系统,反正重启系统也是从0x8000000开始运行……
3.程序下载
3.1.Load程序下载
Load程序就是下载在0x8000000开始的位置,这个和普通下载程序一样,直接用默认值下载就好了,打开keil的Options for Target,选择Target选型卡,可以看到下图,红框里就是默认位置,只不过我将Size设置成了0x4000,也就是16k,因为Load程序很小,16k足够了,同时APP下在0x8004000的位置,正好紧邻着Load程序。
在选择Utilities选项卡,点击Settings,在点击Flash Download选项卡,如下图所示,Start和Size同上,这样一来。设置好后就可以先下载Load程序到单片机了。
3.2.APP程序下载
同理的,APP设置过程同上,只是记得把Start设置为0x8004000,size随便设置,只要放得下你的程序就好了。设置好后点击下载,程序就被下到了0x8004000开始的位置。下面就可以测试啦。
4.程序测试
链接好ttl转串口线,打开串口助手后,可以观察到Load程序的运行结果
按下外部中断对应的按键后,跳入APP程序,打印结果如下。可以观察到打印6次This is APP后系统复位,重新进入Load程序。测试成功。
二、总结
1.跳转函数里要重新初始化堆栈,并禁用中断;
2.APP程序一进main函数首先要开启中断,并重定位中断向量;
3.APP跳回Load时直接用软复位就好了。如果用Jump_test类似的函数,跳回Load后整个中断系统会出现异常,不如直接复位重启所有,古人云出了Bug先重启,是有一定道理的……
通过这次实验实现了Load与APP间的互跳,接下的任务就是打造一个自己的BootLoader以及IAP程序啦。