1. 概念
IAP 的作用,网上其他资料已经有很多介绍了,这里放一个链接,不进行深入的介绍。本文的关注重点是Bootloader在跳转APP程序中出现的问题。
IAP的实现原理讲解以及中断向量表的偏移
2. 程序
本人主要做应用层的开发,所有Bootloader和APP程序使用的是STM32CubeMX工具生成代码后,然后进行修改。
2.1 Bootloader 程序
1. CubeMX 配置
步骤1:使用的芯片为STM32F407ZGT6
步骤2:选择时钟源(根据自己的板子进行选择)
步骤3:时钟配置
步骤4:项目配置
2. 代码(只介绍跳转函数)
完整代码
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
int i = 0;
pIapFun_TypeDef pJump2App;
if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //@1 //检查栈顶地址是否合法.
{
HAL_SPI_MspDeInit(&hspi1); //@2
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOG_CLK_DISABLE();
/* 设置所有时钟到默认状态,使用HSI时钟 */
HAL_RCC_DeInit(); //@3
__set_BASEPRI(1); //@4
__set_PRIMASK(1);
__set_FAULTMASK(1);
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++) //@5
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
SysTick->CTRL = 0; //@6
SysTick->LOAD = 0;
SysTick->VAL = 0;
__set_BASEPRI(0); //@7
__set_PRIMASK(0);
__set_FAULTMASK(0);
//@8
/*
1)不使用OS时: 只用到MSP(中断和非中断都使用MSP);
2)使用OS时(如UCOSII): main函数和中断使用MSP; 各个Task(线程)使用PSP(即任务栈);
*/
__set_MSP(*(uint32_t*)ulAddr_App);//当带操作系统从APP区跳转到BOOT区的时候需要将SP设置为MSP,否则在BOOT区中使用中断将会引发硬件错误!
__set_PSP(*(uint32_t*)ulAddr_App);
__set_CONTROL(0); /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
__ISB();//指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
//@9
pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)
pJump2App (); //跳转到APP.
}
}
@1 代码作用:检查栈顶地址是否合法.0x20000000是sram的起始地址,也是程序的栈顶地址;
if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )
@2 代码作用:下面这几个关闭的是在Bootloader中初始化过的外设,如果没有初始化过其他外设,则不需要;
HAL_SPI_MspDeInit(&hspi1);
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOG_CLK_DISABLE();
@3 代码作用:PLL在Bootloader中已经配置启动了,在APP程序中如果想再进行配置启动的话会返回错误。
问题现象
将Bootloader和APP程序分别下载到板子上,Bootlader程序可以正常运行,而APP程序会死在Error_Handler()的while(1)循环中。具体调试发现程序是在执行HAL_RCC_OscConfig()函数的PLL 配置部分检测到当前PLL已经被配置为了系统时钟而返回了HAL_ERROR的返回值导致进入了Error_Handler()。
方案一: 网上有人的建议是不使用PLL,使用HSI作为系统的时钟源,经过测试可以正常运行;但是这样会有个问题是,使用STM32F4的HSI是16M,这显然和通过PLL倍频之后使用的168M不在一个数量级上。
方案二: 通过查找资料发现,可以在Bootloader启动的时候配置PLL倍频到168M,然后在跳转程序之前将RCC的配置反初始化为默认设置(使用HSI时钟)。
HAL_RCC_DeInit();
@4 代码作用:关闭所有的中断;
__set_BASEPRI(1);
__set_PRIMASK(1);
__set_FAULTMASK(1);
@5 代码作用:清除所有中断挂起标志,防止在APP中触发该中断,导致运行错误;
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++) //@5
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
@6 代码作用:关闭掉系统滴答定时器,该定时器会在APP的程序中重新启动,调用HAL_Init();函数会启动;
注意:@6 的代码一定要放到@3 之后,因为HAL_RCC_DeInit();函数会再次开启滴答定时器。
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
@7 代码作用:重新启动中断开关;
__set_BASEPRI(0);
__set_PRIMASK(0);
__set_FAULTMASK(0);
注意:必须重新启动中断的开关,本人调试过程中没有开启__set_FAULTMASK(0) ;的中断,导致滴答定时器(SysTick)无法正常运行,执行HAL_Delay();,死循环在了延时中,耽误了一天的时间查找问题。
@8 和@9 代码都有详细的注释;
注意: __set_PSP((uint32_t)ulAddr_App); 和 __set_CONTROL(0); 函数是使用RTOS(实时操作系统的读者必须要添加的)
2.2. APP程序
CubeMX 配置基本和Bootloader 基本相同,不赘述。本人使用的FreeRTOS的实时操作系统。
1. Keil 配置
步骤1:配置程序的起始位置(根据自己的需要修改),配置的时候发现只配置这块没用,需要配置步骤2才行;
步骤2:Linker 中修改ScatterFile 文件;
这是本人的配置,每个人的可能都不一样,本人是需要用ccmram,所以使用自己的ScatterFile 文件,修改的内容如下图。
步骤2:设置APP的中断向量表的位置;
首先找到 startup_stm32f407xx.s 文件中调用 SystemInit 函数的地方,找到SystemInit 函数的实现,然后 找到 USER_VECT_TAB_ADDRESS 的宏定义的地方,取消注释。先修改下图 1的位置,然后修改 2的位置,2的位置根据自己的APP偏移位置来修改。
2. 代码
在main 函数的一开始添加如下代码。
/* USER CODE BEGIN 1 */
HAL_DeInit();
HAL_RCC_DeInit();
/* USER CODE END 1 */
HAL_DeInit(); HAL库反初始化;
HAL_RCC_DeInit(); RCC配置反初始化;
注意: 如果直接查找HAL_RCC_DeInit() 的实现,发现是个空函数,实际这是HAL库的一个机制,实际编译使用的另外一个文件中的,如下图1和2