# Bootloader 说明

编写 bootloader 程序步骤说明:【源代码在文末】

  • 根据程序代码区域的大小划分空间
  • 编写 Bootloader
  • 码 APP

# 1、阅读手册,划分空间

image-20230731092115145

我用的是这个型号的,他的空间有 256KB 是可以用来运行代码的。

# 所以可以这样划分:

  • Bootloader FLASH_BASE(((uint32_t)0x08000000U)) 32KB
  • APP1 FLASH_BASE | 0x08000 64KB
  • APP2 FLASH_BASE | 0x18000 64KB

image-20230731094418293

# 程序流程规划:

image-20230731092420894

# 2、编写 Bootloader

# 主函数:

#define BOOTLOADER_APP1_ADDRESS 0x08008000U  // 定义 APP 地址
#define BOOTLOADER_APP2_ADDRESS 0x08018000U
int main(){
    //1、初始化【串口、flash、定时器等外设】
    
    //2、初始化延时等待,例如等待多久后开始进入 app 区执行
    
    //3、判断升级标志位,如果有就更新程序
    
    while(1){
        //2 秒超时时间到、开始跳转
        if(isTimeOut(&time,2)){
            if(((*(__IO uint32_t *)(BOOTLOADER_APP1_ADDRESS + 4)) & 0xFF000000) == 0x08000000){
                iap_load_app(BOOTLOADER_APP1_ADDRESS);
            }else{
                // 这里没有可执行程序 APP1
                
                // 检查 APP2 区域是否有程序
                if(((*(__IO uint32_t *)(BOOTLOADER_APP2_ADDRESS + 4)) & 0xFF000000) == 0x08000000){
                    iap_load_app(BOOTLOADER_APP2_ADDRESS);
                }else{
                    //APP1 APP2 均无程序
                    
                }
            }
        }else{
            // 等待其他操作,可以在这里选择启动模式【因为咱们有两个 APP 区,所以在这里可以选择强制从其中一个启动】
            ...
        }
    }
}

# iap.h

#ifndef __IAP_H__
#define __IAP_H__
#include "systick.h"
// 用户根据自己的需要设置
#define STM32_FLASH_SIZE 1024 	 		// 所选 STM32 的 FLASH 容量大小 (单位为 K)
#if STM32_FLASH_SIZE < 256
#define STM_SECTOR_SIZE 1024 // 字节
#else
#define STM_SECTOR_SIZE 2048
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////
//FLASH 起始地址
#define GD32_FLASH_BASE   0x08000000   	//GD32 FLASH 的起始地址
typedef  void (*iapfun)(void);				// 定义一个函数类型的参数.   
   
void iap_load_app(uint32_t appxaddr);			// 跳转到 APP 程序执行
void iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t applen);	// 在指定地址开始,写入 bin
#endif

# iap.c

iapfun jump2app; 
// 设置栈顶地址
//addr: 栈顶地址
__asm void MSR_MSP(uint32_t addr)
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}
//WriteAddr: 应用程序的起始地址,必须为某扇区的起始地址
//pBuffer: 应用程序 CODE.
//NumToWrite: 应用程序大小 (字节).
void iap_write_appbin(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t NumToWrite)
{
	uint32_t i = 0;
	uint16_t temp;
    if (WriteAddr < GD32_FLASH_BASE || (WriteAddr >= (GD32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
    {
        printf("地址越界!\r\n");
        return;   
    }
          
    if(WriteAddr % STM_SECTOR_SIZE)
    {
        printf("地址非FLASH扇区首地址!\r\n");
        return;
    }
    /* 开始写入 */
    fmc_unlock();
    while(1)
    {
        if(NumToWrite == 0)// 上一个扇区刚好完整写完
        {
            break;
        }
        else if(NumToWrite < STM_SECTOR_SIZE)// 剩余要写入的内容不到一个扇区
        {
            fmc_page_erase(WriteAddr); // 擦除这个扇区 
            
            for(i = 0; i < NumToWrite; i += 2)
            {
                temp  = (uint16_t)pBuffer[i + 1] << 8;
                temp |= (uint16_t)pBuffer[i];
                fmc_halfword_program(WriteAddr, temp);
                WriteAddr += 2;
            }
            break; // 写入结束,退出 while
        }
        else
        {
            fmc_page_erase(WriteAddr); // 擦除这个扇区
            
            // 写入整个扇区
            for(i = 0; i < STM_SECTOR_SIZE; i += 2)
            {
                temp  = (uint16_t)pBuffer[i + 1] << 8;
                temp |= (uint16_t)pBuffer[i];
                fmc_halfword_program(WriteAddr, temp);
                WriteAddr += 2;
            }
           
            pBuffer += STM_SECTOR_SIZE;
            NumToWrite -= STM_SECTOR_SIZE;
        }
    }
    fmc_lock(); // 上锁
}
// 跳转到应用程序段
//appxaddr: 用户代码起始地址
void iap_load_app(uint32_t appxaddr)
{
    unsigned char i = 0;
	if(((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	// 检查栈顶地址是否合法
	{ 
        /* 首地址是 MSP,地址 + 4 是复位中断服务程序地址 */
        jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);
            
         /* 关闭全局中断 */
        __set_PRIMASK(1); 
                 
        /* 关闭滴答定时器,复位到默认值 */
        SysTick->CTRL = 0;
        SysTick->LOAD = 0;
        SysTick->VAL = 0;
        
        /* 设置所有时钟到默认状态 */
        //RCC_DeInit();
        rcu_deinit();
        
        /* 关闭所有中断,清除所有中断挂起标志 */  
        for (i = 0; i < 8; i++)
        {
            NVIC->ICER[i]=0xFFFFFFFF;
            NVIC->ICPR[i]=0xFFFFFFFF;
        }
        
        /* 使能全局中断 */ 
        __set_PRIMASK(0);
        
        /* 在 RTOS 工程,这条语句很重要,设置为特权级模式,使用 MSP 指针 */
        __set_CONTROL(0);
        
        MSR_MSP(*(__IO uint32_t*)appxaddr);					// 初始化 APP 堆栈指针 (用户代码区的第一个字用于存放栈顶地址)
        jump2app();									// 跳转到 APP.
        
        /* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
        printf("-----------jump error!-----------\n");
        while (1)
        {
							
        }
	}
}

# 3、码 APP

那么 APP 区主要干几件事

# 这里记得设置一下

image-20230731162943149

int main(){
    //0、配置中断向量表
    SCB->VTOR = FLASH_BASE | 0x08000;
    //1、初始化
    
    while(1){
        //2、接收升级数据包
        updatarecieve();
        
		if(updata){
            //3、更新升级标志位
            //4、复位
            NVIC_SystemReset();
        }
    }
}

具体的代码有点多,也没有整理。大家感兴趣直接上 gitee 看吧。

项目全部代码

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

flechazo 微信支付

微信支付

flechazo 支付宝

支付宝

flechazo 贝宝

贝宝