STM32之BootLoader
1 简介
bootloader其实就是一段启动程序,它在芯片启动的时候最先被执行,可以用来做一些硬件的初始化或者用作固件热更新,当初始化完成之后跳转到对应的应用程序中去。
bootloader程序需要通过下载器烧写到芯片中,而APP则可以通过有线方式的UART、IIC、USB、SPI等总线来通过bootloader来更新,视所设计的bootloader程序而定。另外,对于无线方式热更新APP,一般是用WiFi、bluetooth通过UART透传的方式烧写芯片APP程序。
另外,也可以合并bootloader的bin文件和APP的bin文件,一次过烧写到芯片中。
2 功能
1.在一定时间内判断是否需要更新 APP,如果需要则接收APP程序并将其烧写到指定地址的 flash 空间里。并从该bootloader中获取栈顶指针和 Reset_Handler 指针,然后跳转执行程序。
2.等待超时后,则在该bootloader地址直接获取之前的栈顶指针和 Reset_Handler 指针并跳转执行。
3 stm32系统的bootloader
3.1 bootloader地址及执行过程
不同系列芯片的bootloader的地址不一样,F10x和F4xx在0x1FFF0000地址
F4和H7系列的执行流程要注意的一点是,如果在MCU进入系统BootLoader前就接入了USB信号线,会导致进入系统BootLoader后优先执行USB DFU方式,导致无法执行其他接口方式。
3.2整体程序执行过程
[①]进入系统存储器并在程序启动后,先从0x1FFF0004处取出复位中断向量地址,执行完复位中断函数后跳转到系统bootloader程序main函数中执行。
[②]当发生中断请求后,程序跳转到中断向量表中取出中断函数入口地址,再跳转到中断服务函数中执行
[③]执行完中断函数后返回main函数中,然后执行系统bootloader过程
[④]成功后跳转到主Flash区(执行跳转指令),或者复位(BOOT0 = 0时)
[⑤]从主Flash的中断向量表处(0x0800 0004)得到相应中断函数地址,执行相应的中断服务函数后,回到APP的main函数中。
[⑥⑦⑧]的过程和前述一致,不再赘述。
3.3进入bootloader的方法
STM32进入系统的bootLoader有两种方法。
3.3.1 设置boot引脚(部分系列会结合选项字节组合成不同模式)
F1系列
3.3.2 应用程序直接跳转到系统bootLoader
注意几个问题:
*禁用所有外设时钟
*禁止使用的PLL
*禁止所有中断
*清除所有中断挂起标志
跳转流程
3.示例代码
void System_Jump2Bootloader(void)
{
uint32_t i = 0;
/** 声明一个函数指针 */
void (SysJump2Boot)(void);
/** STM32F4的系统BootLoader地址 */
__IO uint32_t BootloaderAddr = 0x1FFF0000;
/** 关闭全局中断 */
__disable_irq();
/** 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/** 设置所有时钟到默认状态,使用HSI时钟 */
HAL_RCC_DeInit();
/** 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
/** 使能全局中断 */
__enable_irq();
/** 跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址 */
SysJump2Boot = (void (*)(void)) (*((uint32_t *) (BootloaderAddr + 4)));
/** 设置主堆栈指针 */
__set_MSP(*(uint32_t *)BootloaderAddr);
/** 如果使用了RTOS工程,需要用到这条语句,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
/* 跳转到系统BootLoader */
SysJump2Boot();
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
printf("Jump to bootloader fail\r\n");
break;
}
}
USB DFU方式固件升级
以F4系列为例,F4的系统bootloader地址是0x1FFF0000。注意要将系统bootloader的地址映射到0x00000000。
跳转bootloader程序设计
void System_Jump2Bootloader(void)
{
uint32_t i = 0;
/** 声明一个函数指针 */
void (SysJump2Boot)(void);
/** STM32F4的系统BootLoader地址 */
__IO uint32_t BootloaderAddr = 0x1FFF0000;
/** 关闭全局中断 */
__disable_irq();
/** 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/** 设置所有时钟到默认状态,使用HSI时钟 */
HAL_RCC_DeInit();
/** 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
/** 使能全局中断 */
__enable_irq();
/**
* 重映射到系统flash
* 将系统bootloader的地址映射到0x00000000
*/
__HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();
/** 跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址 */
SysJump2Boot = (void (*)(void)) (*((uint32_t *) (BootloaderAddr + 4)));
/** 设置主堆栈指针 */
__set_MSP(*(uint32_t *)BootloaderAddr);
/** 如果使用了RTOS工程,需要用到这条语句,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
/* 跳转到系统BootLoader */
SysJump2Boot();
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
printf("Jump to bootloader fail\r\n");
break;
}
}
固件升级操作(略)
UART IAP方式固件升级
1.使用系统bootloader做串口IAP升级时,USB接口不要接线到电脑端。
跳转bootloader程序设计
2.在APP程序中利用板载按键来调用下面的函数进入系统bootloader,不需要配置boot0脚为高电平。
void System_Jump2Bootloader(void)
{
uint32_t i = 0;
/** 声明一个函数指针 */
void (SysJump2Boot)(void);
/** STM32F4的系统BootLoader地址 */
__IO uint32_t BootloaderAddr = 0x1FFF0000;
/** 关闭全局中断 */
__disable_irq();
/** 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/** 设置所有时钟到默认状态,使用HSI时钟 */
HAL_RCC_DeInit();
/** 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
/** 使能全局中断 */
__enable_irq();
/**
* 重映射到系统flash
* 将系统bootloader的地址映射到0x00000000
*/
__HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();
/** 跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址 */
SysJump2Boot = (void (*)(void)) (*((uint32_t *) (BootloaderAddr + 4)));
/** 设置主堆栈指针 */
__set_MSP(*(uint32_t *)BootloaderAddr);
/** 如果使用了RTOS工程,需要用到这条语句,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
/* 跳转到系统BootLoader */
SysJump2Boot();
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
printf("Jump to bootloader fail\r\n");
break;
}
}
3.固件升级操作(略)
3.4 退出bootloader的方法
USB DFU方式
更新完毕程序后,不会自动退出USB DFU,需要重新复位芯片后才会退出。由于DFU模式会用到USB线,完成bootloader后一般会拔除USB线,这样就可以使芯片复位。所以是否支持自动退出,并不影响。
UART IAP方式
更新完毕程序后,可以自动退出。所以基于串口来使用系统bootloader比较方便,一般我们也是使用这种方式。
3.5自定义bootloader实现IAP功能
IAP( In Application Programming)在线应用编程,即是用户可以使用自定义程序对单片机用户Flash的某一区域进行烧写,通过BootLoader来完成对App程序的更新升级。实际工作中,是为了产品发布后,可以方便的使用预留的通信接口(串口、USB、网口、蓝牙等)来完成程序的升级,避免需要把产品拆开,再使用仿真下载器来更新应用程序。
步骤:
总体架构
以F407IG为例,芯片flash空间为1MB大小,定义bootloader架构如下。包括Ymodem协议,USART1收发和菜单,flash操作,bootloader空间配置及应用程序跳转等部分。
地址划分
Bootloader 程序区设置于0x8000000 ~ 0x8004000地址范围内。Application程序区设置于0x8004000 ~ 0x8100000地址范围。

整体程序执行过程
flash分配情况
加入自定义IAP程序时的内存分配情况。
执行过程
程序启动后,先从0x08000004处取出复位中断向量地址,执行完复位中断函数后跳转到IAP程序main函数中执行[①]。
当发生中断请求后,程序跳转到中断向量表中取出中断函数入口地址,再跳转到中断服务函数中执行[②],执行完中断函数后返回main函数中[③],然后执行IAP过程,成功后跳转到APP程序[④]。
从偏移后的中断向量表得到相应中断函数地址,执行相应新的中断服务函数后,回到APP的main函数中[⑤]。后面[⑥⑦⑧]的过程和前述一致,不再赘述。
IAP程序执行流程
4 补充
4.1 bootloader擦写flash问题
4.2 中断向量表
如执行过程中所示,中断向量表是存放在Flash区从0x00000004地址(默认)开始的一个数组,数组元素的大小为4个字节,即每一表项的大小为4个字节,这些数组在启动文件中已经初始化好。不同系列根据中断向量的多少,有不同的数组长度。
STM32根据内核和外设中断的优先级,把内核和外设的中断服务函数的地址放在这个数组里面,数组的下标跟中断的优先级对应,也把这个中断的编号叫做中断向量,标号越小,优先级越高。
在启动文件执行的时候,内核和外设的中断服务函数地址都是确定的,地址就在中断向量表中,并且在启动文件里面已经写好了中断服务函数,只是这些中断服务函数为空,而且带[weak]弱定义。
如果使用到相关中断则需要在用户程序里重新实现对应的中断服务函数,而且重写这个中断服务函数的时候,函数名必须跟启动文件里定义好的中断函数名对应,这是因为函数名对应的就是中断服务函数的地址。
当中断发生时,因为每个中断的中断向量不一样,CPU会首先去取向量。然后根据向量来查询中断向量表,最后根据对应的地址找到对应的中断服务函数,从而实现整个中断的响应过程。
中断向量表的设置
如前面介绍所属,中断向量表是默认存放在Flash区从0x00000004地址的。而我们在flash分配时改变APP程序的起始地址为0x08004000,所以我们要设置新的中断向量表的地址。
M0+、M3、M4和M7内核系列的芯片在system_xxx32xxx.c文件中可以找到VECT_TAB_OFFSET这个向量表偏移量宏定义来重新设置中断向量表的地址,也即是修改SCB->VTOR向量表偏移量寄存器。这里我们改成
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET
其中
FLASH_BASE = 0x08000000
VECT_TAB_OFFSET = 0x4000。
相应的APP工程文件中ROM起始地址也修改为0x08004000。
参考文章:
https://blog.csdn.net/weixin_40749320/article/details/123885817
作者:牛子哥嵌入式