STM32上添加bootloader+application,在外部flash中备份及升级程序

近期看到一篇关于bootloader的博文,亲手也尝试了一下,这里做下笔记,方便日后查看,根据项目修改可以加个DTU,用服务器远程升级一下程序。

一、工程配置

使用的是stm32f103vet6芯片,512的ROM,64kRAM,外部flash用的是8M芯片W25Q64,分成三部分,每部分1Mb,第一块(0x000000-0x100000)存放升级应用程序,第二块(0x100000-0x200000)存放备份程序(即当前应用程序),第三块用来存放标志位(新旧程序校验码、旧程序备份标志、新程序更新标志)。

1.BootLoader程序工程配置

STM32上添加bootloader+application,在外部flash中备份及升级程序_第1张图片STM32上添加bootloader+application,在外部flash中备份及升级程序_第2张图片

2.Application程序工程配置

STM32上添加bootloader+application,在外部flash中备份及升级程序_第3张图片
STM32上添加bootloader+application,在外部flash中备份及升级程序_第4张图片

二、大体流程,细节需完善

1.需要注意对flash写操作前,先擦除对应的扇区,外部flash一次查出一扇区4kb,stm32大容量一次擦除2kb,像stm32f103c8t6这种一次擦除1kb。
STM32上添加bootloader+application,在外部flash中备份及升级程序_第5张图片

三、代码

1.BootLoader

main.c

 /**
  ******************************************************************************
  * @file    main.c
  ******************************************************************************
  */ 
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"
#include "app_update.h"	

typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;

__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;

void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}


int main(void)
{ 	
	LED_GPIO_Config();
	
	/* 配置串口为:115200 8-N-1 */
	USART_Config();
	printf("\r\n 这是一个bootloader程序 \r\n");
	
	/* 8M串行flash W25Q64初始化 */
	SPI_FLASH_Init();
	
	/* 获取 Flash Device ID */
	DeviceID = SPI_FLASH_ReadDeviceID();	
	Delay( 200 );
	
	/* 获取 SPI Flash ID */
	FlashID = SPI_FLASH_ReadID();	
	printf("\r\n FlashID is 0x%X,\
	Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);
	
	/* 检验 SPI Flash ID */
	if (FlashID == sFLASH_ID)
	{	
		printf("\r\n 检测到串行flash W25Q64 !\r\n");
		
		// 读取更新标志
		SPI_FLASH_BufferRead(&Prog_Status_Flag.Backup_Flag,PROG_FLAG_ADDR,sizeof(struct _Prog_Status_Flag));
		// 判断是否需要备份程序,新的 FLASH 读出来都是 0xFF
		if(Prog_Status_Flag.Backup_Flag != 0xA5)
		{
				LED_GREEN;
				// 如果检测到是新的 Flash 或者是第一次开机,则备份当前的程序,
				// 备份后在进行校验,校验OK后将校验码写入 PROG_FLAG_ADDR 开始的地址中
				Backup_Current_Program();
		}
		// 判断是否需要更新程序
		if(Prog_Status_Flag.Update_Flag == 0x55)
		{
				LED_BLUE;
				// 显示屏显示提示,正在更新程序....
				Backup_Current_Program();
				Updata_New_Program();
		}
	}
	else
	{ 
		LED_RED;
		printf("\r\n 获取不到 W25Q64 ID!\n\r");
	}
	
	Load_App(STARTADDR);
	while(1);  
}
/*********************************************END OF FILE**********************/

app_update.c

/**
******************************************************************************
* @file    app_update.c
* @author  
******************************************************************************
*/ 
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"
#include "app_update.h"	

struct _Prog_Status_Flag Prog_Status_Flag;
uint32_t JumpAddress;       // 跳转地址
u8 ReadBuf[2048];           // 缓存,2048 , 每次更新 2048 2k

typedef  void (*pFunction)(void);  // 申明一个函数指针
pFunction Jump_To_Application;




// 备分当前程序
void Backup_Current_Program(void)
{
    unsigned int i,j;
    uint32_t ADD_CheckSum = 0;
		unsigned int XOR_CheckSum = 0;
    unsigned int Addr;

    JumpAddress = STARTADDR; // 当前程序的地址

    Prog_Status_Flag.Old_Prog_ADD_CheckSum = 0;
    Prog_Status_Flag.Old_Prog_XOR_CheckSum = 0;

    // 开始备份当前程序
    Addr = OLD_PROG_ADDR;       // 要备份到外部 Flash 的地址
		/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 这里擦除496k,即一个扇区496/4,擦除的最小单位是扇区*/
		for(int m=0;m<496/4;m++)
		{
			SPI_FLASH_SectorErase(OLD_PROG_ADDR+m*4096);
		}
		
    for(i=0;i<496/2;i++)  //      // 使用的是 STM32F103VET6,内部 Flash 是 512K,4K用来做 Bootload,所以只有 496K
    {
        for(j=0;j<2048;j++)     // 每次更新 2K
        {
            ReadBuf[j] = *(__IO uint8_t*)JumpAddress++;
            // 如果是 0x00,或者是 0xFF
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                // 计算校验和
                Prog_Status_Flag.Old_Prog_ADD_CheckSum += ReadBuf[j];
                // 计算异或校验
                Prog_Status_Flag.Old_Prog_XOR_CheckSum ^= ReadBuf[j];
            }
        }
        // 写入到外部 Flash 中
        SPI_FLASH_BufferWrite(ReadBuf,Addr,2048);
        Addr += 2048;
    }

    // 校验程序
    Addr = OLD_PROG_ADDR;
    for(i=0; i<496/2; i++)//
    {
        // 从 Flash 中读取程序
        SPI_FLASH_BufferRead(ReadBuf,Addr,2048);
        for(j=0; j<2048; j++)
        {
            // 计算校验码
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                ADD_CheckSum += ReadBuf[j];
                XOR_CheckSum ^= ReadBuf[j];
            }
        }
        Addr += 2048;
    }

    #ifdef PROG_VERIFY
    // 判断程序是否相同
    if((Prog_Status_Flag.Old_Prog_ADD_CheckSum == ADD_CheckSum)
    &&(Prog_Status_Flag.Old_Prog_XOR_CheckSum == XOR_CheckSum))
    {
        // 备份当前程序成功
        Prog_Status_Flag.Backup_Flag = 0xA5;
        Prog_Status_Flag.Update_Flag = 0x00;
        // 将校验码写入外部 Flash PROG_FLAG_ADDR 开始的地址中
				SPI_FLASH_SectorErase(PROG_FLAG_ADDR);//擦一个扇区4k,写之前一定要擦
        SPI_FLASH_BufferWrite(&Prog_Status_Flag.Backup_Flag,PROG_FLAG_ADDR,sizeof(struct _Prog_Status_Flag));
    }
    #endif
}


// 更新新程序
/** 注意如果你使用的是 STM32F103RC 内部有 256 Kbytes Flash 的 MCU, 则需要注意一点, FLASH_ErasePage 
每次擦除不是 1024 byte, 而是 2048 byte, 我实测是这个样子,所以这段程序要改成*

u8 ReadBuf[2048];           // 缓存,1024 , 每次更新 1024, 改成 2048

// 擦除并写入内部 Flash
        if(FLASH_ErasePage(WriteAddr) == FLASH_COMPLETE)    // 擦除并写入
        {
            p = (uint32_t*)ReadBuf;
            for(j=0; j<512; j++)              // 256 改成 512
            {
                FLASH_ProgramWord(WriteAddr,*p++);          // 每次写入 32 bit
                WriteAddr += 4;
            }

            Addr += 2048;                     // 1024 改成 2048
        }

*/
void Updata_New_Program(void)
{
    unsigned int i,j;
    uint32_t ADD_CheckSum = 0;
		unsigned int XOR_CheckSum = 0;
    unsigned int Addr;
    unsigned int WriteAddr;
    uint32_t *p; 

    FLASH_Unlock();                             // MCU 内部 Flash 擦写解除锁定
    Addr = NEW_PROG_ADDR;
    WriteAddr = STARTADDR;
    Prog_Status_Flag.New_Prog_ADD_CheckSum = 0;
    Prog_Status_Flag.New_Prog_XOR_CheckSum = 0;
    JumpAddress = STARTADDR;
    for(i=0; i<496/2; i++)         // 496 K,每次擦2k写2k
    {
        SPI_FLASH_BufferRead(ReadBuf,Addr,1024*2);              // 从外部 Flash 读取程序

        for(j=0; j<1024*2; j++)   // 2k 
        {
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                ADD_CheckSum += ReadBuf[j];
                XOR_CheckSum ^= ReadBuf[j];
            }
        }
        // 擦除并写入内部 Flash
        if(FLASH_ErasePage(WriteAddr) == FLASH_COMPLETE)    // 擦除并写入
        {
            p = (uint32_t*)ReadBuf;
            for(j=0; j<512; j++)
            {
                FLASH_ProgramWord(WriteAddr,*p++);          // 每次写入 32 bit
                WriteAddr += 4;
            }

            Addr += 2048;
        }
        else
        {
            // 显示屏显示提示 Flash Error
        }
    }
		
    // 校验写入的程序
    for(i=0;i<496/2;i++)       // 496k
    {
        for(j=0;j<2048;j++) // 2k
        {
            ReadBuf[j] = *(__IO uint8_t*)JumpAddress++;
            // 如果是 0x00,或者是 0xFF
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                // 计算校验和
                Prog_Status_Flag.New_Prog_ADD_CheckSum += ReadBuf[j];
                // 计算异或校验
                Prog_Status_Flag.New_Prog_XOR_CheckSum ^= ReadBuf[j];
            }
        }
    }

    #ifdef PROG_VERIFY
    // 判断是否相同
    if((Prog_Status_Flag.New_Prog_ADD_CheckSum == ADD_CheckSum)
    &&(Prog_Status_Flag.New_Prog_XOR_CheckSum == XOR_CheckSum))
    {
        // 程序更新成功
        Prog_Status_Flag.Update_Flag = 0x00;//更新标志清0
        // 将校验码写入外部 Flash PROG_FLAG_ADDR 开始的地址中
				SPI_FLASH_SectorErase(PROG_FLAG_ADDR);//擦一个扇区4k,写之前一定要擦
        SPI_FLASH_BufferWrite(&Prog_Status_Flag.Backup_Flag,PROG_FLAG_ADDR,sizeof(struct _Prog_Status_Flag));
    }
    #endif
}

//跳转到应用程序段,appxaddr:用户代码起始地址.
void Load_App(u32 appxaddr)
{
	// 跳转到用户程序
	if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000)
	{ 
		/* Jump to user application */
		JumpAddress = *(__IO uint32_t*) (appxaddr + 4);
		Jump_To_Application = (pFunction) JumpAddress;
		/* Initialize user application's Stack Pointer */
		
/*后加*/
		__set_BASEPRI((0xFFU >> 2));//关闭全局中断
		NVIC_SetVectorTable(NVIC_VectTab_FLASH,(appxaddr-0X08000000));//设置中断向量表偏移量
		__set_BASEPRI(0);//打开全局中断
/**/
		__set_MSP(*(__IO uint32_t*) appxaddr);

		Jump_To_Application();
	}
}	

app_update.h

#ifndef __APP_UPDATE_H
#define	__APP_UPDATE_H
#include "stm32f10x.h"
// 定义几个常量
// 外部 SPI Flash 地址,我使用的是 8M 外部 Flash,
// 所以空间很大,分了三块出来,每块 1M 的空间
#define NEW_PROG_ADDR       (0x000000)      // 新程序存放的地址
#define OLD_PROG_ADDR       (0x100000)      // 旧程序存放的地址
#define PROG_FLAG_ADDR      (0x200000)      // 程序标志位

#define  STARTADDR          0x08004000      // 用户程序地址


#define PROG_VERIFY				// 程序是否需要校验,取消 xPROG_VERIFY 的 x 就可以添加校验功能


// 程序更新标志位
struct _Prog_Status_Flag
{
    u8  Backup_Flag;                    // 程序是否已经备份
    u32 Old_Prog_ADD_CheckSum;          // 备份程序(旧程序)校验和
    u8  Old_Prog_XOR_CheckSum;          // 备份程序(旧程序)异或校验
    u32 New_Prog_ADD_CheckSum;          // 要更新的程序(新程序)校验和
    u8  New_Prog_XOR_CheckSum;          // 要更新的程序(新程序)异或校验
    u8  Update_Flag;                    // 更新标志位,有更新这个值为0x55,更新完成校验后清0
};
extern struct _Prog_Status_Flag Prog_Status_Flag;
extern void Backup_Current_Program(void);
extern void Updata_New_Program(void);
extern void Load_App(u32 appxaddr);

#endif 

2.Application

因为手里没有写关于串口接收这部分程序,这里先将应用程序复制到外部flash第一块(0x000000-0x100000)中做测试的升级程序,后期有项目再修改。

app程序中进行中断向量偏移:
STM32上添加bootloader+application,在外部flash中备份及升级程序_第6张图片

DownloadCodeToOutFlash.c

/**
******************************************************************************
* @file    DownloadCodeToOutFlash.c
* @author  
******************************************************************************
*/ 
#include "DownloadCodeToOutFlash.h"
#include "./flash/bsp_spi_flash.h"

struct _Prog_Status_Flag Prog_Status_Flag;
uint32_t JumpAddress;       // 跳转地址
u8 ReadBuf[2048];           // 缓存,2048 , 每次更新 2048 2k



// 将当前程序下载到外部flash的第一部分
void Download_Current_Program(void)
{
    unsigned int i,j;
    uint32_t ADD_CheckSum = 0;
		unsigned int XOR_CheckSum = 0;
    unsigned int Addr;

    JumpAddress = STARTADDR; // 当前程序的地址,正常情况应该取串口接收的程序数据头地址

    Prog_Status_Flag.New_Prog_ADD_CheckSum = 0;
    Prog_Status_Flag.New_Prog_XOR_CheckSum = 0;

    // 开始下载当前程序到外部flash,正常情况是将从串口来的数据程序下载到外部flash
    Addr = NEW_PROG_ADDR;       // 要下载到外部 Flash 的地址
		/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 这里擦除496k,即一个扇区496/4,擦除的最小单位是扇区*/
		for(int m=0;m<496/4;m++)
		{
			SPI_FLASH_SectorErase(NEW_PROG_ADDR+m*4096);
		}
		
    for(i=0;i<496/2;i++)  //      // 使用的是 STM32F103VET6,内部 Flash 是 512K,4K用来做 Bootload,所以只有 496K
    {
        for(j=0;j<2048;j++)     // 每次更新 2K
        {
            ReadBuf[j] = *(__IO uint8_t*)JumpAddress++;
            // 如果是 0x00,或者是 0xFF
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                // 计算校验和
                Prog_Status_Flag.New_Prog_ADD_CheckSum += ReadBuf[j];
                // 计算异或校验
                Prog_Status_Flag.New_Prog_XOR_CheckSum ^= ReadBuf[j];
            }
        }
        // 写入到外部 Flash 中
        SPI_FLASH_BufferWrite(ReadBuf,Addr,2048);
        Addr += 2048;
    }

    // 校验程序
    Addr = NEW_PROG_ADDR;
    for(i=0; i<496/2; i++)//
    {
        // 从 Flash 中读取程序
        SPI_FLASH_BufferRead(ReadBuf,Addr,2048);
        for(j=0; j<2048; j++)
        {
            // 计算校验码
            if((ReadBuf[j] != 0xFF)&&(ReadBuf[j] != 0x00))
            {
                ADD_CheckSum += ReadBuf[j];
                XOR_CheckSum ^= ReadBuf[j];
            }
        }
        Addr += 2048;
    }

    #ifdef PROG_VERIFY
    // 判断程序是否相同
    if((Prog_Status_Flag.New_Prog_ADD_CheckSum == ADD_CheckSum)
    &&(Prog_Status_Flag.New_Prog_XOR_CheckSum == XOR_CheckSum))
    {
        // 下载当前(更新的)程序到外部flash第一部分成功
        Prog_Status_Flag.Update_Flag = 0x55;
			
        // 将校验码写入外部 Flash PROG_FLAG_ADDR 开始的地址中
				SPI_FLASH_SectorErase(PROG_FLAG_ADDR);//擦一个扇区4k,写之前一定要擦
        SPI_FLASH_BufferWrite(&Prog_Status_Flag.Backup_Flag,PROG_FLAG_ADDR,sizeof(struct _Prog_Status_Flag));
    }
    #endif
		NVIC_SystemReset();     // 复位
}

DownloadCodeToOutFlash.h

#ifndef __DownloadCodeToFlash_H
#define	__DownloadCodeToFlash_H
#include "stm32f10x.h"

// 定义几个常量
// 外部 SPI Flash 地址,我使用的是 8M 外部 Flash,
// 所以空间很大,分了三块出来,每块 1M 的空间
#define NEW_PROG_ADDR       (0x000000)      // 新程序存放的地址
#define OLD_PROG_ADDR       (0x100000)      // 旧程序存放的地址
#define PROG_FLAG_ADDR      (0x200000)      // 程序标志位

#define  STARTADDR          0x08004000      // 用户程序地址

#define PROG_VERIFY				// 程序是否需要校验,取消 xPROG_VERIFY 的 x 就可以添加校验功能


// 程序更新标志位
struct _Prog_Status_Flag
{
    u8  Backup_Flag;                    // 程序是否已经备份
    u32 Old_Prog_ADD_CheckSum;          // 备份程序(旧程序)校验和
    u8  Old_Prog_XOR_CheckSum;          // 备份程序(旧程序)异或校验
    u32 New_Prog_ADD_CheckSum;          // 要更新的程序(新程序)校验和
    u8  New_Prog_XOR_CheckSum;          // 要更新的程序(新程序)异或校验
    u8  Update_Flag;                    // 更新标志位,有更新这个值为0x55,更新完成校验后清0
};
extern struct _Prog_Status_Flag Prog_Status_Flag;
extern void Download_Current_Program(void);
#endif 

你可能感兴趣的:(stm32)