本例程仅供参考(个人学习总结_有需要文中有的封装好的跳转函数可私信),
例程可举一反三完成FDCAN通信和USART通信。
目录
简介
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程序配置步骤
-
APP 程序起始地址设置方法
图中 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 的倍数即可
-
中断向量表的偏移量设置方法
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 升级。
-
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
推荐相对地址,(不同的工程文件地址可能不同,需要根据自己的工程来定,文件路径较深推荐用绝对路径。)
转换语句的组成解释如下:
首先是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 程序的生成步骤
- 设置 APP 程序的起始地址和存储空间大小
- 设置中断向量表偏移量
- 设置编译后运行 fromelf.exe,生成.bin 文件
以上 3 个步骤,就可以得到一个.bin 的 APP 程序,通过 Bootlader 程序即可实现更新
APP程序中加入文件夹中的.c和.h文件在工程中添加包含路径,并在Main函数最开始部分添加语句
2.IAP(BootLoader 程序)配置(HAL库,Cubemax)
2.1RCC配置
2.2时钟树配置
2.3CAN配置(版本例程CAN接收数据和发送数据为普通模式,配合TIM2定时器使用)
2.4TIM2定时器配置
2.5USART配置
生成工程
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,然后上位机发送数据后会将发送的数据进行反馈给上位机,表示数据接收成功。
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的数组中
4.Flash写入数据
FLASH写入数据是引用封装后的函数,在文件夹的文件中的文件可以直接添加到工程中,并添加头文件的路径。使用时直接引用相关函数。
5.跳转至APP函数(即IAP)
跳转函数同样也是引用的封装好的函数,在跳转函数中做了一些改进,可以直接添加工程中直接进行使用
注意:由于是因引用的封装后的库,建议将
中的所有源文件(.c)和头文件(.h)同时添加到工程文件中。
到此部分Bootloader 程序的CAN通讯和usart通讯已经完成(接收升级固件包数据)
下面程序的升级逻辑介绍
6. Bootloader 程序
升级程序流程图如下
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升级效果
8.2USART升级效果
通过Ecantools发送更新,升级指令实现升级跳转程序文章来源:https://www.toymoban.com/news/detail-777211.html
文章来源地址https://www.toymoban.com/news/detail-777211.html
9.注意事项
- CAN接受数据时不可有延时出现,否则会影响数据接收
- CAN通过上位机发送bin文件数据或其他方式发送文件数据时,需要注意发送每一帧数据的时间间隔的控制,否则会出现数据丢帧,不能接收完整的数据包。
- 配置APP程序和Bootloader程序时,需要注意起始地址和空间大小,具体看程序的大小从而进行配置。本程序的起始控件大小如下
- 程序中有Printf重映射相关的函数,需要将上图中的 use MicroLIB勾选上
到了这里,关于STM32G473 固件升级IAP(BootLoader)CAN/USART。(详细步骤)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!