STM32F103基于STM32CubeMX实现OTA升级(二)代码解析

0 引言

在上一篇文章中,我们已经讲述了STM32的启动流程、IAP的原理和OTA的原理(最后这部分直接分享了一些博客,因为前辈们已经写的非常好了),下面这篇主要用来记录STM32-OTA的实验步骤。源码我大家自行下载即可。

链接:https://pan.baidu.com/s/1uemqEqDNI3-IjulZ4oNFlw?pwd=of3g
提取码:of3g

参考:

  • STM32CubeMx开发之路—在线升级OTA
  • 1 实验条件

    1.1 大家需要准备:
  • STM32F103开发板(我是采用正点的精英版)
  • USB-TTL转化器(查看串口数据)
  • XShell(程序中需要用Ymodem协议进行传输)
  • 1.2 程序叙述:

    我们需要编写3个程序,即新建三个keil工程(分别为bootloader,app1, app2),并把app2转成bin文件(其用作新文件)

    1.3 FLASH分配:

    F103的FALSH共512K,bootloader使用100K, app1:200K,app2:200K,private: 12K(主要用于存放升级标志位),升级标志位存放在 0x0807F800(最后一页的开始)。

    本实验的FLASH设计:

    name 起始地址 size
    boot 0x08000000 0x00019000 (100K)
    app1 0x08019000 0x00032000 (200K)
    app2 0x0804B000 0x00032000 (200K)
    private 0x0807D000 0x00003000 (12K)

    2 bootloader程序设计

    本实验中的bootloader主要用于判断是否有更新标志,如果有,则将app2中的程序拷贝到app1中,并实现中断向量表的跳转。

    2.1 STM32CubeMX程序设计

    在STM32CubeMX中进行初始化设置,外设的话只需要开USART串口(并开启全局中断)即可:

    2.2 在keil中编程程序

    只需要修改main函数中的程序,同时新建f103Flash.c和f103Flash.h文件即可。

    2.2.1 main.c文件设计

    1、 串口1重定向:

    #ifdef __GNUC__
      #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    #else
      #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif
    
    PUTCHAR_PROTOTYPE
    {
      HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);   //阻塞方式打印,串口1
      return ch;
    }
    

    2、 升级标志位

    读取FLASH中标志位的信息:

  • 如果为0xAAAAAAAAAAAAAAAA,则系统有需要升级的程序,需要将FLASH中的程序从app2拷贝到app1,拷贝完成后中断向量表实现跳转app1;
  • 如果没有升级,则中断向量表直接跳转至app1;
  • uint64_t Upgrade_Flag;//升级标志
    uint64_t NotUpgrade = {0xFFFFFFFFFFFFFFFF};
    uint64_t Upgrade =    {0xAAAAAAAAAAAAAAAA};
    

    中断向量表跳转的app1的程序:

    #define  FLASH_APP_ADDR  (0x08019000)//APP存放起始地址
    
    typedef  void (*pFunction)(void);
    pFunction Jump_To_Application;
    void Jump_to_APP(uint32_t app_addr)
    {
         uint32_t JumpAddress;
    	 printf("jump to app: %#x\r\n", FLASH_APP_ADDR);
    	
        /* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
        if (((*(volatile uint32_t*)FLASH_APP_ADDR) & 0x2FFE0000 ) == 0x20000000)              // 上面的值是取出FLASH_APP_ADDR里面的值来
        {
            __set_PRIMASK(1);                                             //关总中断  0打开总中断
            JumpAddress = *(volatile uint32_t*) (FLASH_APP_ADDR + 4);    
            Jump_To_Application = (pFunction) JumpAddress;
            printf("jump %#x success \r\n",FLASH_APP_ADDR);
            __set_MSP(*(volatile uint32_t*) FLASH_APP_ADDR);             //设置SP指针,复位指针
            Jump_To_Application();                                       //开始跳转
        }
        else
        {
            printf( "erorr [0x%08x]\r\n",(*(volatile uint32_t*)FLASH_APP_ADDR) );
        }  
    }
    
    

    3、将数据从app2拷贝到app1中:

    uint64_t u64_Code_Buff[256];//2k
    uint16_t Num = 0;
    void Copy_APP2_to_APP1(void)
    {
      uint32_t APP1_Addr;
      uint32_t APP2_Addr;
      
      APP1_Addr = FLASH_APP1_START_ADDR;
      APP2_Addr = FLASH_APP2_START_ADDR;
      
      MyErase_page(FLASH_APP1_START_ADDR, APP1_PAGE_SUM);//擦除APP1所在地址
      printf("Erase app1 flash\r\n");
      
      /*APP2拷贝到APP1*/
      for(int i=0;i<40;i++)       //一次拷贝2048个字节, 2K字节,限制APP程序最多80KB
      {
          printf("APP1_Addr = %x\r\n",APP1_Addr);
          printf("APP2_Addr = %x\r\n",APP2_Addr);
          
          MyRead64Flash (APP2_Addr, u64_Code_Buff, 256);  //读2k APP2 flash内容
          HAL_Delay(10);
          MyWrite64Flash(APP1_Addr, u64_Code_Buff, 256);  //将从app2 flash读取到的2k程序写入到app1 flash中
          HAL_Delay(10);
          APP1_Addr += 0x800;
          APP2_Addr += 0x800;
          
          /*清空缓存*/
          memset(u64_Code_Buff,0,sizeof(u64_Code_Buff));
          Num = i;
          
          if(flash_flag == 0)
          {
            printf("Successfully copied page %d\r\n",Num);
          }
          else
          {
            printf("Copy failed page %d\r\n",Num);
          }
      }
      MyErase_page(FLASH_APP2_START_ADDR, APP2_PAGE_SUM);//拷贝结束,擦除APP2所在地址
      InFlashWrite(UPGRADE_FLAG_ADDR, NotUpgrade);       //将升级标志设置为不升级
    }
    

    4、main函数中功能为:

    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_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
      printf("bootloader running\r\n");
      print_boot_message();
    	
    	Upgrade_Flag = FLASH_R(UPGRADE_FLAG_ADDR);
    	printf("Upgrade_Flag = %llx\r\n", Upgrade_Flag);
    
    	if(NotUpgrade == Upgrade_Flag)
      { 
    		  printf("------%#x", (FLASH_APP_ADDR+4)&0xFF000000);
          /*跳转到APP1中运行*/
          if(((FLASH_APP_ADDR+4)&0xFF000000)==0x08000000)    //Judge if start at 0X08XXXXXX.
          {
              printf("jump APP1 running\r\n");
              Jump_to_APP(FLASH_APP_ADDR); // Jump to APP
          }
      }
    
      if(Upgrade == Upgrade_Flag)
      {
          /*将APP2拷贝到APP1中*/
          Copy_APP2_to_APP1();
    		  printf("------%#x", (FLASH_APP_ADDR+4)&0xFF000000);
    		
          if(((FLASH_APP_ADDR+4)&0xFF000000)==0x08000000)   //Judge if start at 0X08XXXXXX.
          {
              printf("jump APP2 running\r\n");
              Jump_to_APP(FLASH_APP_ADDR); // Jump to APP
          }
      }
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    

    5、main.c的整体代码如下所示:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2023 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    #include "stdio.h"
    #include "f103Flash.h"
    #include "string.h"
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    #ifdef __GNUC__
      #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    #else
      #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif
    
    PUTCHAR_PROTOTYPE
    {
      HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);   //阻塞方式打印,串口1
      return ch;
    }
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    uint64_t Upgrade_Flag;//升级标志
    uint64_t NotUpgrade = {0xFFFFFFFFFFFFFFFF};
    uint64_t Upgrade =    {0xAAAAAAAAAAAAAAAA};
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    // ******************************** 定义初始函数 ***********************************//
    #define  FLASH_APP_ADDR  (0x08019000)//APP存放起始地址
    
    typedef  void (*pFunction)(void);
    pFunction Jump_To_Application;
    void Jump_to_APP(uint32_t app_addr)
    {
        uint32_t JumpAddress;
    	  printf("jump to app: %#x\r\n", FLASH_APP_ADDR);
    	
        /* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
        if (((*(volatile uint32_t*)FLASH_APP_ADDR) & 0x2FFE0000 ) == 0x20000000)              // 上面的值是取出FLASH_APP_ADDR里面的值来
        {
            __set_PRIMASK(1);                                             //关总中断  0打开总中断
            JumpAddress = *(volatile uint32_t*) (FLASH_APP_ADDR + 4);    
            Jump_To_Application = (pFunction) JumpAddress;
            printf("jump %#x success \r\n",FLASH_APP_ADDR);
            __set_MSP(*(volatile uint32_t*) FLASH_APP_ADDR);             //设置SP指针,复位指针
            Jump_To_Application();                                       //开始跳转
        }
        else
        {
            printf( "erorr [0x%08x]\r\n",(*(volatile uint32_t*)FLASH_APP_ADDR) );
        }  
    }
    
    
    void print_boot_message(void)
    {
    	  // private中的最后一个字节存放是否升级的标志位
        printf("---------- Enter BootLoader ----------\r\n");
        printf("\r\n");
        printf("======== flash pration table =========\r\n");
        printf("| name     | start addr |    size    |\r\n");
        printf("--------------------------------------\r\n");
        printf("| boot     | 0x08000000 | 0x00019000 |\r\n");
        printf("| app1     | 0x08019000 | 0x00032000 |\r\n");
        printf("| app2     | 0x0804B000 | 0x00032000 |\r\n");
        printf("| private  | 0x0807D000 | 0x00003000 |\r\n");
        printf("======================================\r\n");
    }
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    /************************************************************
    *函数名:Copy_APP2_to_APP1
    *功  能:APP2拷贝到APP1地址中
    *参  数:无
    *返回值:无
    *************************************************************/
    
    uint64_t u64_Code_Buff[256];//2k
    uint16_t Num = 0;
    void Copy_APP2_to_APP1(void)
    {
      uint32_t APP1_Addr;
      uint32_t APP2_Addr;
      
      APP1_Addr = FLASH_APP1_START_ADDR;
      APP2_Addr = FLASH_APP2_START_ADDR;
      
      MyErase_page(FLASH_APP1_START_ADDR, APP1_PAGE_SUM);//擦除APP1所在地址
      printf("Erase app1 flash\r\n");
      
      /*APP2拷贝到APP1*/
      for(int i=0;i<40;i++)       //一次拷贝2048个字节, 2K字节,限制APP程序最多80KB
      {
          printf("APP1_Addr = %x\r\n",APP1_Addr);
          printf("APP2_Addr = %x\r\n",APP2_Addr);
          
          MyRead64Flash (APP2_Addr, u64_Code_Buff, 256);  //读2k APP2 flash内容
          HAL_Delay(10);
          MyWrite64Flash(APP1_Addr, u64_Code_Buff, 256);  //将从app2 flash读取到的2k程序写入到app1 flash中
          HAL_Delay(10);
          APP1_Addr += 0x800;
          APP2_Addr += 0x800;
          
          /*清空缓存*/
          memset(u64_Code_Buff,0,sizeof(u64_Code_Buff));
          Num = i;
          
          if(flash_flag == 0)
          {
            printf("Successfully copied page %d\r\n",Num);
          }
          else
          {
            printf("Copy failed page %d\r\n",Num);
          }
      }
      MyErase_page(FLASH_APP2_START_ADDR, APP2_PAGE_SUM);//拷贝结束,擦除APP2所在地址
      InFlashWrite(UPGRADE_FLAG_ADDR, NotUpgrade);       //将升级标志设置为不升级
    }
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    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_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
      printf("bootloader running\r\n");
      print_boot_message();
    	
    	Upgrade_Flag = FLASH_R(UPGRADE_FLAG_ADDR);
    	printf("Upgrade_Flag = %llx\r\n", Upgrade_Flag);
    
    	if(NotUpgrade == Upgrade_Flag)
      { 
    		  printf("------%#x", (FLASH_APP_ADDR+4)&0xFF000000);
          /*跳转到APP1中运行*/
          if(((FLASH_APP_ADDR+4)&0xFF000000)==0x08000000)    //Judge if start at 0X08XXXXXX.
          {
              printf("jump APP1 running\r\n");
              Jump_to_APP(FLASH_APP_ADDR); // Jump to APP
          }
      }
    
      if(Upgrade == Upgrade_Flag)
      {
          /*将APP2拷贝到APP1中*/
          Copy_APP2_to_APP1();
    		  printf("------%#x", (FLASH_APP_ADDR+4)&0xFF000000);
    		
          if(((FLASH_APP_ADDR+4)&0xFF000000)==0x08000000)   //Judge if start at 0X08XXXXXX.
          {
              printf("jump APP2 running\r\n");
              Jump_to_APP(FLASH_APP_ADDR); // Jump to APP
          }
      }
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    
    2.2.2 f103flash.c文件设计

    此文件重要用于对FALSH操作的整合,对HAL库提供的函数进行了封装,其设计如下所示:

    #include "f103Flash.h"
    #include "stdint.h"
    #include "main.h"
    #include "stdio.h"
    #include "string.h"
    
    /*获取当前地址所在页*/
    static uint32_t GetPage(uint32_t Addr)
    {
      return (Addr - FLASH_BASE) / FLASH_PAGE_SIZE;
    }
    
    
    /***********************************************
    *函数名:MyErase_page
    *功  能:指定擦除页数
    *参  数:pageaddr:擦除的起始页
    *参  数:num:     擦除的页数
    *返回值:1
    *************************************************/
    int MyErase_page(uint32_t pageaddr, uint32_t num)
    {
      uint32_t FirstPage = 0;//擦除的地址首页
      uint32_t PageError = 0;
      
      printf("FirstPage = %d\r\n", FirstPage);
      printf("Page num = %d\r\n", num);
      HAL_FLASH_Unlock();
      
      /* 擦除FLASH*/
      FLASH_EraseInitTypeDef FlashSet;
    	
      FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;
    	FlashSet.PageAddress = pageaddr;
    	FlashSet.NbPages = num;
    	
      /*设置PageError,调用擦除函数*/
      if(HAL_FLASHEx_Erase(&FlashSet, &PageError) == HAL_OK)
      {
        printf("Erase Page Success\r\n");
      }
      else
      {
        printf("Erase Page Error\r\n");
      }
      
      HAL_FLASH_Lock();
      return 1;
    }
    
    //不定长按读8字节读取flash中的值
    /***********************************************
    *函数名:MyRead64Flash
    *功  能:指定地址读取不定长字符串
    *参  数:addr      :指定读取的起始地址
    *参  数:buff      :存储读取数据的字符串首地址
    *参  数:word_size :读取的数组长度
    *返回值:无
    *************************************************/
    void MyRead64Flash(uint32_t addr, uint64_t * buff, uint16_t word_size)
    {
      for(int i =0; i < word_size; i++)
      {
        buff[i] = *(__IO uint64_t*)(addr + 8 * i);
      }
      return;
    }
    
    
    /***************************************************************
    *函数名:FLASH_R
    *功  能:指定内部FLASH地址读出uint64_t数据
    *参  数:64位读出FLASH地址。
    *返回值:一个uint64_t数据
    *******************************************************************/
    uint64_t FLASH_R(uint32_t add)//参数1:32位读出FLASH地址。返回值:16位数据
    { 
      uint64_t a;
      a = *(uint64_t*)(add);//从指定页的addr地址开始读
      return a;
    }
    
    //写flash
    //参数:addr:写入地址;buff:单字节数组;word_size:写入数组长度
    /***********************************************
    *函数名:My_Write_Flash
    *功  能:指定地址写入不定长uint64_t数组
    *参  数:addr      :指定写入的起始地址
    *参  数:buff      :写入的uint64_t数组首地址
    *参  数:double_word_size :写入的数组长度
    *返回值:无
    *************************************************/
    uint8_t flash_flag = 0;
    void MyWrite64Flash(uint32_t addr, uint64_t * buff, int double_word_size)
    {
      HAL_FLASH_Unlock();
      //__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);//清除标志位
      for(int i = 0; i < double_word_size; i++)
      {
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, (uint32_t)(uint64_t *)addr + (8 * i), buff[i]) == HAL_OK)
        {
          flash_flag = 0;
        }
        else
        {
            printf("Flash write failed, Start rewriting \r\n");
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, (uint32_t)(uint64_t *)addr + (8 * i), buff[i]) == HAL_OK)
            {
                
            }
    				else
    				{
    						flash_flag = 1;
    						printf("Flash Rewrite failed\r\n");
    						return ;
    				}
        }
      }
      
      HAL_FLASH_Lock();
      
    }
    
    
    /******************************************************
    *
    *功能:指定地址写入一个双字(uint64_t)内部FLASH
    *****************************************************/
    void InFlashWrite(uint32_t Address, uint64_t data)
    {
      HAL_FLASH_Unlock();//开锁
      
      uint32_t FirstPage = 0, NbOfPages = 0;
      uint32_t PageError = 0;
    
      FLASH_EraseInitTypeDef EraseInitStruct;
      
      FirstPage = GetPage(Address);//首页地址
      printf("FirstPage = %d\r\n", FirstPage);
    
      NbOfPages = GetPage(Address+sizeof(data)) - FirstPage + 1;//页数
      printf("NbOfPages = %d\r\n", NbOfPages);
      
      EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
      EraseInitStruct.PageAddress = Address;
      EraseInitStruct.NbPages     = NbOfPages;
    	
      if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)
      {
        printf("ErasePageError\r\n");
      }else{
    	  printf("ErasePageSuccess\r\n");
    	}
      
      if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, data) == HAL_OK)
      {
        printf("Flash write success\r\n");
      }
      HAL_FLASH_Lock();//上锁
      
    }
    
    
    2.2.3 f103flash.h文件设计:
    #ifndef __STM32G0FLASH_H__
    #define __STM32G0FLASH_H__
    
    #include "main.h"
    
    
    #define STM32_FLASH_SIZE 512  //512k flash
    
    #define FLASH_START_ADDR           (0x08000000)              //FLASH起始地址
    
    
    /*APP1地址分配,大小0x32000 100页 200k*/
    #define FLASH_APP1_START_ADDR      (0x08019000)             //APP1 FLASH起始地址,FLASH大小0x32000
    #define FLASH_APP1_END_ADDR        ((0x08019000)+(0x32000))  //APP1 FLASH结束地址
    #define APP1_PAGE_SUM              (0x32000/0x800)           //APP1存储空间页数
    
    /*APP2地址分配,大小0x32000 100页 200k*/
    #define FLASH_APP2_START_ADDR      (0x0804B000)             //APP2 FLASH起始地址,FLASH大小0x32000
    #define FLASH_APP2_END_ADDR        ((0x0804B000)+(0x32000))  //APP2 FLASH结束地址
    #define APP2_PAGE_SUM              (0x32000/0x800)           //APP2存储空间页数
    
    /*自定义区地址分配,大小0x3000 6页 12k*/
    #define FLASH_PERSONAL_START_ADDR  (0x0807D000)             //自定义区FLASH起始地址,FLASH大小0x3000
    #define FLASH_PERSONAL_END_ADDR    ((0x0807D000)+(0x3000))  //自定义区FLASH结束地址
    #define PERSONAL_PAGE_SUM          (0x3000/0x800)           //自定义区存储空间页数
    
    /*最后一页的第一个数据存放升级标志Upgrade_Flag  0x0807D000 + 0x2800  */
    #define UPGRADE_FLAG_ADDR          ((uint32_t)0x0807F800)
    
    #define RAM_START_ADDR             (0x20000000)
    
    extern uint8_t flash_flag;
    
    /***********************************************
    *函数名:MyErase_page
    *功  能:指定擦除页数
    *参  数:pageaddr:擦除的起始页
    *参  数:num:     擦除的页数
    *返回值:1
    *************************************************/
    int MyErase_page(uint32_t pageaddr, uint32_t num);//
    
    /***********************************************
    *函数名:MyWrite_Flash
    *功  能:指定地址写入不定长字符串
    *参  数:addr      :指定写入的起始地址
    *参  数:buff      :写入的字符串首地址
    *参  数:word_size :写入的数组长度
    *返回值:无
    *************************************************/
    //void MyWrite_Flash(uint32_t addr, unsigned char * buff, int word_size);//不定长指定地址写入flash
    
    
    /***********************************************
    *函数名:MyRead64Flash
    *功  能:指定地址读取不定长字符串
    *参  数:addr      :指定读取的起始地址
    *参  数:buff      :存储读取数据的字符串首地址
    *参  数:word_size :读取的数组长度
    *返回值:无
    *************************************************/
    void MyRead64Flash(uint32_t addr, uint64_t * buff, uint16_t word_size);
    
    /***************************************************************
    *函数名:FLASH_R
    *功  能:指定内部FLASH地址读出uint64_t数据
    *参  数:64位读出FLASH地址。
    *返回值:一个uint64_t数据
    *******************************************************************/
    uint64_t FLASH_R(uint32_t add);
    
    //写flash
    //参数:addr:写入地址;buff:单字节数组;word_size:写入数组长度
    /***********************************************
    *函数名:My_Write_Flash
    *功  能:指定地址写入不定长uint64_t数组
    *参  数:addr      :指定写入的起始地址
    *参  数:buff      :写入的uint64_t数组首地址
    *参  数:double_word_size :写入的数组长度
    *返回值:无
    *************************************************/
    void MyWrite64Flash(uint32_t addr, uint64_t * buff, int double_word_size);
    
    
    /******************************************************
    *
    *功能:指定地址写入一个双字(uint64_t)内部FLASH
    *****************************************************/
    void InFlashWrite(uint32_t Address, uint64_t data);
    #endif
    

    在烧录程序的时候一定要注意:

    然后烧录进去,bootloader的程序则设计实现完成,下面进行app1程序的设计,其中要用到Ymodem协议,同时需要进行串口的DMA接收。

    在串口调试助手中的运行结果如下:

    3 APP1程序设计

    在app1中主要实现的功能为:新bin文件的接收与中断向量表的偏移。我们需要在STM32CubeMx中新建程序:

    3.1 STM32CubeMX设计:

    需要开启两个串口,一个用于打印,另一个用于接收(DMA接收)


    3.2 Keil程序设计:
    3.2.1 main.c 函数设计

    1、串口1重定向:
    这个就不写了,同上。

    2、中断向量表偏移:

    static void VectorRemap(void)
    {
    	__set_PRIMASK(0);   //  打开全局中断
      SCB->VTOR = FLASH_BASE|0x19000;
    }
    

    3、开启串口中断接收:

    HAL_UART_Receive_DMA(&huart2, Rx_Buf, Rx_Max);  
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); 
    

    4、main.c函数整体代码:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2023 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "dma.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "stdio.h"
    #include "ymodem.h"
    #define Rx_Max 2048 	 
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    int fputc(int ch, FILE *f)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1, 0xFFFF);
        return ch;
    }
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    uint8_t		Rx_Flag = 0;
    uint16_t		Rx_Len = 0;
    uint8_t		Rx_Buf[Rx_Max] = {0};	
    uint8_t    flag_reset = 0;
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    static void VectorRemap(void)
    {
    	__set_PRIMASK(0);
      SCB->VTOR = FLASH_BASE|0x19000;
    }
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    	
    	VectorRemap();//中断向量表重映射
    	
      /* 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_USART1_UART_Init();
      MX_USART2_UART_Init();
      /* USER CODE BEGIN 2 */
      HAL_UART_Receive_DMA(&huart2, Rx_Buf, Rx_Max);  
    	__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); 
    	
    	printf("\r\n");
    	printf("***********************************\r\n");
    	printf("*                                 *\r\n");
    	printf("*    Leaf_Fruit's Application     *\r\n");
    	printf("*                                 *\r\n");
    	printf("*    Version : 0.0.1              *\r\n");
    	printf("*                                 *\r\n");
    	printf("***********************************\r\n");
    	
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {	
    		
    		ymodem_fun();
    		
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    
    3.2.2 stm32f1xx_it.c设计:

    这部分主要是为了配合串口接收文件设计的,在下面使用串口空闲中断接收的方法实现:

    void USART2_IRQHandler(void)
    {
      /* USER CODE BEGIN USART2_IRQn 0 */
      uint32_t temp;
    	if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))  
    	{   
    		/*清除状态寄存器和串口数据寄存器*/
    		__HAL_UART_CLEAR_IDLEFLAG(&huart2); 
    		/*失能DMA接收*/
    		HAL_UART_DMAStop(&huart2);  
    		/*读取接收长度,总大小-剩余大小*/
    		temp = huart2.hdmarx->Instance->CNDTR; 
    		Rx_Len = Rx_Max - temp; 
    		/*接收标志位置1*/
    		Rx_Flag=1;  
    		/*使能接收DMA接收*/
    		HAL_UART_Receive_DMA(&huart2,Rx_Buf,Rx_Max);  
    	}
      /* USER CODE END USART2_IRQn 0 */
      HAL_UART_IRQHandler(&huart2);
      /* USER CODE BEGIN USART2_IRQn 1 */
    
      /* USER CODE END USART2_IRQn 1 */
    }
    
    3.2.3 ymodelm.c设计

    这个就是负责接收bin文件,其协议可参考文章开头的博客。

    #include "ymodem.h"
    #include "main.h"
    #include "stdio.h"
    #include "usart.h"
    #include "string.h"
     
    
    #define Rx_Max 2048 
    
    
    __STATIC_INLINE void Set_FAULTMASK(uint32_t faultMask)
    {
      register uint32_t __regFaultMask       __ASM("faultmask");
      __regFaultMask = (faultMask & (uint32_t)1U);
    } 
    
    extern uint8_t		Rx_Flag;
    extern uint16_t		Rx_Len;
    extern uint8_t		Rx_Buf[Rx_Max] ;	
    extern uint8_t    flag_reset;
    
    uint64_t NotUpgrade = {0xFFFFFFFFFFFFFFFF};
    uint64_t Upgrade =    {0xAAAAAAAAAAAAAAAA};
    
    /* 发送指令 */
    void send_command(unsigned char command)
    {
    	HAL_UART_Transmit(&huart2, (uint8_t *)&command,1 , 0xFFFF);
    	HAL_Delay(10);
    }
    
    /**
     * @bieaf 擦除页
     *
     * @param pageaddr  页起始地址	
     * @param num       擦除的页数
     * @return 1
     */
    static int Erase_page(uint32_t pageaddr, uint32_t num)
    {
    	HAL_FLASH_Unlock();
    	
    	/* 擦除FLASH*/
    	FLASH_EraseInitTypeDef FlashSet;
    	FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;
    	FlashSet.PageAddress = pageaddr;
    	FlashSet.NbPages = num;
    	
    	/*设置PageError,调用擦除函数*/
    	uint32_t PageError = 0;
    	if(HAL_FLASHEx_Erase(&FlashSet, &PageError) == HAL_OK){
    		printf("erase APP2 success\r\n");
    	}
    	else{
    		printf("erase APP2 failed\r\n");
    	}
    	
    	HAL_FLASH_Lock();
    	return 1;
    }
    
    
    /**
     * @bieaf 写若干个数据
     *
     * @param addr       写入的地址
     * @param buff       写入数据的数组指针
     * @param word_size  长度
     * @return 
     */
    static void WriteFlash(uint32_t addr, uint32_t * buff, int word_size)
    {	
    	/* 1/4解锁FLASH*/
    	HAL_FLASH_Unlock();	
    	for(int i = 0; i < word_size; i++)	
    	{
    		/* 3/4对FLASH烧写*/
    		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + 4 * i, buff[i]);	
    	}
    
    	/* 4/4锁住FLASH*/
    	HAL_FLASH_Lock();
    }
    
    
    /*获取当前地址所在页*/
    static uint32_t GetPage(uint32_t Addr)
    {
      return (Addr - FLASH_BASE) / FLASH_PAGE_SIZE;
    }
    
    
    void InFlashWrite(uint32_t Address, uint64_t data)
    {
      HAL_FLASH_Unlock();//开锁
      
      uint32_t FirstPage = 0, NbOfPages = 0;
      uint32_t PageError = 0;
    
      FLASH_EraseInitTypeDef EraseInitStruct;
      
      FirstPage = GetPage(Address);//首页地址
      printf("FirstPage = %d\r\n", FirstPage);
    
      NbOfPages = GetPage(Address+sizeof(data)) - FirstPage + 1;//页数
      printf("NbOfPages = %d\r\n", NbOfPages);
      
      EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
      EraseInitStruct.PageAddress = Address;
      EraseInitStruct.NbPages     = NbOfPages;
      
      if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)
      {
        printf("ErasePageError\r\n");
      }
      
      if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, data) == HAL_OK)
      {
        printf("Flash write success\r\n");
      }
      HAL_FLASH_Lock();//上锁
      
    }
    
    /* 标记升级完成 */
    void Set_Update_Down(void)
    {
    	unsigned int update_flag = 0xAAAAAAAA;				///< 对应bootloader的启动步骤
    	WriteFlash((Application_2_Addr + Application_Size - 4), &update_flag,1 );
    }
    
    
    
    /* 临时存储的buff */
    unsigned char save_buf[128] = {0};
    
    /**
     * @bieaf CRC-16 校验
     *
     * @param addr 开始地址
     * @param num   长度
     * @param num   CRC
     * @return crc  返回CRC的值
     */
    #define POLY        0x1021  
    uint16_t crc16(unsigned char *addr, int num, uint16_t crc)  
    {  
        int i;  
        for (; num > 0; num--)					/* Step through bytes in memory */  
        {  
            crc = crc ^ (*addr++ << 8);			/* Fetch byte from memory, XOR into CRC top byte*/  
            for (i = 0; i < 8; i++)				/* Prepare to rotate 8 bits */  
            {
                if (crc & 0x8000)				/* b15 is set... */  
                    crc = (crc << 1) ^ POLY;  	/* rotate and XOR with polynomic */  
                else                          	/* b15 is clear... */  
                    crc <<= 1;					/* just rotate */  
            }									/* Loop for 8 bits */  
            crc &= 0xFFFF;						/* Ensure CRC remains 16-bit value */  
        }										/* Loop until num=0 */  
        return(crc);							/* Return updated CRC */  
    }
    
    
    
    /**
     * @bieaf 获取数据包的类型, 顺便进行校验
     *
     * @param buf 开始地址
     * @param len 长度
     * @return 
     */
    unsigned char Check_CRC(unsigned char* buf, int len)
    {
    	unsigned short crc = 0;
    	
    	/* 进行CRC校验 */
    	if((buf[0]==0x00)&&(len >= 133))
    	{
    		crc = crc16(buf+3, 128, crc);
    		if(crc != (buf[131]<<8|buf[132]))
    		{
    			return 0;///< 没通过校验
    		}
    		
    		/* 通过校验 */
    		return 1;
    	}
    }
    
    
    
    /* 设置升级的步骤 */
    static enum UPDATE_STATE update_state = TO_START;
    void Set_state(enum UPDATE_STATE state)
    {
    	update_state = state;
    }
    
    
    /* 查询升级的步骤 */
    unsigned char Get_state(void)
    {
    	return update_state;
    }
    
    
    unsigned char temp_buf[512] = {0};
    uint16_t temp_len = 0;
    
    /**
     * @bieaf YModem升级
     *
     * @param none
     * @return none
     */
    void ymodem_fun(void)
    {
    	int i;
    	if(Get_state()==TO_START)
    	{
    		send_command(CCC);
    		HAL_Delay(1000);
    	}
    	if(Rx_Flag)    	// Receive flag
    	{
    		Rx_Flag=0;	// clean flag
    				
    		/* 拷贝 */
    		temp_len = Rx_Len;
    		printf("---Rx-len: %#x--------:\r\n", Rx_Len);
    		for(i = 0; i < temp_len; i++)
    		{
    			temp_buf[i] = Rx_Buf[i];
    		}
    		switch(temp_buf[0])
    		{
    			case SOH:///<数据包开始
    			{
    				static unsigned char data_state = 0;
    				static unsigned int app2_size = 0;
    				if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
    				{					
    					if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
    					{
    						printf("> Receive start...\r\n");
    						Set_state(TO_RECEIVE_DATA);
    						data_state = 0x01;						
    						send_command(ACK);
    						send_command(CCC);
    						printf("1111111111\r\n");
    
    						/* 擦除App2 */							
    						Erase_page(Application_2_Addr, 100);    // 要擦除100页
    						printf("2222222222\r\n");
    					}
    					else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    					{
    						printf("> Receive end...\r\n");
                InFlashWrite(UPGRADE_FLAG_ADDR, Upgrade);       //将升级标志设置为升级
    						printf("33333333333\r\n");
    						Set_state(TO_START);
    						printf("44444444444\r\n");
    						send_command(ACK);
    						printf("55555555555\r\n");
    					
    						HAL_NVIC_SystemReset();
    												
    					}					
    					else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
    					{
    						printf("> Receive data bag:%d byte\r\n",data_state * 128);
    						
    						/* 烧录程序 */
    						WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
    						data_state++;
    						
    						send_command(ACK);		
    					}
    				}
    				else
    				{
    					printf("> Notpass crc\r\n");
    				}
    				
    			}break;
    			case EOT://数据包开始
    			{
    				if(Get_state()==TO_RECEIVE_DATA)
    				{
    					printf("> Receive EOT1...\r\n");
    					
    					Set_state(TO_RECEIVE_EOT2);					
    					send_command(NACK);
    				}
    				else if(Get_state()==TO_RECEIVE_EOT2)
    				{
    					printf("> Receive EOT2...\r\n");
    					
    					Set_state(TO_RECEIVE_END);					
    					send_command(ACK);
    					send_command(CCC);
    				}
    				else
    				{
    					printf("> Receive EOT, But error...\r\n");
    				}
    			}break;	
    		}
    		
    	}
    }
    
    
    3.2.4 ymodelm.h文件设计:
    #ifndef __YMODEM_H
    #define __YMODEM_H
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    
    /*=====用户配置(根据自己的分区进行配置)=====*/
    #define BootLoader_Size 		0x19000U			///< BootLoader的大小 20K
    #define Application_Size		0x32000U			///< 应用程序的大小 40K
    
    #define Application_1_Addr		0x08019000U		///< 应用程序1的首地址
    #define Application_2_Addr		0x0804B000U		///< 应用程序2的首地址
    /*==========================================*/
    #define UPGRADE_FLAG_ADDR          ((uint32_t)0x0807F800)
    
    
    #define SOH		0x01
    #define STX		0x02
    #define ACK		0x06
    #define NACK	0x15
    #define EOT		0x04
    #define CCC		0x43
    
    
    
    /* 升级的步骤 */
    enum UPDATE_STATE
    {
    	TO_START = 0x01,
    	TO_RECEIVE_DATA = 0x02,
    	TO_RECEIVE_EOT1 = 0x03,
    	TO_RECEIVE_EOT2 = 0x04,
    	TO_RECEIVE_END = 0x05
    };
    
    
    
    void ymodem_fun(void);
    
    
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* __YMODEM_H */
    
    

    其运行结果为:

    4 APP2设计:

    此程序可以设计的比较简单:直接输出app2即可。我们还是需要新建工程,并将它转为bin文件:

    4.1 STM32CubeMX设计:

    对于APP2程序,只需要开一路串口用于打印信息即可:

    4.2 keil程序编程

    1、串口重定向:
    此部分不再叙述。

    2、main函数中实现中断向量表偏移:

    __set_PRIMASK(0);//开总中断  1关闭总中断
      SCB->VTOR = FLASH_BASE|0x19000;
    

    main.c文件的整体程序为:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2023 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "stdio.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    int fputc(int ch, FILE *f)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1, 0xFFFF);
        return ch;
    }
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
      __set_PRIMASK(0);//开总中断  1关闭总中断
      SCB->VTOR = FLASH_BASE|0x19000;
      /* 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_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    	printf("app2 start -----\r\n");
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
    		printf("app2\r\n");
    		HAL_Delay(500);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    
    4.3 转为bin文件:

    将同名的文件转为bin文件:

    C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe –bin -o “$L@L.bin” “#L”

    生成的bin文件可在下面的文件夹中找到:

    5 总体实验:

    1、在Xshell中,配置如下图所示:


    右键按下,按下面地步骤进行选择想传输的文件:

    XShell正在使用Ymodelm协议传输数据:

    bin文件传输完成

    开始运行新程序

    5. 总结

    至此,STM32-OTA部分就全部完成了,当然Ymodelm协议后面可以换成任意一个协议,我们只要能获取到,然后将其存到Flash中即可。
    今天也不早了,下班狗要小班了,我们下期再见!!!

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32F103基于STM32CubeMX实现OTA升级(二)代码解析

    发表回复