赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
在这之前写了STM32G070 hal Flash读写操作。
在这个基础上,做个IAP在线升级。
IAP升级方法有多种:
1、双APP内存法:创建两个APP内存,一个升级,一个运行;
2、BootLoader引导法:当程序升级时候,跳转到BootLoader去,进行升级。
下面介绍双APP内存法,BootLoader引导法后面在写。
提示:下面代码需要引用#include “./BSP/STMFLASH/stmflash.h”,在我上一个文章里面,连接 stm32g070 Flash HAL读写操作
STM32G070 Flash总共128kb,对应地址是0x8000000 -0x8020000。
我这里对他分成4个区域,分别是:
在代码中定义如下:
然后记录好上面地址,后面写代码需要!!
这里需要两套程序:
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。
在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程序主要就是3点:
keil偏移地址可以不改,修改原因是为了防止编写代码超了APP内存区域。如果程序比较小,可以不用管的。主要修改就是编译的起始地址。我这里改成是0x0800500,对应就是APP1_ADDR 0x08005000 //APP1 跳转地址
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 */ }
生成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点:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。