一、实验目的
实验平台: 使用的是正点原子阿波罗F429IGT6开发板,芯片为STM32F429IGT6,使用的仿真器为CMSIS-DAP
开发环境: Keil软件,编译器使用Keil内置的VC6编译器,编译优化选择-O3
实验前言: STM32的单片机出厂的时候在内部Flash中烧录了一段程序,这个程序称为自举程序Bootloader,用户可以通过拉高Boot0引脚的方式设置单片机进入Bootloader,也可以通过软件跳转的方式进入Bootloader,进入Bootloader以后就可以基于串口或者USB等接口实现程序烧录达到更新固件的目的。
实验目的: 主要是想通过软件方式在用户程序中跳转到STM32内置的Bootloader程序,然后通过串口或者USB接口实现固件更新。
图1.1:使用的开发板示意图
二、问题描述
遇到的问题: 按照安福莱的教程,编写了相应的代码,但是无法成功跳转到系统Bootloader中,主要现象是一跳转到系统Bootloader后,就会发生软件复位,导致单片机复位。
所使用的有问题的代码如下所示: 在main()函数中调用此函数跳转Bootloader后单片机会直接软复位。
static void JumpToApp(void)
{
uint32_t i = 0;
void (*SysMemBootJump)(void); /* 声明一个函数指针 */
__IO uint32_t BootAddr = 0x1FFF0000; /* STM32F429的系统BootLoader地址 */
__set_PRIMASK(0); /* 关闭全局中断 */
SysTick->CTRL = 0; /* 关闭滴答定时器,复位到默认值 */
SysTick->LOAD = 0;
SysTick->VAL = 0;
HAL_RCC_DeInit(); /* 设置所有时钟到默认状态,使用HSI时钟 */
for (i = 0; i < 8; i++) /* 关闭所有中断,清除所有中断挂起标志 */
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
__set_PRIMASK(1); /* 使能全局中断 */
SysMemBootJump = (void (*)(void)) (*((uint32_t *) (BootAddr + 4))); /* 跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址 */
__set_MSP(*(uint32_t *)BootAddr); /* 设置主堆栈指针 */
__set_CONTROL(0); /* 设置为特权级模式,使用MSP指针 */
SysMemBootJump(); /* 跳转到系统BootLoader */
while (1)
{
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
}
}
三、问题解决
首先给出能够成功跳转到Bootloader的代码: 如下所示,在main()函数中调用此函数即可成功跳转到Bootloader。
重点注意事项:
static void DirJumpToBootloader(void)
{
uint32_t i=0;
void (*SysMemBootJump)(void);
volatile uint32_t BootAddr = 0x1FFF0000;
/* 重点:下面的顺序不可以弄错,必须使用__disable_irq关闭中断,随后先复位RCC,再复位SysTick */
__disable_irq(); /* Disable all interrupts */
HAL_RCC_DeInit(); /* Set the clock to the default state */
HAL_DeInit();
SysTick->CTRL = 0; /* Disable Systick timer */
SysTick->VAL = 0;
SysTick->LOAD = 0;
for (i=0; i<sizeof(NVIC->ICER)/sizeof(NVIC->ICER[0]); i++) /* Clear Interrupt Enable Register & Interrupt Pending Register */
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
__enable_irq(); /* Re-enable all interrupts */
SysMemBootJump = (void (*)(void)) *((uint32_t *) ((BootAddr + 4))); /* Set up the jump to boot loader address + 4 */
__set_MSP(*(uint32_t *)BootAddr); /* Set the main stack pointer to the boot loader stack */
__HAL_REMAPMEMORY_SYSTEMFLASH(); /* Remapping */
SysMemBootJump(); /* Call the function to jump to boot loader location */
while (1)
{
/* Code should never reach this loop */
}
}
上述代码主要的改动有四处,分别为:
(1) 使用__disable_irq()关闭中断,而不是使用__set_PRIMASK(0);
(2) 先复位HAL_RCC_DeInit()然后再清空SysTick,否则无法关闭SysTick定时器
(3) 使用__enable_irq()开启中断,而不是使用__set_PRIMASK(1);
(4) Keil软件中一定要使用-O3优化,不要使用-O1
通过上述三处修改即可成功跳转到Bootloader,然后就可以使用CubeProgrammer软件通过UART或者USB接口实现程序的烧录。
四、其他方案
基于软件复位的方式跳转Bootloader: 根据安福莱的教程,还有一种跳转Bootloader的终极教程,该方法通过软件复位实现所有片内寄存器的复位,能够给Bootloader程序提供一个绝对干净的运行环境,具体可以参考链接基于软件复位跳转Bootloader。
本文给出该方式的代码: main()部分内容函数如下,完整代码参考博客最后的完整工程附件:
uint32_t g_JumpInit __attribute__( ( section( ".bss.NoInit")));
int main(void)
{
if(g_JumpInit == 0xAA553344)
{
SftJumpToBootloader(); /* 软件复位后跳转 */
}
g_JumpInit = 0xAA553344;
HAL_NVIC_SystemReset(); /* 软件自动复位后跳转 */
}
软件复位方式跳转Bootloader代码:
static void SftJumpToBootloader(void)
{
void (*SysMemBootJump)(void);
volatile uint32_t BootAddr = 0x1FFF0000;
g_JumpInit = 0xAA553344;
SysMemBootJump = (void (*)(void)) *((uint32_t *) ((BootAddr + 4))); /* Set up the jump to boot loader address + 4 */
__set_MSP(*(uint32_t *)BootAddr); /* Set the main stack pointer to the boot loader stack */
__HAL_REMAPMEMORY_SYSTEMFLASH(); /* Remapping */
SysMemBootJump(); /* Call the function to jump to boot loader location */
while (1)
{
/* Jump is done successfully */
/* Code should never reach this loop */
}
}
五、完整工程代码
工程下载百度云链接
链接:https://pan.baidu.com/s/1BBTdN3zUhlxPc2hsiM3B8Q?pwd=1234
提取码:1234