使用STM32G070 IAP HAL库实现在线升级

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、Flash分区
  • 二、IAP引导程序实现
  • 1.创建iap文件
  • 2.main.c
  • 三、app程序实现
  • 1.配置keil
  • 1.在main中重映射向量表
  • 3.生成.bin 文件
  • 四、测试结果
  • 五、出现的问题
  • 总结

  • 前言

    在这之前写了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个区域,分别是:

    1. BootLoader 区 0x08000000 – 0x08004800	18k
    在代码中定义如下:

    然后记录好上面地址,后面写代码需要!!
    这里需要两套程序:

    1. IAP引导程序,这个程序只能用下载工具烧录。
    2. 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点:

    1. 配置keil
    2. 在main中重映射向量表
    3. 生成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点:

    1. boot程序:分好区域,重新写入引导程序,引导程序就是flash读取和写入。
    2. boot程序的keil的flash可以不用配置,配置flash大小就是防止boot超出内存。
    3. 逻辑:用config区,提示升级标志,这个区域可以用结构体解析,存储应用其他信息
    4. app程序:就是重新配置中断向量表和keil的flash编译地址。

    作者:小鱼️遨游

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32G070 IAP HAL库实现在线升级

    发表评论