STM32使用CAN总线实现IAP程序升级

什么是IAP升级?

IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级

UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级

STM32的代码启动过程

要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧

此部分参考:https://www.cnblogs.com/gulan-zmc/p/12248509.html

在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):

从0x0000 0000地址取出MSP(主堆栈寄存器)的值

从0x0000 0004地址取出PC(程序计数器)的值

然后取出第一条指令执行
在这里插入图片描述

启动文件源代码分析

;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
;* File Name          : startup_stm32f10x_hd.s
;* Author             : MCD Application Team
;* Version            : V3.5.0
;* Date               : 11-March-2011
;* Description        : STM32F10x High Density Devices vector table for MDK-ARM 
;*                      toolchain. 
;*                      This module performs:
;*                      (上电复位后会做下面的几件事情)
;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
;*                      - Set the initial PC == Reset_Handler(设置PC的值)
;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
;*                        SRAM mounted on STM3210E-EVAL board to be used as data 
;*                        memory (optional, to be enabled by user)
;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)
;*                        calls main()).
;*                      After Reset the CortexM3 processor is in Thread mode,
;*                      priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>   
;*******************************************************************************
 
 
 
; ------------------分配栈空间----------------
Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
                AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
__initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到
 
                                           
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
; ------------------分配堆空间----------------
;和分配栈空间一样不过大小只是512字节
Heap_Size       EQU     0x00000200
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
 
__heap_base        ;__heap_base堆的起始地址
Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
__heap_limit       ;__heap_base堆的结束地址
 
                PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐
                THUMB;THUMB作用是后面的指令使用Thumb指令集
 
; ------------------设置中断向量表----------------
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
                ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
                EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用          
                EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
                EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用
 
 
                
;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化
 
;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
__Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
                                                   ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) 
                                                   ;* __initial_sp的值是链接后,由链接器生成
 
                DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
                                                   ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
                                                   ;* Reset_Handler是一个函数,在下面定义
                ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
                DCD     NMI_Handler                ; NMI Handler      
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
 
 
                .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件
 
                DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
                DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
                DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End                                    ;__Vectors_End向量表的结束地址
 
__Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小
 
                AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读
 
;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
; Reset handler
Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
 
                EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
                                                          ;*编译时使用另一个地方的函数,否则使用这个函数
                                                
                                                          ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
                IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
                IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
                LDR     R0, =SystemInit
                BLX     R0              
                LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
                BX      R0
                ENDP
                
; Dummy Exception Handlers (infinite loops which can be modified)
;下面定义的都是异常服务函中断服务函数
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
.....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP
 
Default_Handler PROC
 
                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]
 
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
.....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .
 
                ENDP
 
                ALIGN    ;四字节对齐
 
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;下面函数是初始化堆栈的代码
                 IF      :DEF:__MICROLIB     
                 ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
                 ;这种方式初始化堆栈是由 __main 初始化的
                 EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
                 EXPORT  __heap_base    ;堆的起始地址
                 EXPORT  __heap_limit   ;堆的结束地址
                
                 ELSE
                 ;由用户初始化堆
                 ;否则编译下面的
                 IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap
                 
                 LDR     R0, =  Heap_Mem              ;堆的起始地址
                 LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
                 LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址
                 LDR     R3, = Stack_Mem              ;栈的结束地址
                 BX      LR
 
                 ALIGN
 
                 ENDIF
 
                 END
 
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****

STM32的启动步骤如下:

1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的

2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟

3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序

IAP设计思路

大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序

下面这个流程比较通用一些,大概是下图所示升级流程:

升级流程图

在这里插入图片描述

Flash分区

是以STM32L431KBU6为主控做的flash分区,主要功能:
在这里插入图片描述
bootloader区:0x0800 0000 到 0x0800 5000 地址的flash块划分给bootloader,用于升级固件,大小是20kb

APP区:0x0800 5000 到 0x0801 1800 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是50Kb

APP缓存区: 0x0801 1800 到 0x0801 E000 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 50kb

用户参数区+未定义:0x0801 E000 到 0x0802 0000 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是8Kb,flash块划分未定义区,可以根据具体用途定义

代码实现

内部Flsah读写及擦除操作

Flash擦除:

/*
	功能:擦除用户地址所在页
	参数:EraseAddr 需要擦除的Flash地址
		  NumberPage 擦除的页数
*/
HAL_StatusTypeDef MY_FLASH_Erase(uint32_t EraseAddr,uint8_t NumberPage)
{
	uint8_t page;
	uint32_t PAGEError=0;
	FLASH_EraseInitTypeDef FLASH_EraseInitStruct;

	/** 计算用户编程地址在FLASH中哪个页(每页2K字节) **/
	page = (EraseAddr - FLASH_BASE) / FLASH_PAGE_SIZE;//编程地址在Bank1上用此公式
//	page = (addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;//地址在Bank2
	
	FLASH_EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;	//擦除方式:页擦除
	FLASH_EraseInitStruct.Banks = FLASH_BANK_1;		//擦除页所在的区域:BANK1
	FLASH_EraseInitStruct.Page = page;		//擦除页的编号
	FLASH_EraseInitStruct.NbPages = NumberPage;	//擦除页的数量
	return HAL_FLASHEx_Erase(&FLASH_EraseInitStruct,&PAGEError);
}

写入Flash:

/*
	功能:写入Flash
	参数:WriteNumber 写入Flash的数据
		  addr 写的地址
*/
void Flash_WriteNumber(uint32_t addr,uint32_t WriteNumber)
{	
	uint8_t page;
	uint32_t PageError = 0;
	FLASH_EraseInitTypeDef FlashSet;
	HAL_StatusTypeDef status;
	
	/* 擦除Flash */
	/** 计算用户编程地址在FLASH中哪个页(每页2K字节) **/
	page = (addr - FLASH_BASE) / FLASH_PAGE_SIZE;//编程地址在Bank1上用此公式
//	page = (addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;//地址在Bank2
	
	FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;	//擦除方式:页擦除
	FlashSet.Banks = FLASH_BANK_1;		//擦除页所在的区域:BANK1
	FlashSet.Page = page;		//擦除页的编号
	FlashSet.NbPages = 1;	//擦除页的数量
	//解锁Flash操作
	HAL_FLASH_Unlock();
	status = HAL_FLASHEx_Erase(&FlashSet, &PageError);//擦除flash
	HAL_FLASH_Lock();
	if(status != HAL_OK)
	{
		printf("erase fail, PageError = %d\r\n", PageError);
	}
	else
		printf("erase success\r\n");
	
	/* 写入新的数据 */
	HAL_FLASH_Unlock();//解锁
	status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, (uint64_t)WriteNumber);//写入Flash
	if(status != HAL_OK)
	{
		printf("Flash write error,0x%08x\r\n",addr);
	}
	else
		printf("Flash write success\r\n");
	HAL_FLASH_Lock();//上锁
}

读取FLASH:

/*
	功能:读取Flash内容
	参数:addr 需要读取的Flash地址
		  size 读取字节大小
*/
uint32_t* Flash_ReadNumber(uint32_t addr,uint16_t size)
{
	static uint32_t ReadData[] = {0};
	
	/* 读取Flash内容 */
	for(uint32_t i=0;i<size;i++)
	{
		ReadData[i] = (*(uint32_t*)(addr+4*i));
		printf("Flash read data:%d\r\n",ReadData[i]);
	}
	
	return ReadData;
}

CAN中断接收升级固件包

//CAN1中断服务函数
void CAN1_RX0_IRQHandler(void) 
{
	HAL_CAN_IRQHandler(&hcan1);
}

/**
* \brief      函数功能:CAN中断回调函数
*             此函数会被CAN_Receive_IT()调用
* \param[in]  hcan:CAN句柄
* \return   
*            
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
	// 可以使用rxHeader和rxData来获取消息的ID和数据内容
	CAN_RxHeaderTypeDef RxHeader;
	uint8_t rxData[8];
    // 检查是否有新的CAN消息到达
    if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, rxData) == HAL_OK) 
	{
        // 在这里对接收到的CAN消息进行处理
        // 可以使用rxHeader和rxData来获取消息的ID和数据内容
		if(RxHeader.StdId == 0x666)
		{
			CAN_RxData = 0;
			CAN_RxData= ((uint64_t)rxData[7]<<56)|((uint64_t)rxData[6]<<48)|((uint64_t)rxData[5]<<40)|((uint64_t)rxData[4]<<32) \
			|((uint64_t)rxData[3]<<24)|((uint64_t)rxData[2]<<16)|((uint64_t)rxData[1]<<8)|((uint64_t)rxData[0]<<0);
			if(CAN_RxData == 0x8888888800000000)//固件包发送完毕
			{
				Rx_Complete_State = 0xF;//0-未接收完固件 F-固件全部接收完成 1-单次接收完成
			}
			else
			{
				Rx_Complete_State = 0x1;
			}		
		}
    }
}

IAP代码设计

bootloader代码设计:

#include "main.h"

uint8_t Rx_Complete_State = 0; //0-未接收完固件 F-固件全部接收完成 1-单次接收完成

volatile uint8_t recv_tcp_complete = 0;     //接收到平板发送的一帧tcp命令  1:接收到  处理完成后需要把此变量清零

#define Fifo_Buf_Size_MAX	2048
uint8_t Fifo_Buf[Fifo_Buf_Size_MAX] = {0};

/* 定义变量 ----------------------------------------------------------*/
#define APP_BASE_ADDR   		(uint32_t)0x08005000      	//定义应用程序的起始地址
#define APP_CODE_MAX			(uint32_t)50*1024			//定义APP程序最大长度  50K

#define UPDATE_CODESAVE_ADDR	(uint32_t)0x08011800		//定义升级程序待存放的起始地址


#define	PAPER_FLAG_UPSTART_ADDR		(uint32_t)0x0801F800		//升级标志存放的地址
#define PAPER_BIN_LEN_ADDR		(uint32_t)0x0801F808			//bin文件大小存放的地址

uint32_t Updatepara = 0;       //定义更新参数在flash中的位置

typedef	void (*piapfunc)(void);				//定义一个函数类型的参数.
void iap_load_app(uint32_t JumpAddress)
{
	piapfunc Jump_To_Application;
	__disable_irq();
	 
	/* Check if the top address of the stack is legal*/
	if (((*(__IO uint32_t*)APP_BASE_ADDR)&0x2FFE0000)==0x20000000)
	{
		DEBUG("goto APP Start...\r\n");
//		HAL_Delay(100);
	  
		JumpAddress = *(__IO uint32_t*)(APP_BASE_ADDR + 4);
		/* Jump to user application */
		Jump_To_Application = (piapfunc)JumpAddress;	
		/* Initialize user application's Stack Pointer */
		__set_MSP(*(__IO uint32_t*) APP_BASE_ADDR);
		__enable_irq();  
		Jump_To_Application();	
	}
	else
	{
		printf("\r\n No APP found!!!\r\n");
	}
}

int main(void)
{	
	uint32_t bin_total_len = 0;  		//定义固件总大小
//	uint32_t recv_total_bin_len = 0;	//需要接收的总固件的大小
	uint32_t recv_bin_len = 0;			//已经接收的固件大小
	
	/* USER CODE BEGIN SysInit */
	HAL_Init();
	SystemClock_Config();
	LL_mDelay(500);

	/* 初始化外围配置 */
	MX_GPIO_Init();
	MX_USART2_UART_Init();
	printf("\r\n Bootloader function!!\r\n");
	CAN1_Config();
	
	HAL_FLASH_Unlock();
	/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
	HAL_FLASH_Lock();
	uprintf("p_Updatepara1:0x%x\r\n\r\n",*((uint32_t*)PAPER_FLAG_UPSTART_ADDR));
	uprintf("PAPER_BIN_LEN:0x%x--%dk\r\n\r\n ",*((uint32_t*)(PAPER_FLAG_UPSTART_ADDR+8)),*((uint32_t*)(PAPER_FLAG_UPSTART_ADDR+8))/1024);
	
	Updatepara = *((uint32_t*)PAPER_FLAG_UPSTART_ADDR);
	uprintf("Updatepara3:0x%08x\r\n\r\n ",Updatepara);
	if(Updatepara == 0x66668888)//需要更新固件,接收固件保存至备份区
	{
		/* 保存bin文件长度 */
		bin_total_len = *((uint32_t *)(PAPER_BIN_LEN_ADDR));
		printf("\r\n bin_total_len:%d\r\n",bin_total_len);
		
		if(bin_total_len > APP_CODE_MAX)	//如果固件长度超过存储空间   发送error类  擦除升级标志  复位
		{
//			SendError(ERR_BIN_LARGE,40);   //固件太大  存不下
			printf("\r\n Updating firmware file too large \r\n");//更新文件过大
            goto pp1;
		}
		
		/* 更新flash中固件升级标志位状态改为正在升级 */
		HAL_FLASH_Unlock();
        /* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
        __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
		MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,PAPER_FLAG_UPSTART_ADDR,0x11112222);
        HAL_FLASH_Lock();
		
		/* 擦除备份区flash数据 */
		printf("\r\n Start erase falsh!\r\n");
		HAL_FLASH_Unlock();
        /* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
        __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
		MY_FLASH_Erase(UPDATE_CODESAVE_ADDR,APP_CODE_MAX/2048);
        HAL_FLASH_Lock();
		
		printf("\r\n Erase compelte!\r\n");
		printf("\r\n Waiting to receive updated firmware package \r\n");
		
		/* 发送Bootloader准备接收数据标志 */
//		CAN1_Send_Msg();//发送Bootloader准备完毕,可以发送固件包
		
		
		/* 等待接受更新固件包并写入备份区 */
		while(1)
		{
			if(Rx_Complete_State == 1)//接收到一次固件包
			{
				Rx_Complete_State = 0;
				
				HAL_FLASH_Unlock();
				/* 接收到一次写入备份区 */ 
				uint64_t data_to_write = 0;

				printf("\r\n receive CAN_RxData: 0x%16llx\r\n\r\n",CAN_RxData);
//				data_to_write = ((CAN_RxData & 0x00000000ffffffff) << 32) | (CAN_RxData >> 32);
				data_to_write = CAN_RxData;
				printf("data_to_write: 0x%16llx\r\n\r\n",data_to_write);
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,UPDATE_CODESAVE_ADDR+recv_bin_len,data_to_write);
				HAL_FLASH_Lock();	
				printf("\r\n Program addr: 0x%08x\r\n",UPDATE_CODESAVE_ADDR+recv_bin_len);
				
				/* 校验接收数据错误或flash写入错误 */
				uint64_t CheckData = ((uint64_t)(*(uint32_t*)(UPDATE_CODESAVE_ADDR+recv_bin_len+4)) << 32) | (*(uint32_t*)(UPDATE_CODESAVE_ADDR+recv_bin_len)) ; 
				if(CheckData != CAN_RxData)
				{
					printf("\r\n Check data fail,addr:0x%08x\r\n",UPDATE_CODESAVE_ADDR+recv_bin_len);
					printf("CheckData:0x%llx \r\n data_to_write: 0x%llx\r\n",CheckData,CAN_RxData);
					//发送error类  数据出错  需要重发
//					SendError(ERR_CALC_FAIL,CLASS_UPDATE_DATA_INDEX);
				}
				recv_bin_len = recv_bin_len+8;//一次接受8个字节(64位)
				printf("\r\n recv cuccess,recv_total_bin_len:%d\r\n",recv_bin_len);
						
				uint8_t Return_Buf[] = {0xFF,0x4A,0x5A,0xA5,0xFF,0x4A,0x5A,0xA5};
				CAN1_Send_Msg(0x68,Return_Buf,8);//告诉上位机单次接收完成,准备接受下次数据
			}
			else if(Rx_Complete_State == 0xF)//固件包全部接收完成 /* 拷贝备份区数据至app */
			{
//				Rx_Complete_State = 0;
				HAL_FLASH_Unlock();
				/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
				__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
				MY_FLASH_Erase(APP_BASE_ADDR,APP_CODE_MAX/2048);
				
				/* 拷贝备份区固件至app */
				uint64_t data_to_write = 0;
				printf("\r\n Copy the firmware of the backup area to the app\r\n");
				for(uint32_t i=0;i<recv_bin_len;i+=8)
				{
					data_to_write = ((uint64_t)*((uint32_t *)(UPDATE_CODESAVE_ADDR+i+4)) << 32) | ((uint64_t)*((uint32_t *)(UPDATE_CODESAVE_ADDR+i)));
					HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,APP_BASE_ADDR+i,data_to_write);
				}
				for(uint32_t i=0;i<recv_bin_len;i+=4)
				{
					printf("\r\n addr:0x%08x  data:0x%08x \r\n",APP_BASE_ADDR+i,*((uint32_t *)(APP_BASE_ADDR+i)));
				}
				
				/* Clear All pending flags */
				__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
				MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,PAPER_FLAG_UPSTART_ADDR,0x88886666);//跟新完之后这里改变标志位
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,(PAPER_FLAG_UPSTART_ADDR+8),0x8000);
				HAL_FLASH_Lock();
				printf("\r\n Successfully copied to the app \r\n");
				
				printf("FLAG_UPSTART:%x\r\n",*((uint32_t*)PAPER_FLAG_UPSTART_ADDR));
				HAL_Delay(100);
				printf("\r\n iap_load_app! \r\n");
				iap_load_app(APP_BASE_ADDR);
				//复位  运行bootloaderr
//				printf("\r\n system reset!\r\n");
//				NVIC_SystemReset();	
			}
		}	
	}
	else if(Updatepara == 0x88886666)//不需要更新固件 直接运行app
	{
		printf("\r\n iap_load_app! \r\n");
		iap_load_app(APP_BASE_ADDR);
	}
	else
	{
		iap_load_app(APP_BASE_ADDR);
	}
	
pp1:	HAL_FLASH_Unlock();
        /* Clear All pending flags */
        __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
        MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
        HAL_FLASH_Lock();

        HAL_Delay(100);
        //复位  运行bootloaderr
//        printf("\r\n system reset!\r\n");
//        NVIC_SystemReset();
		iap_load_app(APP_BASE_ADDR);
}

bin文件生成

在这里插入图片描述

$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L

APP代码设计
APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码

SCB->VTOR = FLASH_BASE | 0x5000;//设置中断偏移

在这里插入图片描述

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值