STM32 Bootloader跳转到APP程序中无法启动实时系统
一、问题描述:
STM32G431 bootloader跳转到APP中执行,能够进入APP的main函数,但main函数里创建实时系统(CMSIS-RTOS2)失败。
二、问题解决过程:
首先明确:APP程序稍作修改(去掉中断向量偏移,flash起始地址改为0x08000000),不用bootloader,直接烧录到单片机里是肯定可以执行的,实时系统也是可以运行的。
以下是bootloader跳转处的程序(bootloader也用的是CMSIS-RTOS2系统)。
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
jump2app();//跳转到APP
osKernelUnlock();
}
此程序能跳到APP中,但无法创建实时系统。
1、首先想到的是中断,所以在bootloader 添加__set_PRIMASK(1)或__set_FAULTMASK(1)关闭所有中断,在APP中添加__set_PRIMASK(0)或__set_FAULTMASK(0)打开中断。
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
__set_FAULTMASK(1);//__set_PRIMASK(1);
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
jump2app();//跳转到APP
osKernelUnlock();
}
没有成功,接下来我于是调整__set_PRIMASK(0)或__set_FAULTMASK(0)的位置,比如说放到main函数的开头、稍后或者bootloader中,作尝试,都没有成功。
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
__set_PRIMASK(1);
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
__set_PRIMASK(0);
jump2app();//跳转到APP
osKernelUnlock();
}
2、接下来我对程序究竟死在哪儿,做了研究。
如果不加__set_PRIMASK(1),CMSIS-RTOS2的osKernelInitialize()函数返回值是-6,然后会再往下执行。
/// Status code values returned by CMSIS-RTOS functions.
typedef enum {
osOK = 0, ///< Operation completed successfully.
osError = -1, ///< Unspecified RTOS error: run-time error but no other error message fits.
osErrorTimeout = -2, ///< Operation not completed within the timeout period.
osErrorResource = -3, ///< Resource not available.
osErrorParameter = -4, ///< Parameter error.
osErrorNoMemory = -5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
osErrorISR = -6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
osStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
} osStatus_t;
如果加上__set_PRIMASK(1),程序直接运行到HardFault_Handler中断里了。
/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
while (1)
{
}
}
3、在bootloader里对分中断再做处理,禁止Systick、禁止串口中断、禁止外部中断。
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
__set_PRIMASK(1);
SysTick->CTRL = 0X00;//禁止SysTick
NVIC_DisableIRQ(USART1_IRQn);
NVIC_DisableIRQ(EXTI2_IRQn);
NVIC_ClearPendingIRQ(USART1_IRQn);
NVIC_ClearPendingIRQ(EXTI2_IRQn);
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
jump2app();//跳转到APP
osKernelUnlock();
}
没有成功,再对NVIC作清理。
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
__set_PRIMASK(1);
SysTick->CTRL = 0X00;//禁止SysTick
NVIC_DisableIRQ(USART1_IRQn);
NVIC_DisableIRQ(EXTI2_IRQn);
NVIC_ClearPendingIRQ(USART1_IRQn);
NVIC_ClearPendingIRQ(EXTI2_IRQn);
for(i=0;i<8;i++)//适用于中断编号是16~255
{
NVIC->ICER[i] = 0xFFFFFFFF;//写1清除中断使能
NVIC->ICPR[i] = 0xFFFFFFFF;//写1清除中断挂起状态
}
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
jump2app();//跳转到APP
osKernelUnlock();
}
没有成功。
4、接下来,将APP main函数里的程序作屏蔽,能屏蔽的都全部屏蔽了,没有成功,又在bootloader里添加了禁止指令Cache、Data Cache等程序,没有成功,后来怀疑svc中断是不是没有打开。
5、猜想是不是bootloader占了内存,APP运行也占了内存,内存不够呢?于是屏蔽没用到线程,给bootloader的CMSIS-RTOS2堆分配6K,给APP的CMSIS-RTOS2堆分配15K,单片机的总内存是32K;没有成功。
6、看了两个帖子,事情有了眉目。
https://club.rt-thread.org/ask/question/425321.html
https://club.rt-thread.org/ask/question/425358.html
改成这样:
if((Flash_Read_Word(AppCode_Address) & 0xFF000000 ) == 0x20000000)//检查栈顶地址高字节是否为0x20
{
osKernelLock();
__set_PRIMASK(1);
SysTick->CTRL = 0X00;//禁止SysTick
SysTick->LOAD = 0;
SysTick->VAL = 0;
NVIC_DisableIRQ(USART1_IRQn);
NVIC_DisableIRQ(EXTI2_IRQn);
NVIC_ClearPendingIRQ(USART1_IRQn);
NVIC_ClearPendingIRQ(EXTI2_IRQn);
for(i=0;i<8;i++)//适用于中断编号是16~255
{
NVIC->ICER[i] = 0xFFFFFFFF;//写1清除中断使能
NVIC->ICPR[i] = 0xFFFFFFFF;//写1清除中断挂起状态
}
__set_PRIMASK(0);
// SCB->ICSR = 0x9E000000;
__HAL_FLASH_PREFETCH_BUFFER_DISABLE();
__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
__HAL_FLASH_DATA_CACHE_DISABLE();
__HAL_FLASH_INSTRUCTION_CACHE_RESET();
__HAL_FLASH_DATA_CACHE_RESET();
jump2app=(iapFun)*(volatile uint32_t*)(AppCode_Address+4);//APP代码区第二个字存放复位中断的地址
MSR_MSP(*(volatile uint32_t*)AppCode_Address);//APP代码区的第一个字用于存放栈顶地址
SCB->VTOR = 0x08007800;
__set_CONTROL(0);
jump2app();//跳转到APP
osKernelUnlock();
}
成功,于是将屏蔽的线程全部打开、堆分配给够,再试,成功。
三、问题解决:
成功的关键是添加了__set_CONTROL(0),这个的作用是将0写到CONTROL寄存器,即程序运行在特权访问等级,使用MSP主栈指针。
关于CONTROL寄存器的介绍,参考《Cortex M3与M4权威指南》的86页(电子版)、《ARM Cortex-M3与Cortex-M4权威指南(第三版)》的59页(中文纸质)。
有以下的分析:
bootloader的程序本来就运行在特权访问等级和进程栈指针(PSP)模式下,所以__set_CONTROL(0)主要作用是将PSP改为了MSP,所以跳转后APP里的实时系统就可以运行起来了。(周四花费半天、周五花费一天、周日花费半天)