STM32G473 固件升级IAP(BootLoader)CAN/USART。(详细步骤)_iap固件升级 can 软件-程序员宅基地

技术标签: stm32  c语言  嵌入式硬件  STM32_报警、错误  单片机  

本例程仅供参考(个人学习总结_有需要文中有的封装好的跳转函数可私信),

例程可举一反三完成FDCAN通信和USART通信。

链接:https://pan.baidu.com/s/1avuZRA8Lbm8K-cBPkvpshw?pwd=idnj 
提取码:idnj 
复制这段内容后打开百度网盘手机App,操作更方便哦

目录

简介

1.APP程序配置步骤

APP 程序起始地址设置方法

中断向量表的偏移量设置方法

KEIL5生成bin文件步骤

2.IAP(BootLoader 程序)配置(HAL库,Cubemax)

2.1RCC配置

2.2时钟树配置

2.3CAN配置(版本例程CAN接收数据和发送数据为普通模式,配合TIM2定时器使用)

2.4TIM2定时器配置

2.5USART配置

3.IAP(BootLoader)代码程序配置

        3.1CAN过滤器,发送,接收函数配置

3.2CAN发送配置

3.3CAN.h函数声明

3.4CAN测试函数

3.5CAN测试效果

3.6USART接收

3.7Printf重映射函

3.8USART主函数测试

3.9USART测试效果

4.Flash写入数据

5.跳转至APP函数(即IAP)

6. Bootloader 程序

7.Main程序代码

8.升级效果

8.1CAN升级效果

8.2USART升级效果

9.注意事项


简介

IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产 品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,

第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;

第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它做如下操作:

1)检查是否需要对第二部分代码进行更新

2)如果不需要更新则转到 4)

3)执行更新操作

4)跳转到第二部分代码执行

第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP 代码更新。

我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32G4  FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的),这样我们就是要实现 2 个程序:Bootloader 程序和 APP程序。

STM32G4 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,我们选用在 FLASH 运行。Flash存储器是一种非易失性存储器,可以在掉电之后保存数据,通常用于存储程序代码。Flash存储器的可写入次数有限,且需要执行擦除操作才能写入新的数据,因此,在使用过程中需要注意擦写周期和数据备份问题。SRAM存储器则是一种易失性存储器,具有相对较快的读写速度和无限的读写次数,但掉电时将会丢失所有内容。SRAM存储器主要用于暂存数据和临时变量,读写操作由CPU直接完成,访问速度较快。

单片机的Flash存储器和SRAM存储器都嵌入在单片机芯片内部,能够方便的实现对程序和数据、变量的读写操作。通常,编译器会把程序烧录在Flash存储器,并使用SRAM存储器来存储变量、函数堆栈以及其他的临时数据

1.APP程序配置步骤

  1. APP 程序起始地址设置方法                                                                                               4a47349c58b84646a5a1f820d20e2cfd.png

    图中 IROM1 的起始地址(Start)为 0x08000000,大小(Size)为 0x80000,即从 0x08000000 开始的 512K 空间为我们的程序存储区。

    APP程序将设置起始地址(Start)为 0x08010000,即偏移量为 0x10000(64K 字节,即留给 BootLoader 的空间),因而,留给 APP 用的 FLASH 空间(Size)只有 0x80000- 0x10000=0x70000(448K 字节)大小了。设置好 Start 和 Size,就完成 APP 程序的起始地址设置。IRAM 是内存的地址,APP 可以独占这些内存,不需要修改。

    注意:需要确保 APP 起始地址在 Bootloader 程序结束位置之后,并且偏移量为 0x200 的倍数即可

  2. 中断向量表的偏移量设置方法

    VTOR 寄存器存放的是中断向量表的起始地址。默认的情况它由 BOOT 的启动模式决定,对于 STM32G4 来说就是指向 0x0800 0000 这个位置,也就是从默认的启动位置加载中断向量等信息,不过 ST 允许重定向这个位置,这样就可以从 Flash 区域的任意位置启动我们的代码了。我们可以通过调用 sys.c 里面的 sys_nvic_set_vector_table 函数实现,该函数定义如下:
    /** 
    * @brief 设置中断向量表偏移地址 
    * @param baseaddr : 基址 
    * @param offset : 偏移量 
    * @retval 无 
    */ 
    void sys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset) 
    { 
    /* 设置 NVIC 的向量表偏移寄存器,VTOR 低 9 位保留,即[8:0]保留 */ 
    SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00); 
    }

    该函数用于设置中断向量偏移,baseaddr 为基地址(即 APP 程序首地址),Offset 为偏移量,需要根据自己的实际情况进行设置。比如 FLASH APP 设置中断向量表偏移量为0x10000,调用情况如下:

    /* 设置中断向量表偏移量为 0x10000 */
    sys_nvic_set_vector_table(FLASH_BASE, 0x10000);

    通过以上两个步骤的设置,我们就可以生成 APP 程序了,只要 APP 程序的 FLASH大小不超过我们的设置即可   MDK 默认生成的文件是.hex 文件,并不方便我们用作 IAP更新,工程直接生成.bin 文件,可以方便进行 IAP 升级。291a42e291634989a7f5ef8c56409637.png

  3. KEIL5生成bin文件步骤

通过 MDK 自带的格式转换工具 fromelf.exe 来生成 bin 文件。注意:如果用户安装 MDK 在 C 盘的默认路径,那它的位置一般C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe。

其他位置可点击鼠标右键 KEIL 软件 打开文件所在目录 ,然后跳转到上一级文件目录的ARM ->>ARMCC ->>获得绝对路径。

点击Options for Target→User,在 After Build/Rebuild 一栏中,勾选 Run #1,输入转换语句

D:\A_A_ProgramFiles\A_Keil5\ARM\ARMCC\bin\fromelf --bin -o ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\Output\@L.bin ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\MDK-ARM\Can_IAP_003\%L

推荐相对地址,(不同的工程文件地址可能不同,需要根据自己的工程来定,文件路径较深推荐用绝对路径。)78821d302a5946b983a9316f473890df.png

转换语句的组成解释如下:

首先是MDK 自带的格式转换工具 fromelf.exe的绝对路径 + 命令fromelf --bin -o 路径(生成bin文件保存路径)\@L.bin  路径(生.axf的文件路径)%L。

D:\A_A_ProgramFiles\A_Keil5\ARM\ARMCC\bin\fromelf --bin -o ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\Output\@L.bin ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\MDK-ARM\Can_IAP_003\%L

总结APP 程序的生成步骤

  1. 设置 APP 程序的起始地址和存储空间大小
  2. 设置中断向量表偏移量
  3. 设置编译后运行 fromelf.exe,生成.bin 文件

以上 3 个步骤,就可以得到一个.bin 的 APP 程序,通过 Bootlader 程序即可实现更新

APP程序中加入374458b52fe64542820b694461914098.png文件夹中的266fb646f1b64a23b50264f1ed8e8e8b.pnga7b838eeb26346cebf90f877f7af20a5.png.c和.h文件在工程中添加包含路径,并在Main函数最开始部分添加语句39fb3ba23de44884806a702f42d285ce.png

2.IAP(BootLoader 程序)配置(HAL库,Cubemax)

2.1RCC配置

578bc811513f4bfcadd4c0f7464c2b89.png

2.2时钟树配置

7a3a7035fbcc414192330494fb6de74c.png

2.3CAN配置(版本例程CAN接收数据和发送数据为普通模式,配合TIM2定时器使用

12755a5d0d0542938aa85a41c698f523.png

2.4TIM2定时器配置

dcb1c024ee2c4f3db8ddc1f61d168c57.png

2.5USART配置

2e11651bada3411499b78d4df74de6fe.png

生成工程

d2755821066941879b11d9f08af33ead.png

e6b41def59ca4beab62299b69c5dfcf0.png

3.IAP(BootLoader)代码程序配置

        3.1CAN过滤器,发送,接收函数配置

/* USER CODE BEGIN 0 */
FDCAN_FilterTypeDef   sFilterConfig;	//接收过滤器类型定义结构体
/* USER CODE END 0 */

 /* USER CODE BEGIN FDCAN1_Init 2 *///配置接收过滤	
sFilterConfig.IdType = FDCAN_STANDARD_ID;       
//  配置为过滤标准帧
  sFilterConfig.FilterIndex = 0;                 
  // 过滤器的索引号  如果有2组标准帧过滤,那么就是通过index区别,0  1   2这种
  sFilterConfig.FilterType = FDCAN_FILTER_RANGE;  
//  过滤方式为范围,即从FilterID1~FilterID2之间的值
  sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
  sFilterConfig.FilterID1 = 0x0000;
  sFilterConfig.FilterID2 = 0x07ff;         		  
//标准帧为11位ID,即0x7ff,本例配置为接收所有帧
/*设置接收过滤器,根据FDCAN_FilterTypeDef结构中指定的参数配置FDCAN接收筛选器。*/
if(HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)	
{
    Error_Handler();
}
  /*配置FDCAN全局过滤器*/
if(HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,FDCAN_REJECT,FDCAN_REJECT,DCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
  {
    Error_Handler();
  }
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
  {
    Error_Handler();
  }
/* USER CODE END FDCAN1_Init 2 */

3.2CAN发送配置

/*发送函数*/
void CAN_Send_Msg(FDCAN_HandleTypeDef hcan,uint32_t can_id,uint8_t tx_buff[])
{	
	FDCAN_TxHeaderTypeDef TxHeader;	
	TxHeader.Identifier = can_id;                         /* 32位ID */         
TxHeader.IdType = FDCAN_STANDARD_ID;					  /* 标准ID */
  TxHeader.TxFrameType = FDCAN_DATA_FRAME;		     	  /* 数据帧 */
  TxHeader.DataLength = FDCAN_DLC_BYTES_8;				 	/* 数据长度 */
  TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
  TxHeader.BitRateSwitch = FDCAN_BRS_ON;			   //用于设置发送是否波特率可变。
  TxHeader.FDFormat = FDCAN_CLASSIC_CAN;				//普通can还是FDcan
  TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;		
//是用于设置发送事件FIFO控制事件是否保存
  TxHeader.MessageMarker = 0;											
//marker++;	//用于设置复制到TX EVENT FIFO的消息Maker,来识别消息状态,范围0到0xFF
  HAL_FDCAN_AddMessageToTxFifoQ(&hcan, &TxHeader, tx_buff); //发送
}	
void CAN_Receive_Msg(void)
{  /*函数的作用是获取CAN接收FIFO的填充级别。*/
  if(HAL_FDCAN_GetRxFifoFillLevel(&hfdcan1,FDCAN_RX_FIFO0) >= 1)
{
			/*从Rx FIFO 收取一个 CAN 帧*/
if(HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &CANRec_msg, CANRec_buff) == HAL_OK )	{
		  CANRecFlag = 1;//接收完成标志位
		}				
}					
}
在数函数中全局变量
uint8_t CANRecFlag ;     //CAN接收表示,完成 1 
uint8_t CANRec_buff[8];  //CAN接受到的数据位
FDCAN_RxHeaderTypeDef CANRec_msg;   
在Main.h中extern
extern uint8_t CANRecFlag ;
extern uint8_t CANRec_buff[8];
extern FDCAN_RxHeaderTypeDef CANRec_msg;   

3.3CAN.h函数声明

void CAN_Send_Msg(FDCAN_HandleTypeDef hcan,uint32_t can_id,uint8_t tx_buff[]);
void CAN_Receive_Msg(void);
void Power_ON_Start_TX(void);

3.4CAN测试函数

void Power_ON_Start_TX(void)
{
  FDCAN_TxHeaderTypeDef TxHeader;
  uint8_t tx_data[8] = {0x20, 0x23, 0x11, 0x09, 0x12, 0x20, 0x00, 0x00};
  uint32_t can_id = 0x601;  
TxHeader.Identifier = can_id;
TxHeader.IdType = FDCAN_STANDARD_ID;
  TxHeader.TxFrameType = FDCAN_DATA_FRAME;
  TxHeader.DataLength = FDCAN_DLC_BYTES_8;
  TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
  TxHeader.BitRateSwitch = FDCAN_BRS_ON; 				//用于设置发送是否波特率可变。
  TxHeader.FDFormat = FDCAN_CLASSIC_CAN;				//普通can还是FDcan
  TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;		
//是用于设置发送事件FIFO控制事件是否保存
  TxHeader.MessageMarker = 0;											
//marker++;	//用于设置复制到TX EVENT FIFO的消息Maker,来识别消息状态,范围0到0xFF
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, tx_data);//向 Tx 邮箱中增加一个消息,并且激活对应的传输请求
  }

在#include "stm32g4xx_it.c文件中添加CAN头文件和TIM2中断函数中添加接收函数

#include "fdcan.h"
int TickMs = 0;
void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */
  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */
CAN_Receive_Msg();//FDCAN接收函数
  TickMs++;//定时器计数
  /* USER CODE END TIM2_IRQn 1 */
}

主函数Main.c中while中添加如下代码

//while前
HAL_TIM_Base_Start_IT(&htim2);	          //定时器 中断
Power_ON_Start_TX();						//上电后发送一串数据	
//While中
if (CANRecFlag)
{
CANRecFlag = 0;
CAN_Send_Msg(hfdcan1,CANRec_msg.Identifier,CANRec_buff);
//接收到数据网CAN上位机反馈一下
}

3.5CAN测试效果

上电程序运行,会发送一组数据 20 23 11 09 12 20 00 00,然后上位机发送数据后会将发送的数据进行反馈给上位机,表示数据接收成功。

ae4fb1bfafcf45e987f25397a0b705a1.png

3.6USART接收

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)             /* 如果是串口1 */
    {
        if ( UART_RX_Cont  < USART1_MAX_REC_LENGTH)
        {
            UART1_Rx_Buff[UART_RX_Cont] = UART1_Rx_Temp[0];
            UART_RX_Cont++;
        }
        /* 接收完成后在开启写一次中断接收 */
        HAL_UART_Receive_IT(&huart1, (uint8_t *)UART1_Rx_Temp, RXBUFF_ERSIZE);
    }
}



串口相关定义
/* USER CODE BEGIN 0 */
uint16_t UART1_Rx_Sta=0;               					/* 接收状态 */
uint8_t UART1_Rx_Buff[USART1_MAX_REC_LENGTH];           /* HAL库使用的串口接收缓冲 */
uint8_t UART1_Rx_Temp[RXBUFF_ERSIZE];         			/* 接收字节 */      
uint32_t UART_RX_Cont = 0;
UART_HandleTypeDef huart1;
/* USER CODE END 0 */



usart.h声明
#include "stdio.h"
int fputc(int ch, FILE *f);
#define RXBUFF_ERSIZE    1 /* 缓存大小 */
#define USART1_MAX_REC_LENGTH   64*1024          /* 定义最大接收字节数 200 */
extern uint8_t  UART1_Rx_Buff[USART1_MAX_REC_LENGTH];  	
/* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t UART1_Rx_Sta;   							/* 接收状态标记 */  
extern uint8_t UART1_Rx_Temp[RXBUFF_ERSIZE];    			/* HAL库USART接收Buffer */
extern uint32_t UART_RX_Cont ;            /* 接收到的app代码长度 */



Main.c
uint32_t lastcount = 0;                     /* 上一次串口接收数据值 */
uint32_t applenth = 0;                      /* 接收到的app代码长度 */
printf("STM32_CAN_UASRT_IAP_End\n");	  //程序串口运行发送STM32_CAN_UASRT_IAP_End



 在usart.c中MX_USART1_UART_Init函数中,打开中断接收
 /* USER CODE BEGIN USART1_Init 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)UART1_Rx_Temp, RXBUFF_ERSIZE);//开启中断
  /* USER CODE END USART1_Init 2 */

3.7Printf重映射函

int fputc(int ch, FILE *f)
{
 uint8_t temp[1] = {ch};
 HAL_UART_Transmit(&huart1, temp, 1, 0xffff);
return ch;
}

3.8USART主函数测试

/*----------串口接收1--------------------------------------*/		
 if (UART_RX_Cont)
    {
       if (lastcount == UART_RX_Cont)   /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
         {
            applenth = UART_RX_Cont;
            lastcount = 0;
            UART_RX_Cont = 0;
          for(int i = 0; i<applenth;i++)
			{
printf("UART1_Rx_Buff[%d] == %x \r\n",i,UART1_Rx_Buff[i]);
			}
                printf("用户程序接收完成!\r\n");
                printf("代码长度:%dBytes\r\n", applenth);
            }
            else lastcount = UART_RX_Cont;
        }

3.9USART测试效果

上电发送STM32_CAN_UASRT_IAP_End,通过上位机发送数据,将数据每一位转化成ascll码后16进制进行打印,发送的bin文件的所有数据都存放在UART1_Rx_Buff的数组中7fba19bd72f94089990633ff04c36691.png

4.Flash写入数据

FLASH写入数据是引用封装后的函数,在文件夹1529d6bcd3534112a378cffc95faf945.png284c9c0553754c35ae3208f8d102a9fd.png文件中的5a8155d2eca44d8184c19be9d6970c9b.png文件可以直接添加到工程中,并添加头文件的路径。使用时直接引用相关函数。

5.跳转至APP函数(即IAP)

跳转函数同样也是引用的封装好的函数,在跳转函数中做了一些改进,可以直接添加工程中直接进行使用e3d327cbd2694a5582ef6b7d2be0b368.png9b39ceb64e5e444ca7d9f8e6d0da2510.pngd2ca4de8e4524a969899227e463cb9ae.png

注意:由于是因引用的封装后的库,建议将c81c7bac47204c9fa638fcc6df0b6f16.png

中的所有源文件(.c)和头文件(.h)同时添加到工程文件中。

到此部分Bootloader 程序的CAN通讯和usart通讯已经完成(接收升级固件包数据)

下面程序的升级逻辑介绍

6. Bootloader 程序

升级程序流程图如下

2de2e750b6c648e0a03de5039ad5fbda.png

7.Main程序代码

函数声明部分
/* USER CODE BEGIN Includes */
#include "sys.h"
#include "delay.h"
#include "iap.h"
uint8_t CANRecFlag ;     //CAN接收表示,完成 1 
uint8_t CANRec_buff[8];  //CAN接受到的数据位
FDCAN_RxHeaderTypeDef CANRec_msg;   

#define APP_LEN_MAX 60*1024
uint8_t APP_RX_BUF[APP_LEN_MAX] = {0};  //接收缓冲,最大64kb.
uint32_t APPLength = 0;									//升级程序数据长度
extern int TickMs;
/* USER CODE END Includes */


Bootloader程序升级或跳转选择while 
 /* USER CODE BEGIN 2 */
	 //delay_init(170);                //若有报错打开注释
	 uint32_t lastcount = 0;                      /* 上一次串口接收数据值 */
      uint32_t applenth = 0;                      /* 接收到的app代码长度 */
	 HAL_TIM_Base_Start_IT(&htim2);	           //定时器 中断
	 Power_ON_Start_TX();				      //上电后发送一串数据	
	 printf("STM32_IAP_02\n");	 
	while(1)
    {    
	  if(TickMs>5000)//5秒后跳转
	  {
		if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)
 /* 判断FLASH里面是否有APP,有的话执行 */
		{
		 printf("开始执行FLASH用户代码!!\r\n\r\n");
		 printf("RUN addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));
		 iap_load_app(FLASH_APP1_ADDR);       /* 执行FLASH APP代码 */					}else{
			printf("非APP———FLASH应用程序,无法执行!\r\n");
			printf("进入主程序等待升级!!\r\n");
			break;
			 }
	 }
	if(CANRecFlag)//升级命令CAN口有接收任意消息
	{
		CANRecFlag = 0;
		printf("进入主程序等待升级!!\r\n");
	break;
	  }
    }
  /* USER CODE END 2 */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
/*-----------串口接收数据处理--------------------------------------*/		
if (UART_RX_Cont)
     {
        if (lastcount == UART_RX_Cont)   /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
        {
                applenth = UART_RX_Cont;
                lastcount = 0;
                UART_RX_Cont = 0;
//		for(int i = 0; i<applenth;i++)
//		{
//		  printf("UART1_Rx_Buff[%d] == %x \r\n",i,UART1_Rx_Buff[i]);
//	     }
          printf("用户程序接收完成!\r\n");
          printf("代码长度:%dBytes\r\n", applenth);
        }
            else lastcount = UART_RX_Cont;
     }
/*----------CAN升级命令------------------------------*/
if (CANRecFlag)
	{
	  CANRecFlag = 0;
//	CAN_Send_Msg(hfdcan1,CANRec_msg.Identifier,CANRec_buff);
//接收到数据网CAN上位机反馈一下
	  if (CANRec_msg.Identifier ==0x123) //升级指令包
	{
		  if (applenth < APP_LEN_MAX)
			{
		     	for(int i=0 ;i < 8;i++)
				{
			     	APP_RX_BUF[applenth] = CANRec_buff[i];
					applenth++;
				}
			 }else{	
				printf("缓冲区容量不足...\r\n");
				}
			}
	 if (CANRec_msg.Identifier ==0x111) 
		{
	     	printf("APP_RX_BUF[%d] \n",applenth);//APPLength
//		  for(int i = 0; i<applenth;i++)
//			{
//		     	printf("APP_RX_BUF[%d] = %x\n",i,APP_RX_BUF[i]);
//			}
	 if(applenth)
		{
/*-------------串口升级----------------------------------------------------------------------*/			  if (((*(volatile uint32_t *)(UART1_Rx_Buff + 4)) & 0xFF000000) == 0x08000000)  
/* 判断是否为0X08XXXXXX */
		{
		  printf(" \n串口升级开始更新固件...\r\n");
		printf("updata addr :%x \r\n",(*(volatile uint32_t *)(UART1_Rx_Buff + 4)) & 0xFF000000);		iap_write_appbin(FLASH_APP1_ADDR, UART1_Rx_Buff, applenth);/* 更新FLASH代码 */		printf("UPdata addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));         			printf("\n!!!串口升级固件更新完成!!!\r\n");						
		 }
/*---------------CAN升级-------------------------------------------------------------------*/		
		else if (((*(volatile uint32_t *)(APP_RX_BUF + 4)) & 0xFF000000) == 0x08000000)  
/* 判断是否为0X08XXXXXX */
		{					
		printf(" \nCAN升级开始更新固件...\r\n");										
		printf("updata addr :%x \r\n",(*(volatile uint32_t *)(APP_RX_BUF + 4)) & 0xFF000000);		iap_write_appbin(FLASH_APP1_ADDR, APP_RX_BUF, applenth);	/* 更新FLASH代码 */		printf("UPdata addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));         			printf("\n!!!固件更新完成!!!\r\n");
		}else{		
			printf("\n!!!非FLASH应用程序!!!\r\n");
	     }
		}else{
            printf("没有可以更新的固件!\r\n");
            }
		}
	if (CANRec_msg.Identifier == 0x222)   /* KEY1按键按下, 运行FLASH APP代码 */
	{
	  printf("flash Run addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000);
	   if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000) /* 判断FLASH里面是否有APP,有的话执行 */
		{
		  printf("开始执行FLASH用户代码!!\r\n\r\n");
			printf("RUN addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));
			iap_load_app(FLASH_APP1_ADDR);/* 执行FLASH APP代码 */						}else	{
			printf("没有可以运行的固件!\r\n");
			}
		}
	}
/*-------------------Bootloader LED反馈--------------------------------------------------*/
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_1|GPIO_PIN_0);
		HAL_Delay(500);
  }
  /* USER CODE END 3 */

8.升级效果

8.1CAN升级效果

884c3eb944ad4d69aa0967169d5c98c9.png

429e71910501434d8b5bf7f5979d4f5a.png

8.2USART升级效果

通过Ecantools发送更新,升级指令实现升级跳转程序

373ea3a1859d4e9d94a02d55ec787d4b.png

9.注意事项

  1. CAN接受数据时不可有延时出现,否则会影响数据接收
  2. CAN通过上位机发送bin文件数据或其他方式发送文件数据时,需要注意发送每一帧数据的时间间隔的控制,否则会出现数据丢帧,不能接收完整的数据包。
  3. 配置APP程序和Bootloader程序时,需要注意起始地址和空间大小,具体看程序的大小从而进行配置。本程序的起始控件大小如下eed5db76dce54f8bacd13498294d96a3.png6b669a6d5ff7492cb10fe85b054fac2d.png
  4. 程序中有Printf重映射相关的函数,需要将上图中的           use MicroLIB27cca3eeb3be4ab5b3df5f3eae72c881.png勾选上

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_61385844/article/details/134316725

智能推荐

前端系列——vue2+高德地图web端开发(poi搜索两种方式)_前端开发的时候地图搜索一般用什么-程序员宅基地

文章浏览阅读5.8k次,点赞12次,收藏34次。前端系列——vue2+高德地图web端开发(poi搜索)前言基础什么是poi搜索1. 输入提示结合poi搜索官方代码步骤1.进行plugins插件注册2.data中编写placeSearch变量3.在methods中编写select函数4.在initMap函数中增加poi搜索处理逻辑解释2.直接进行poi搜索步骤1.在Search.vue中我们把接收到的值传到MapContainer.vue中2.在MapContainer.vue中接收3.编写watch进行监听完整代码(MapContainer.vue)结_前端开发的时候地图搜索一般用什么

微信退款操作_微信退款签名生成-程序员宅基地

文章浏览阅读600次。请求XML示例<xml> <appid>wx2421b1c4370ec43b</appid> <mch_id>10000100</mch_id> <nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str> <out_refund_no>1415701182</out_refund_no> <out_trad_微信退款签名生成

JS(javascript) delete 详解_js delete-程序员宅基地

文章浏览阅读7.6k次。js delete javascript_js delete

链路追踪 SkyWalking 源码分析 —— Collector Storage 存储组件-程序员宅基地

文章浏览阅读857次。点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析..._writerequest.refreshpolicy.immediate save

Error creating bean with name ‘dataSource‘ defined in class path resource [applicationContext.xml]_error creating bean with name 'datasource' defined-程序员宅基地

文章浏览阅读5.1k次。分析:异常信息提示错误出在C3P0连接池,检查applicationContext.xml后发现并没有什么错误,于是我们可以锁定问题出在 jar 库依赖,问题的出现可能就是 jar 库冲突,选择性地删掉一个就OK了。严重: 在路径为[/SSH02-SpringWebPojo]的上下文中,servlet[servlets.RegisterServlet]的Servlet.service()引发异常org.springframework.beans.factory.BeanCreationException_error creating bean with name 'datasource' defined in class path resource [c

kafka参数auto.offset.reset与消失丢失_kafka auto.offset.reset 找不到配置-程序员宅基地

文章浏览阅读218次。kafka一种极端的的消息丢失。_kafka auto.offset.reset 找不到配置

随便推点

从excel表格中批量给图片重命名_用excel批量重命名图片-程序员宅基地

文章浏览阅读1.7w次,点赞10次,收藏39次。1.如果图片有扩展名,可以直接跳过直接看第2步,没有就继续往下看在文件扩展名前面的方框打上对号,方便判断要操作的图片是png还是jpg2.这时图片显示出了扩展名例如:001.png就是有扩展名 001就是没有扩展名3.打开excel表格插入公式="ren “&A1&”.png “&B1&”.png"- 重点解说一下="ren “&A1&”.png “&B1&”.png"这个公式A1就是重命名之前的图片名称B1就是想改成的那_用excel批量重命名图片

Windows批处理脚本:ffmpeg转换b站m4n视频_ffmpeg批处理脚本-程序员宅基地

文章浏览阅读437次。(3)将下载到的M4S文件拖到.bat文件上即可作为参数执行,转为MP3需要一个音频文件,转为MP4需要音频文件和视频文件两个一起拖到.bat上。(1)上面的代码两段set是用于修改下载到的文件的后缀名,从m4s改为MP3或者MP4。的是延迟变量,~n是取出文件的文件名,~x是取出文件的扩展名。(2)修改文件后缀名,将.txt改为.bat。(1)创建文本文件,并将需要代码复制进去。_ffmpeg批处理脚本

搜集的游戏引擎-程序员宅基地

文章浏览阅读130次。从Gist上发现的,挺全的IMPORTANT! Remember to check out the wiki page at https://github.com/bebraw/jswiki/wiki/Game-Engines for the most up to date version. There's also a "notes" column in the table but it s...

网易2018校园招聘编程题真题集合(一)_小易准备去魔法王国采购魔法神器购买魔法神器需要使用魔法币但是小易现在一枚魔-程序员宅基地

文章浏览阅读255次。1、答案及运行结果:递归(逆推):直接或者间接地调用自身递归算法解决问题的特点:(1) 递归就是在过程或函数里调用自身。(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一..._小易准备去魔法王国采购魔法神器购买魔法神器需要使用魔法币但是小易现在一枚魔

hashcode详解-程序员宅基地

文章浏览阅读2k次。HashCode是在Java中用于获取对象的唯一标识符的方法。它是根据对象的内容生成的一个整数值。对象的hashCode()方法被调用时,它返回的是对象的哈希码。哈希码可以用于在哈希表等数据结构中快速定位对象。在Java中,hashCode()方法是被Object类定义的,所有的对象都可以调用该方法。默认情况下,hashCode()方法返回的是对象的内存地址的哈希码表示。通常情况下,如果两个对象的equals()方法返回true,那么它们的hashCode()方法应该返回相同的值。_hashcode

java计算机毕业设计(附源码)英语单词学习软件app(ssm+mysql+maven+LW文档)-程序员宅基地

文章浏览阅读231次,点赞5次,收藏6次。其次,通过智能化的学习算法,软件能够根据用户的学习进度和记忆能力,提供定制化的学习计划和复习提醒,从而确保学习效果的最大化。此外,软件中的互动元素和游戏化设计,增加了学习的趣味性,激发了用户的学习动力。最后,随着用户词汇量的增加,他们将更加自信地运用英语进行沟通和表达,这不仅有助于个人职业发展,也促进了跨文化交流,增进了不同文化之间的理解和尊重。因此,英语单词学习软件APP的开发和应用,不仅是技术进步的体现,更是推动语言学习和文化交流的重要力量。随着科技的发展和移动设备的普及,学习英语的方式也在不断进化。

推荐文章

热门文章

相关标签