天天看点

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

准备资源 

一 软件

1、ST官网下载基于stm32cube开发的IAP示例:X-CUBE-IAP-USART

      https://www.stmicroelectronics.com.cn/content/st_com/zh/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32cube-expansion-packages/x-cube-iap-usart.html

2、开发工具

       1、STM32CubeIDE   下载:https://www.stmicroelectronics.com.cn/content/st_com/zh/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-ides/stm32cubeide.html

       2、STM32CubeProgrammer  下载:https://www.stmicroelectronics.com.cn/content/st_com/zh/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html

       3、 SecureCRT  可以使用Ymodem协议发送文件

二 硬件

      1、开发板选择 正点原子战舰v3  STM32F103   FLASH 512KB

      2、 Jlink或者ST-link 

 IAP原理

 IAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给用户一个代码文件即可。

      而IAP却能很好的解决掉这个难题,一片STM32芯片的Code(代码)区内一般只有一个用户程序。而IAP方案则是将代码区划分为两部分,两部分区域各存放一个程序,一个叫bootloader(引导加载程序),另一个较user application(用户应用程序)。bootloader在出厂时就固定下来了,在需要变更user application时只需要通过触发bootloader对userapplication的擦除和重新写入即可完成用户应用的更换

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

1 stm32的启动模式选择

   STM32F103ZET6的启动方式有三种:内置FLASH启动、内置SRAM启动、系统存储器ROM启动,通过BOOT0和BOOT1引脚的设置可以选择从哪中方式启动,这里选择内置的FLASH启动。其FLASH的地址为0x08000000—0x0807ffff,共512KB,这些都能从芯片数据手册中直接得到。而这里首要的一个问题是中断的问题。正常情况下发生中断的过程为:发生中断(中断请求)à到中断向量表查找中断函数入口地址à跳转到中断函数à执行中断函数à中断返回。也就是说在STM32的内置的Flash中有一个中断向量表来存放各个中断服务函数的入口地址,内置Flash的分配情况如下:

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发
基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

2 stm32f103内存组织

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发
基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

3 程序的执行流程

在只有一个程序的情况下,程序执行的走向应该如图

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

 STM32F10x有一个中断向量表,这个中断向量表存放在代码开始部分的后4个字节处(即0x08000004),代码开始的4个字节存放的是堆栈栈顶的地址,当发生中断后程序通过查找该表得到相应的中断服务程序入口地址,然后再跳到相应的中断服务程序中执行。上电后从0x08000004处取出复位中断向量的地址,然后跳转到复位中断程序的入口(标号①所示),执行结束后跳转到main函数中(标号②所示)。在执行main函数的过程中发生中断,则STM32强制将PC指针指回中断向量表处(标号③所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数(标号④所示),执行完中断函数后再返回到main函数中来(标号⑤所示)。

若在STM32F103x中使用IAP方案,则内置的Flash分配情况大致如图:

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

在内置的Flash里面添加一个BootLoader程序,BootLoader程序和user application各有一个中断向量表,假设BootLoader程序占用的空间为N+M字节,则程序的走向应该如图2-2所示(借用网友的原图并做改动,其中虚线部分为原图步骤④⑤的走向,本人改为指向灰色部分)。

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

  上电初始程序依然从0x08000004处取出复位中断向量地址,执行复位中断函数后跳转到IAP的main(标号①所示),在IAP的main函数执行完成后强制跳转到0x08000004+N+M处(标号②所示),最后跳转到新的main函数中来(标号③所示),当发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,再跳转到新的中断服务函数中执行(标号④⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。

      对于步骤④⑤,网友认为是:“在main执行的过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0x08000004中断向量表处,而不是新的中断向量表,如图标号④所示,程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示”。我对此的理解是:“当发生中断后,程序从0x08000004(旧)处的中断向量表中得到相应的中断服务函数入口地址,继而跳转到相应的中断服务程序”。但是旧的中断向量列表里边存放的是IAP程序中断函数的入口地址,它是如何得到user程序中断函数的入口地址呢?所以我觉得此种说法是错误的。“当发生中断时PC指针强制会跳转到0x08000004处”这种说法并没有错,只是忽略了后续的一些知识要点而导致这个说法出现矛盾。

      对于步骤④⑤我认为的是,在main函数的执行过程中,如果CPU得到一个中断请求,PC指针本来应该跳转到0x08000004处的中断向量表,由于我们设置了中断向量表偏移量为N+M,因此PC指针被强制跳转到0x08000004+N+M处的中断向量表中得到相应的中断函数地址(待求证),再跳转到相应新的中断服务函数,执行结束后返回到main函数中来。

BootLoader实现过程

 STM32F103ZET6的Flash地址为0x08000000—0x0807ffff共512KB,把这512KB的空间分为两块,第一块大小为32KB存放BootLoader程序,剩余的空间存放用户程序(根据实际情况分配这两块空间的大小,BootLoader程序占用的空间越小越好,则BootLoader地址为0x08000000—0x08007fff,用户程序地址为0x08008000—0x0807ffff。BootLoader流程图大致应该如下:

1 使用STM32CUBEIDE选择芯片STM32F103ZET6  配置时钟 KEY和串口1 生成基本工程

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发
基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

2 将X-CUBE-IAP-USART/STM3210C_EVAL/IAP_Main/Src下 common flash_if menu ymodem 文件拷入工程中,编译 如图:

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

3 main函数修改为如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "key.h"
#include "menu.h"
#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 */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
pFunction JumpToApplication;
uint32_t JumpAddress;
/* 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 */

  /* 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 */

  if(KEY_Scan() == 1)
  {
	/* Initialise Flash */
	FLASH_If_Init();

	/* Display main menu */
	Main_Menu ();
  }
  else
  {
	/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
	if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
	{
	  /* Jump to user application */
	  JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
	  JumpToApplication = (pFunction) JumpAddress;
	  /* Initialize user application's Stack Pointer */
	  __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
	  JumpToApplication();
	}
  }
  /* 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 CPU, AHB and APB busses clocks 
  */
  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 busses 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 */

  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
           

     上电通过检测KEY0按键状态决定进入IAP下载模式还是启动 user flash程序  重点说一下 

    if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)

    {

      JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);

      JumpToApplication = (pFunction) JumpAddress;

      __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);

      JumpToApplication();

    }                          

    if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)  这句的理解首先我们要先说一下APP.bin 的格式  前四个字节存放的堆栈地址指向SRAM的0x20XXXXXX,后四个字节是中断向量表地址如图

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

 stm32f103是小端模式,数据的低字节保存在低位置 ,所以堆栈地址为0x20050000   中断向量表地址为0x08014C01,所以上面那句是判断堆栈地址是否合法

JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4); 由前面图可知这是复位中断向量 保存复位中断函数入口地址

__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);   //设置主函数栈指针

JumpToApplication(); //跳转到APP复位中断函数执行

4  配置Bootloader Flash 区间  配置 STM32F103ZETX_FLASH.ld 中

    RAM    (xrw)    : ORIGIN = 0x20000000,    LENGTH = 64K

    FLASH    (rx)    : ORIGIN = 0x8000000,    LENGTH = 512K

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

5 配置 USER FLASH 区间,这主要是通过串口下载用户代码保存的位置 

#define APPLICATION_ADDRESS     (uint32_t)0x08008000      开始地址在32KB处

#define USER_FLASH_END_ADDRESS        0x08080000         user flash 结束地址在512KB

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

5 配置完以上,BootLoader 工程已经结束, 这里说明一下menu函数:

上电通过串口打印:

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

串口输入字符‘1’进入下载模式, ‘3’加载用户应用代码   串口工具使用SecureCRT  支持Ymodem协议传输文件  这里选择128字节,Ymodem协议单独介绍。

void Main_Menu(void)
{
  uint8_t key = 0;

  Serial_PutString((uint8_t *)"\r\n======================================================================");
  Serial_PutString((uint8_t *)"\r\n=              (C) COPYRIGHT 2016 STMicroelectronics                 =");
  Serial_PutString((uint8_t *)"\r\n=                                                                    =");
  Serial_PutString((uint8_t *)"\r\n=  STM32F1xx In-Application Programming Application  (Version 1.0.0) =");
  Serial_PutString((uint8_t *)"\r\n=                                                                    =");
  Serial_PutString((uint8_t *)"\r\n=                                   By MCD Application Team          =");
  Serial_PutString((uint8_t *)"\r\n======================================================================");
  Serial_PutString((uint8_t *)"\r\n\r\n");

  /* Test if any sector of Flash memory where user application will be loaded is write protected */
  FlashProtection = FLASH_If_GetWriteProtectionStatus();

  while (1)
  {

    Serial_PutString((uint8_t *)"\r\n=================== Main Menu ============================\r\n\n");
    Serial_PutString((uint8_t *)"  Download image to the internal Flash ----------------- 1\r\n\n");
    Serial_PutString((uint8_t *)"  Upload image from the internal Flash ----------------- 2\r\n\n");
    Serial_PutString((uint8_t *)"  Execute the loaded application ----------------------- 3\r\n\n");


    if(FlashProtection != FLASHIF_PROTECTION_NONE)
    {
      Serial_PutString((uint8_t *)"  Disable the write protection ------------------------- 4\r\n\n");
    }
    else
    {
      Serial_PutString((uint8_t *)"  Enable the write protection -------------------------- 4\r\n\n");
    }
    Serial_PutString((uint8_t *)"==========================================================\r\n\n");

    /* Clean the input path */
    __HAL_UART_FLUSH_DRREGISTER(&UartHandle);
	
    /* Receive key */
    HAL_UART_Receive(&UartHandle, &key, 1, RX_TIMEOUT);

    switch (key)
    {
    case '1' :
      /* Download user application in the Flash */
      SerialDownload();
      break;
    case '2' :
      /* Upload user application from the Flash */
      SerialUpload();
      break;
    case '3' :
      Serial_PutString((uint8_t *)"Start program execution......\r\n\n");
      /* execute the new program */
      JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
      /* Jump to user application */
      JumpToApplication = (pFunction) JumpAddress;
      /* Initialize user application's Stack Pointer */
      __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
      JumpToApplication();
      break;
    case '4' :
      if (FlashProtection != FLASHIF_PROTECTION_NONE)
      {
        /* Disable the write protection */
        if (FLASH_If_WriteProtectionConfig(FLASHIF_WRP_DISABLE) == FLASHIF_OK)
        {
          Serial_PutString((uint8_t *)"Write Protection disabled...\r\n");
          Serial_PutString((uint8_t *)"System will now restart...\r\n");
          /* Launch the option byte loading */
          HAL_FLASH_OB_Launch();
        }
        else
        {
          Serial_PutString((uint8_t *)"Error: Flash write un-protection failed...\r\n");
        }
      }
      else
      {
        if (FLASH_If_WriteProtectionConfig(FLASHIF_WRP_ENABLE) == FLASHIF_OK)
        {
          Serial_PutString((uint8_t *)"Write Protection enabled...\r\n");
          Serial_PutString((uint8_t *)"System will now restart...\r\n");
          /* Launch the option byte loading */
          HAL_FLASH_OB_Launch();
        }
        else
        {
          Serial_PutString((uint8_t *)"Error: Flash write protection failed...\r\n");
        }
      }
      break;
	default:
	Serial_PutString((uint8_t *)"Invalid Number ! ==> The number should be either 1, 2, 3 or 4\r");
	break;
    }
  }
}
           

APP 代码实现过程:

  1  程序比较简单,随便LED 串口就可以  但是一定要生成BIN文件   

          keil生成bin文件: 这里是相对路径   fromelf位于keil安装目录下

fromelf --bin -o "[email protected]" "#L"
           
基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

stm32cubeide生成bin文件

arm-none-eabi-objcopy "${ProjName}.elf" -O binary "${ProjName}.bin"
           
基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

2  更改下载地址和文件大小,APP的起始地址 = BootLoader起始地址 + BootLoader的文件大小

      这里改为0x08008000  32KB   大小改为480KB

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

3 更改中断向量偏移地址

      1. STM32标准库设置中断向量表:

  NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0ffset);

       2. STM32HAL库设置中断向量表:

  SCB->VTOR = FLASH_BASE | offset;

这里设置如下:

基于Ymodem协议的串口IAP升级---STM32CUBEIDE开发

BootLoader代码路径:https://download.csdn.net/download/weixin_39949884/11984766

继续阅读