天天看點

基于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

繼續閱讀