STM32 BootLoader(Cortex-M3)入门指南

读前须知:

PC : Win10

Keil5 Version :5.30

STM32CubeMX Version:6.2.1

芯片 : STM32F103ZET6

​ 不指定 板子 , M3 芯片 都可 , 在这里 使用 HAL 库 ,标准库不做说明.

​ **M0 内核 注意 ,M0 的芯片 内部 没有 向量表 偏移寄存器 VTOR ,需要手动 修改 启动文件实现(xxx.s) **

特殊说明:(用作了解)

1.为了 正常 执行 我们的代码 ,我们先 对 板子的 启动 方式进行检查

image-20250218214254252

选择: BOOT1 = 0 , BOOT0 = 0 ( 主闪存存储器 )(ref :STM32中文参考手册_V10 ,P33)

2.为了正常配置 Boot 分区 我们需要查需要 一下 Datasheet中的 闪存 组织 ( 以下是 我所使用的芯片对应的闪存组织 )

image-20250218215323955

  • 每个Page 是 2K 字节 大小 ,共 255+1 页 , 总 Flash 大小 :512K . 用来确定 Boot分区大小 (不产生冲突的情况下)(ref:TM32中文参考手册_V10 , P30 )
  • 3.向量表选择位置

    image-20250218220428597

    我们选定的的地址 必需是 **可以被 64 * 4 (每个向量 占用 一个 Word ,也就是 4 字节,所以 我们可知 一个向量表 占用 256 字节 ) 整除 ** 的地址 .

    4.部分 知识 :

    Boot Loader ( 引导 加载 ) 是 这个 单词的 含义 . Bootloader 代码 所以 我们一般 可以 称之为 引导加载 程序.

    关于 Bootloader 由来 ,互联网上 有着 许多 文献 ,自行 查找 ( 想要了解的)

    以及 关于 0x0800 0000 作为 程序 开始的 由来

    image-20250218222321573

    BootLoader 流程 :

    img

    Url:
    https://bbs.huaweicloud.com/blogs/233359
    

    我们简单 讲述 一下 .以 文本的方式(水平有限, 如有错误请批评指正):

    ​ 1.(BOOT0 = 0 ,BOOT1 = X )芯片 上电 ,cpu 从栈顶 获取 主程序 的(0x0000_0000) 堆栈地址(MSP Main Stack Pointer) ,然后跳到 Reset Handler 异常 执行 (0x0000_0004), 这个使用 主闪存映射(0x0800_0000)到 0x0000_0000这个地址,那么 ,上电执行就是 0x0800_0000 里面的内容 ,之后就执行 Reset Handler(0x0800_0004) 异常执行的函数.( DCD 是 一个 字 在32 中 代表4字节大小 )

    image-20250221204439702

    这是 Reset Handler 中的程序 ,加载 ,跳转执行 ,返回 ,加载 _main 函数 ,然后 ,无返回值的跳转到 __main 函数中 .即跳入 你在main 函数中写的代码

    image-20250221204818778

    //__Main 执行了什么
    

    image-20250221211944447

    //Doc 
    //Url : https://developer.arm.com/documentation/100748/0618/Embedded-Software-Development/Application-startup
    //国内可能访问不了
    // 更详细 一点 在  << Arm Compiler C Library Startup and Initialization >> (Arm 编译器 C 库的启动和初始化)
    //url:https://developer.arm.com/documentation/dai0241/latest
    

    image-20250221212627835

    因为要使用 C 语言 ,这些都是 执行 C语言 所需要的 环境设置,(也可以更改自己的__main 函数 )

    正式进入 Bootloader 的编写 (笔者 使用 J-Link 下载器 ,使用 其他的 ,在下载器设置的位置 自行设置)

    (这里再次说明,本次使用的 是 Cortex-M3 的处理器 , 这个处理器 是有 向量偏移寄存器的, 地址 :0xE000_ED08,好处就是我们不需要,在执行 应用程序时 ,再次在 应用程序 flash 头 前 重写一遍 这些异常 handler 的执行地址)

    项目配置

    创建项目

    ​ ① 选择芯片

    image-20250221221419399

    ②配置时钟 (外部高速时钟,使用晶振)

    image-20250221221530127

    ③配置下载口(我这里时J-Link,我配置 四口 ,如果你们时 DAP ,或者是 STLink ,可以 Serial Wire )

    J-LINK :

    image-20250221221629700


    DAP,ST-LINK:

    image-20250221221809592

    ④ 配置 自己的 LED 引脚:

    image-20250221222002303

    ⑤配置时钟树

    image-20250221222032679

    ⑥ 配置项目生成路径 ,以及项目需要生成的依赖

    image-20250221222154625


    image-20250221222244625

    ⑦ 生成 并 打开 项目

    image-20250221222322300

    项目中代码

    image-20250221223734807

    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    uint32_t JumpAddress  = 0; // 
    
    typedef void ( *pFunction )(void);  // 
    
    pFunction JumpToApplication; 
    
    #define FLASH_BASE_ADDRESS  (( uint32_t ) 0x8000000)
    #define BOOTLOADER_OFFSET   (( uint32_t ) 0x0004000)
    #define VERTION_CONTROL     (( uint32_t ) 0x0001000)
    #define APPLICATION_ADDRESS  VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS//
    
    unsigned char ucJumpToAppcationFlag = 0;
    
    
    /* USER CODE END 0 */
    

    image-20250221223851427

      /* USER CODE BEGIN 1 */
    	unsigned char ucI = 0;
      /* USER CODE END 1 */
    

    在 Main 函数的 while 的 / USER CODE BEGIN 3 / 循环中写入

        /* USER CODE END WHILE */
    	
        /* USER CODE BEGIN 3 */
    		
    		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
    		
    		HAL_Delay(300);
    		
    		if( ucJumpToAppcationFlag == 1)
    		{
    			ucJumpToAppcationFlag = 0;
    		
    			if (((*(__IO uint32_t*)FLASH_BASE_ADDRESS) & 0x2FFE0000 ) == 0x20000000) // 
    			{
    				
    				JumpAddress = *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ); // 
    				
    				JumpToApplication = (pFunction) JumpAddress; // 
    				__set_MSP(*(__IO uint32_t *) APPLICATION_ADDRESS); // 
    				__disable_irq();  
    				
    			
    				JumpToApplication();
    					
    
    			
    			}
    		}
    		if(ucI > 30) ucJumpToAppcationFlag = 1 ;
    		ucI++;
    		
    		
    
    

    image-20250221223808661

    步骤 1

    打开魔法棒

    image-20250221213013603

    image-20250222144325632

    设置 对应 程序 存储 的 ROM地址 ,RAM 不需要调整 默认 .

    因为 Bootloader 是 默认 上电执行的程序 BOOT0 = 0 时.

    步骤 2

    image-20250221215336037

    fromelf --bin -o ".\bin_file\Bootloader.bin" "#L"
    

    在这个地方时写入 上图的 执行指令 ( 在编译之后运行 )

    就会在你的 项目 地址 – > MDK-ARM – > bin_file 文件夹中生成你设定的名字的bin 文件

    image-20250221222646101

    我们知道了 BootLoader 的文件 大小 就可以设置 对应 程序 存放 地址了

    步骤3

    下载器设置

    image-20250221220604371

    至此,Bootloader 的项目环境 配置完成


    我们可以 重新生成一份新的也可以 ,复制现在的项目 用作 Application 项目 ,推荐重新生成一个新的项目.这样两个项目就没有重叠的地方 独立设置(放在boot loader 设置了的东西 ,你忘记改回来)



    Application 项目的环境设置

    除了 Bootloader 中的步骤 1 不同,其他 修改 如上

    这里需要注意 :

    一定要看你的Bootloader 的大小 , 然后划分出大于你Bootloader 程序的 Flash 给 Bootloader 存储

    并且 程序 的 首地址 一定要符合 X / 254 = ( Int) 是 个整数 , 原因 在这里 讲清除 了 —-> 3.向量表选择位置

    步骤 1 修改成 下面的值

    image-20250222144216446

    Application 的程序 :

    image-20250222145153686

    至此 分别把他们烧录进去就可以了

    // Bootloader Main 函数 代码
    
    #include "main.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* 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 */
    
    /* 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 */
    uint32_t JumpAddress  = 0; // 
    
    typedef void ( *pFunction )(void);  // 函数 指针
    
    pFunction JumpToApplication;  //定义一个 函数指针 变量
    
    #define FLASH_BASE_ADDRESS  (( uint32_t ) 0x8000000)  // Flash 首地址 (用来存放 Bootloader )
    #define BOOTLOADER_OFFSET   (( uint32_t ) 0x0001000)  // bootloader 的大小
    #define VERTION_CONTROL     (( uint32_t ) 0x0000000)  // 给产品 ,以及 升级做 记录的区域 
    #define APPLICATION_ADDRESS  VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS // 主程序 开始地址
    
    unsigned char ucJumpToAppcationFlag = 0; //用来做 延迟 进入主程序的变量 , 
    // 在实际开发的时候可以使用 flash 来存储
    /**
    *uint32_t BOOTLOADER_UPLOADING __attribute__((aligned(4), at(BOOTLOADER_OFFSET + FLASH_BASE_ADDRESS + 0))) = 0; // 字节对齐 
    **/
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    	unsigned char ucI = 0;
      /* 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();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    	
        /* USER CODE BEGIN 3 */
    		
    		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
    		
    		HAL_Delay(300);
    		
    		if( ucJumpToAppcationFlag == 1)
    		{
    			ucJumpToAppcationFlag = 0;
    		
    			if (((*(__IO uint32_t*)FLASH_BASE_ADDRESS) & 0x2FFE0000 ) == 0x20000000) // 判断堆栈指针是否在 主 堆栈
    			{
    				
    				JumpAddress = *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ); //  获取 Reset handler 的执行地址
    				
                    //(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ) -- >这个 说的是 将它 强制 转成 __IO uint32_t* 变量 , __IO  一个重定义volatile ,告诉编译器 不需要 优化 我这个变量. 对于敏感的 变量 ,一定要 用 volatile .
                    // *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ) 就是 取 这个地址 中的 值 
                    
    				JumpToApplication = (pFunction) JumpAddress; //  
    				__set_MSP(*(__IO uint32_t *) APPLICATION_ADDRESS); // 设置堆栈指针 ,我们从 .s 文件中 ,就知道 程序的 首地址 就是 堆栈顶 地址
    				__disable_irq();  // 关闭中断 
                    // 这里其实还要 清除一下 中断标志位 ,防止 后续 触发
    				
    			
    				JumpToApplication(); //跳转到 Application
    					
    
    			
    			}
    		}
    		if(ucI > 30) ucJumpToAppcationFlag = 1 ;
    		ucI++;
    		
    		
      }
      /* 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 */
    
    
    //Application Main 函数
    
    #include "main.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* 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 */
    
    /* 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 */
    
    
    // BOOTLOADER 3KB   3*1024 = 3072  0xC00  // 这个地址必须要被 256 整除 
    // 这里我们可以 稍微 在大一点 ,给 Bootloader  4k 的大小  也就是 0x1000
    
    #define FLASH_BASE_ADDRESS  (( uint32_t ) 0x8000000) 
    #define BOOTLOADER_OFFSET   (( uint32_t ) 0x0001000)
    #define VERTION_CONTROL     (( uint32_t ) 0x0000000)
    
    
    #define APPLICATION_ADDRESS  VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS 
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    	// 向量表偏移 
    	SCB->VTOR = APPLICATION_ADDRESS; // 设置向量表的偏移 
        //有些人 疑惑 为什么这个 向量表的偏移 可以写在 Application 中 ,或者为什么这个时候 才执行
        /**
        * 我的理解 (有误请指针):
        * MSP PUSH 是 --SP 的, 当跳转到这个 主函数 时,直接运行 Reset Handle ( 主程序 ),主程序中的 Reset Handler 重新 执行 SystemInit(),和__main
        * __main 执行完 之后 就 调用 c main 函数 ,这个时候就到了 执行上面的语句 .这个期间 MSP 一直在 Reset Handler (进入 主程序 开始),中间 中断我们也	* 关闭了,所以,可以放到这里.能不能放 BootLoader 区 你们 可以 自己 搜索下 .(我没有思考过)
        *
        **/
        
        
        
        
    	__enable_irq(); //使能 中断
      /* 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();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    		
    		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
    		HAL_Delay(100);
    		
      }
      /* 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 */
    

    * 以上代码不能直接运行,需要配合硬件 ,修改*

    代码链接:

     https://gitee.com/kysfh/stm32_-bootloader
    
    转载  需要 署名 和 注明来源
    

    作者:KYSDFH

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 BootLoader(Cortex-M3)入门指南

    发表回复