用的芯片是f103RCT6,编译环境是keil5
从boot跳转到APP需要做的事:
我们首先应该把boot和app看作两个独立的程序,而由于执行一个程序第一件事就是初始化堆栈指针,这样才可以执行C语言程序;其次就是更新中断向量表,这样后续发生中断时才能进入相应的中断服务函数。所以跳转步骤为:
1、 屏蔽所有中断,防止在跳转过程中,中断干扰出现异常
2、初始化APP堆栈指针(用户代码区的第一个字起始位置用于存放栈顶地址)
3、清除B区使用的外设和所有使用的中断标志位
4、更新中断向量完成跳转
注:想要跳转后成功执行app程序,app程序的起始位置和中断向量表位置应该和BOOT跳转后的位置对应上;并且应该在程序开始时开启总中断。
具体实现:
main.c
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#include "boot.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
BootLoader_Brance();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/*************************************************************/
main.h
#define FLASH_SADDR 0x08000000 //FLASH起始地址
#define PAGE_SIZE 2048 //FLASH扇区大小
#define PAGE_NUM 128 //FLASH总扇区个数
#define B_PAGE_NUM 32 //B区扇区个数
#define A_PAGE_NUM PAGE_NUM - B_PAGE_NUM //A区扇区个数
#define A_START_PAGE B_PAGE_NUM //A区起始扇区编号
#define A_SADDR FLASH_SADDR + A_START_PAGE * PAGE_SIZE //FA区起始地址
/*************************************************************/
boot.c
load_a load_A;
void BootLoader_Brance(void){
if(OTA_Info.OTA_flag == OTA_SET_FLAG){
myprintf("OTA更新\r\n");
}else{
myprintf("跳转A分区\r\n");
//Delay_Ms(1000);
LOAD_A(A_SADDR);
}
}
__asm void MSR_SP(uint32_t addr){
MSR MSP, r0 //更改栈顶指针初始值
BX r14
}
void LOAD_A(uint32_t addr){
if((*(uint32_t *)addr>=0x20000000)&&(*(uint32_t *)addr<=0x2000C000)){ //由于我们已经下载了app程序,所以此时判断app程序的起始地址中保存的栈顶是否位于内存区域
MSR_SP(*(uint32_t *)addr); //将栈顶指针指向app程序设置的栈顶而不是bootloader的栈顶,当然这两个程序的栈顶一般都为0x20000000
load_A = (load_a)*(uint32_t *)(addr+4); //执行load_a函数时会让单片机pc指针指向app程序的复位向量,然后从这里开始往下执行,符合一个程序的正常开始流程
BootLoader_Clear(); //清除所有外设
CLOSE_ALL_INT(); //屏蔽所有中断并清除中断标志位
load_A(); //跳转
}
}
void BootLoader_Clear(void){
HAL_UART_MspDeInit(&huart1); //UART+对于DMA
HAL_SPI_MspDeInit(&hspi1); //SPI+对于DMA
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_11|GPIO_PIN_12); //软件IIC
HAL_GPIO_DeInit(GPIOD, GPIO_PIN_2); //LED1
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_8|GPIO_PIN_2); //LED0和F_CS
//HAL_DeInit();
}
void CLOSE_ALL_INT(void)
{
__set_PRIMASK(1); // 失能全局中断
// 关闭滴答定时器,复位到默认值
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 设置所有时钟到默认状态,使用HSI时钟
HAL_RCC_DeInit();
// 关闭所有中断,清除所有中断挂起标志
for (int i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
}
/*************************************************************/
boot.h
#ifndef BOOT_H
#define BOOT_H
#include "stdint.h"
typedef void (*load_a)(void); //设置一个无参数无返回值的函数指针
void BootLoader_Brance(void);
__asm void MSR_SP(uint32_t addr);
//void MSR_SP(uint32_t addr);
void LOAD_A(uint32_t addr);
void BootLoader_Clear(void);
void CLOSE_ALL_INT(void);
#endif
这里我们设置bootloader的起始地址为:0x8000000,大小为0x10000,那么下载程序时擦除应该设置为按扇区擦除而不是整片擦除,app程序起始地址为0x8010000大小为0x30000,同理。
部分擦除设置擦除如下:
同时app程序的起始位置和中断向量表位置应该和BOOT跳转后的位置对应上;并且应该在程序开始时开启总中断。
中断向量表偏移0x10000,和boot跳转后0x8010000对应上了,同时app起始地址和大小也要设置。
开启总中断:
从APP跳转到boot:硬件复位或者软件复位。软件复位具体实现为:
参考网上一些博主说的,在调用复位函数和真正复位之间还有一段延迟,在这段时间单片机还是可以正常处理中断等程序的,为了避免这种情况,应该把相应的中断都屏蔽掉。
/*函数功能:STM32软复位函数 */
Void Stm32_SoftReset(void) {
__set_FAULTMASK(1);//禁止所有的可屏蔽中断
NVIC_SystemReset();//软件复位
}
//复位后就不需要再使能开启中断了,因为系统已经重置了。
参考链接:STM32笔记——软件复位相关知识小记_nvic_systemreset会让 rst引脚拉地么-优快云博客
【手把手写【STM32/GD32】OTA升级Boot程序,利用函数指针,编写跳转A区程序】https://www.bilibili.com/video/BV1BY4y1y7Rb?vd_source=d1e91e9e17a031e2d6e11ed04c3ab205
/***************** 补充 ****************/
对于APP程序,APP的ADDRESS RANGE起始地址必须为08000000,即保持默认不要动类似下图这样,不然APP程序比较大时烧录可能会失败,比如我用F407VGT6,Start地址设置为程序的起始地址0x8010000,然后烧录时verify就报错Contents mismatch at: 08080094H (Flash=05H Required=A5H) !;改回成下图这样就能正常烧录;
当然APP程序只有下面这处地方是保持默认,其他地方按照前面的该怎么改怎么改;
参考如下:
GD32/KEIL Contents mismatch at: 08040000H (Flash=F3H Required=FFH) !-优快云博客