使用STM32G070 IAP HAL库实现在线升级
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
在这之前写了STM32G070 hal Flash读写操作。
在这个基础上,做个IAP在线升级。
IAP升级方法有多种:
1、双APP内存法:创建两个APP内存,一个升级,一个运行;
2、BootLoader引导法:当程序升级时候,跳转到BootLoader去,进行升级。
下面介绍双APP内存法,BootLoader引导法后面在写。
提示:下面代码需要引用#include “./BSP/STMFLASH/stmflash.h”,在我上一个文章里面,连接 stm32g070 Flash HAL读写操作
一、Flash分区
STM32G070 Flash总共128kb,对应地址是0x8000000 -0x8020000。
我这里对他分成4个区域,分别是:
在代码中定义如下:
然后记录好上面地址,后面写代码需要!!
这里需要两套程序:
- IAP引导程序,这个程序只能用下载工具烧录。
- APP升级程序,也叫应用程序。
二、IAP引导程序实现
1.创建iap文件
iap.h
代码如下(示例):
/**
****************************************************************************************************
* @file iap.c
* @author 935848559@qq.com
* @version V1.0
* @date 2024-03-11
* @brief IAP 代码
* @license Copyright (c) 2020-2032,
****************************************************************************************************
* @attention
*
*
****************************************************************************************************
*/
#ifndef __IAP_H
#define __IAP_H
#include "main.h"
#include "./BSP/STMFLASH/stmflash.h"
typedef void (*iapfun)(void); /* 定义一个函数类型的参数 */
#define FLASH_APP1_ADDR APP1_ADDR /* 第一个应用程序起始地址(存放在内部FLASH) */
void IAP_printf(void);
void iap_load_app(uint32_t appxaddr); /* 跳转到APP程序执行 */
void iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t applen); /* 在指定地址开始,写入bin */
#endif
上述代码需要引用#include “./BSP/STMFLASH/stmflash.h”,在我上一个文章里面,连接 stm32g070 Flash HAL读写操作
这里主要定义是FLASH_APP1_ADDR 程序地址,这个在后面配置时候需要。
iap.c
/**
****************************************************************************************************
* @file iap.c
* @author 935848559@qq.com
* @version V1.0
* @date 2024-03-11
* @brief IAP 代码
* @license Copyright (c) 2020-2032,
****************************************************************************************************
* @attention
*
*
****************************************************************************************************
*/
#include "./BSP/STMFLASH/IAP/iap.h"
iapfun jump2app;
/**
* @brief IAP 版本提示
* @param void
* @retval 无
*/
void IAP_printf(void)
{
printf("\r\n");
printf("******************************************************\r\n");
printf("* *\r\n");
printf("* IAP Version: 0.0.2 *\r\n");
printf("* *\r\n");
printf("******************************************************\r\n");
}
/**
* @brief IAP写入APP BIN
* @param appxaddr : 应用程序的起始地址
* @param appbuf : 应用程序CODE
* @param appsize : 应用程序大小(字节)
* @retval 无
*/
void iap_write_appbin(uint32_t appxaddr, uint8_t *appbuf, uint32_t appsize)
{
if(Write_Flash(appxaddr,appbuf,appsize) != FLASH_OK)
printf("iap_write_appbin error!!\r\n");
else
printf("iap_write_appbin ok!!\r\n");
}
/**
* @brief 设置栈顶地址
* @note 左侧的红X, 属于MDK误报, 实际是没问题的
* @param addr: 栈顶地址
* @retval 无
*/
void sys_msr_msp(uint32_t addr)
{
__set_MSP(addr); /* 设置栈顶地址 */
}
iapfun JumpToApplication;
/**
* @brief 跳转到应用程序段(执行APP)
* @param appxaddr : 应用程序的起始地址
* @retval 无
*/
void iap_load_app(uint32_t appxaddr)
{
if (((*(volatile uint32_t *)appxaddr) & 0x2FFE0000) == 0x20000000) /* 检查栈顶地址是否合法.可以放在内部SRAM共64KB(0x20000000) */
{
/* 用户代码区第二个字为程序开始地址(复位地址) */
jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4);
printf("jump2app = %p\r\n",jump2app); //打印main地址,我的测试程序显示地址是0x080050d1
/* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
sys_msr_msp(*(volatile uint32_t *)appxaddr);
printf("appxaddr = %x\r\n",appxaddr); //打印栈顶地址,应该是0x08005000
/* 跳转到APP */
jump2app();
}
else
{
printf("program in flash is error\r\n");
}
}
我们在这里需要注意的是跳转地址,跳转地址是APP地址+4,这个地址里面指针指向地址就是我们的程序运行地址。
我的APP程序跳转指向地址是0x080050d1,这里偏移50d1,是因为APP地址我这边设定就是0x800500。 偏移d1是地址程序main函数指针。
下图是正常STM32程序地址,基地址+4指向是0x80000d1。
2.main.c
在while循环里面读取到串口传来的APP代码,然后在上电重新启动,在while循环之前,升级app1程序,并加入判断程序是否升级成功。
代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART3_UART_Init();
MX_USART4_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t t;
uint32_t oldcount = 0; /* 老的串口接收数据值 */
uint32_t applenth = 0; /* 接收到的app代码长度 */
// IAP_printf();
// extern DMA_HandleTypeDef hdma_usart3_rx;
// debug_rxStC.Phdma_rx = &hdma_usart3_rx;
// HAL_UARTEx_ReceiveToIdle_DMA(&huart3,debug_rxStC.rx_buf,USART_REC_LEN);
HAL_UART_Receive_IT(&huart3, (uint8_t *)&aRxBuffer3, 1);
uint32_t config = stmflash_read_word(CONFIG_ADDR);
printf("config = %x\r\n",config);
#define LEN 10240
if(config == UPDATE_FLAG)
{
STMFLASH_Read(OTA_ADDR,debug_rxStC.rx_buf,LEN); //读取OTA_ADDR上面的程序,放到内存里面,进行升级
Write_Flash(APP1_ADDR,debug_rxStC.rx_buf,LEN); //把内存数据写到APP1_ADDR
Write_Config(UPDATE_OVER); //重置标识位
iap_load_app(APP1_ADDR); //跳转程序APP1
}
else
printf("config error\r\n");
if(config == UPDATE_OVER)
{
printf("UPDATE_OVER iap_load_app\r\n");
iap_load_app(APP1_ADDR);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (debug_rxStC.rx_cnt)
{
if (oldcount == debug_rxStC.rx_cnt) /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
{
applenth = debug_rxStC.rx_cnt;
oldcount = 0;
debug_rxStC.rx_cnt = 0;
printf("用户程序接收完成!\r\n");
printf("代码长度:%dBytes\r\n", applenth);
}
else oldcount = debug_rxStC.rx_cnt;
if(applenth >9000)
{
printf("用户程序开始升级!\r\n");
Flash_test(debug_rxStC.rx_buf, applenth);
}
}
LED_G_TOGGLE();
LED0_TOGGLE();
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
三、app程序实现
app程序主要就是3点:
- 配置keil
- 在main中重映射向量表
- 生成bin文件
1.配置keil
keil偏移地址可以不改,修改原因是为了防止编写代码超了APP内存区域。如果程序比较小,可以不用管的。主要修改就是编译的起始地址。我这里改成是0x0800500,对应就是APP1_ADDR 0x08005000 //APP1 跳转地址
1.在main中重映射向量表
SCB->VTOR = APP1_ADDR; //重映射向量表 下面代码就是这个一句话最重要,APP1_ADDR 0x08005000 //APP1 跳转地址
int main(void)
{
/* USER CODE BEGIN 1 */
SCB->VTOR = APP1_ADDR; //重映射向量表
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART3_UART_Init();
MX_USART4_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t i;
IAP_printf();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
LED0_TOGGLE();
LED_R_TOGGLE();
HAL_Delay(100);
printf("app1程序!\r\n");
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.生成.bin 文件
生成bin文件方法有很多,这里就介绍一个,用keil自带方法,网上也有很多,这里不赘述
在4中添加C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe –bin –output .\test.bin .\G070-warehouse\G070-warehouse.axf
这个是我的文件路径。具体方法,可以自行查找,资料比较多,就演示一下!
四、测试结果
用串口发送bin文件,接收后,重新上电,就完成升级!
五、出现的问题
在IAP代码中,一开始总是升级不成功,程序完成写入后,跳转的APP中就死机,无法仿真,后来发现是使用的DMA串口空闲中断 接收出现错误,可以接收但是数据不完整。这里使用的串口中断接收,没有问题。
总结
IAP升级总结就是4点:
- boot程序:分好区域,重新写入引导程序,引导程序就是flash读取和写入。
- boot程序的keil的flash可以不用配置,配置flash大小就是防止boot超出内存。
- 逻辑:用config区,提示升级标志,这个区域可以用结构体解析,存储应用其他信息
- app程序:就是重新配置中断向量表和keil的flash编译地址。
作者:小鱼️遨游